Подключите два клиентских разъема

Допустим, в Java есть два типа сокетов:

  • серверные сокеты "ServerSocket"
  • клиентские сокеты или просто «Розетка»

Представьте себе ситуацию двух процессов:

X = клиент
Y = сервер

Серверный процесс Y: имеет «ServerSocket», который прослушивает TCP-порт
Клиентский процесс X: отправляет запрос на соединение через «Socket» в Y.

Y: Затем метод accept() возвращает новый тип клиента «Socket»,
когда это происходит, два Socket «соединяются»,

Итак: сокет в клиентском процессе соединен с сокетом в серверном процессе.
Затем: чтение / запись через сокет X похоже на чтение / запись через сокет Y.
Теперь два клиента Розетки соединяются между собой !!

Но ...
Что, если я создам два клиентских сокета в одном процессе и хочу, чтобы они были «соединены между собой»?

... даже возможно?

Скажем, как соединить два клиентских сокета без использования промежуточного ServerSocket?

Я решил это, создав два потока для непрерывного чтения A и записи B, а другой - для чтения B и записи A ...
Но я думаю, что может быть лучший способ ... (Эти энергопотребляющие потоки не требуются при подходе клиент-сервер)

Любая помощь или совет будут оценены !! Спасибо


Изменить:

Пример приложения: «Существующее серверное приложение может быть преобразовано в клиентское». Например, сервер VNC, один клиентский сокет подключается к VNC-серверу, и создается другой клиентский сокет (для подключения к промежуточному серверу), затем приложение соединяет два клиента, в результате чего VNC-сервер становится клиентским приложением! Кроме того, общедоступный IP-адрес не требуется.

VNCServer --- MyApp ---> | промежуточный сервер | <---Пользователь


person Hernán Eche    schedule 05.04.2010    source источник
comment
спасибо за комментарий, почистил немного !, и избегаю заглавных букв. За то, что это ошибочная тема, я пытался объяснять ее медленно, спасибо   -  person Hernán Eche    schedule 05.04.2010
comment
Поправьте меня, если я ошибаюсь, но вот что я понял: у вас MyApp работает на одном сервере, VNCServer - на другом, а middleServer - на третьем. Пользователь подключается к промежуточному серверу (который, я думаю, имеет общедоступный IP-адрес). Вы хотите подключиться к VNCServer с промежуточного сервера, но ваш VNCServer находится внутри некоторой интрасети, а промежуточный сервер находится за пределами этой сети. Итак, вы хотите навести мост между соединениями. Для этого вы написали myApp (который находится внутри интрасети) открывает соединение с промежуточным сервером, затем с VNCServer, и вы хотите передать данные с промежуточного сервера на VNCServer. Это близко?   -  person Elister    schedule 05.04.2010
comment
@ Hernán Eche, спасибо, что очистили его и предоставили дополнительную информацию! Мой голос "против" удален.   -  person Bart Kiers    schedule 05.04.2010
comment
@Elister, да, это близко, (VNCServer и MyApp могут быть даже на одной машине), и да, VNCServer может быть внутри интрасети, и я соединяю соединения, у меня работает два MyApp, один в интрасети для соединения VNCServer с промежуточным сервером (скажем, разъем клиент-сокет-клиент) и один на промежуточном сервере для воссоздания сервера (скажем, разъем-сервер-сокет-сервер), я даже назвал их адаптерами, потому что напоминает мне женский -женщины, мужчины-мужчины аудио или любые переходники =)   -  person Hernán Eche    schedule 05.04.2010
comment
Кто-то может помочь вам оптимизировать код, только если вы его сначала опубликуете.   -  person Babar    schedule 08.06.2010
comment
Да! Думаю, я понял! Думаю, я знаю, чего ты хочешь. Прямо как TeamViewer. У них есть сервер, на котором хранится информация о том, кто находится в сети. Этот главный сервер имеет статический общедоступный IP-адрес, к которому могут подключаться все клиенты. Это означает, что VNCServer на самом деле не является большой серверной машиной. Он также является клиентом сети, но у него работает ServerSocket. Таким образом, VNCServer может сообщить главному серверу, где он находится (общедоступный IP-адрес).   -  person Martijn Courteaux    schedule 15.06.2010


Ответы (12)


Прежде всего, не называйте принятый клиент (на стороне сервера) его сокет Client Socket. Это очень сбивает с толку.

Скажем, как соединить два клиентских сокета без использования промежуточного ServerSocket?

Это невозможно. Всегда нужно делать серверную часть, которая может принимать клиентов. Теперь возникает вопрос: какая сторона соединения должна быть серверной?
Что нужно учитывать при принятии этого решения:

  • Сервер должен иметь статический общедоступный IP.
  • Сервер, который находится после подключения маршрутизатора, должен выполнять «переадресацию портов». (См. UPnP)
  • Клиент должен знать, к какому хосту он должен подключиться (общедоступный IP-адрес).

Средний сервер

I don't see what you want to do with that third server. Maybe holding the VNCServer's public IP? *Elister* wrote, you want to make a brigde between the client and the VNCServer. I don't see the advantage of it.

Почему бы сразу не подключиться к VNCServer?

Но если вы действительно этого хотите, вы можете сделать такую ​​ситуацию:


      /   VNCServer (Server Running)  <---.
     |                                     |
LAN -|                             Connects to VNCServer
     |                                     |
      \   MyApp (Server Running --> Accepts from Middle Server) <------.
                                                                        |
                                                            (Through a router)
                                                                        |
     Middle server (Server Running --> Accepts client) ---> Connects to Your App
                                             ^
                                             |
                                    (Through a router)
                                             |
     Client --> Connects to Middle Server --°

А вот как это выглядит без третьего сервера (что рекомендую):


      /   VNCServer (Server Running)  <---.
     |                                     |
LAN -|                             Connects to VNCServer
     |                                     |
      \   MyApp (Server Running --> Accepts Clients) <------.
                                                             |
                                                      (Through a router)
                                                             |
     Client --> Connects to MyApp --------------------------°


РЕДАКТИРОВАТЬ:

Думаю, теперь я понял:

Мы должны представить вашу ситуацию так:

                             Your Main Server (What you called middle server)
                    (1)         |       |      (2)
            /⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻/         \⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻\
           |                                                |
      Your VNCServer   <---------------------------->   The client
         (5)                        (3)

(1) VNCServer подключается к главному серверу. Итак, главный сервер получил IP-адрес VNCServer.
(2) Клиент подключается к главному серверу.
(3) Теперь главный сервер знает, где находятся сервер и клиент. Затем он отправляет клиенту информацию о том, где находится сервер. Затем клиент подключится к IP, полученному от главного сервера. Это, конечно, IP от VNCServer.
(5) VNCServer запущен, это сервер для приема клиента.

Теперь можно начинать совместное использование рабочего стола.

Я думаю, что это наиболее рекомендуемая ситуация, которую вы можете себе представить.
Конечно, писать это на Java - это вам.

person Martijn Courteaux    schedule 13.06.2010
comment
@ Hernán Eche: +1, потому что теперь я понимаю, в чем ваша проблема ;-) - person Martijn Courteaux; 15.06.2010
comment
Это был выбранный ответ, потому что искусство ASCII !! = S странно ... это последний раз, когда я использую награду, это отстой ... и @Martijn Corteaux, нет, ваше решение невозможно, соединение, помеченное как (3), не может быть выполнено, потому что клиент и VNCServer может быть за NAT / брандмауэром / и т. Д., Вы продолжаете думать, что они могут быть достигнуты как сервер, они этого не делают - person Hernán Eche; 15.06.2010

Зачем вам это нужно?

Если вы хотите иметь систему типа «одноранговая сеть», вам просто нужно, чтобы каждый клиент запускал и клиентский, и серверный сокет - серверный сокет для приема соединений от других клиентов и клиентский сокет для установления соединений с другими.

ETA: не совсем понятно, о чем вы спрашивали в исходном вопросе, но после вашего редактирования кажется, что вы хотите создать что-то вроде прокси-сервер.

В вашем примере ваше приложение создаст два клиентских сокета, один из которых подключается к VNCServer, а другой - к «среднему серверу». Тогда «средний сервер» будет иметь два серверных сокета (один для подключения вашего приложения, а другой для подключения пользователя. Затем внутренне ему необходимо знать, как сопоставить эти сокеты и передавать данные между ними.

person Eric Petroelje    schedule 05.04.2010
comment
Конвертировать любое серверное приложение в клиентское. Он работает правильно, но я просто сомневаюсь, что двухпоточный подход будет хорошим, или можно ли его решить с помощью другого языка, библиотеки или средства ОС. - person Hernán Eche; 05.04.2010
comment
@ Hernán - Я бы не стал беспокоиться о двухпоточном подходе. Пока вы эффективно управляете потоками, переводя их в состояние ожидания на сокетах, все будет в порядке. - person Eric Petroelje; 15.06.2010

ServerSocket позволяет вам прослушивать соединения на определенном порту. Когда серверный сокет принимает соединение, он порождает другой поток и перемещает соединение на другой порт, поэтому исходный порт все еще может прослушивать дополнительные соединения.

Клиент инициирует соединение на известном порте. Затем, как правило, клиент отправляет некоторый запрос, и сервер отвечает. Это будет повторяться до тех пор, пока связь не будет завершена. Это простой подход клиент / сервер, который использует Интернет.

Если вам не нужен этот механизм и запросы могут приходить из любого сокета в любое время, тогда реализация потоков чтения и записи так, как вы это делаете, кажется подходящей.

Внутри они по-прежнему используют механизмы ожидания, поэтому вы не должны видеть большой загрузки ЦП, пока они ждут прибытия данных.

Я думаю, вам все еще нужен один конец, чтобы быть серверным сокетом, потому что я не думаю, что возможно, чтобы клиентский сокет принимал соединение. ClientSocket подразумевает TCP, для которого требуется соединение. Если вы использовали DatagramSocket, что подразумевает UDP, у вас могло бы быть взаимодействие клиента с клиентом без соединения.

person Marcus Adams    schedule 05.04.2010
comment
Он НЕ перемещает соединение на другой порт. Это городской миф. Смотрите любой вывод netstat на любом сервере. - person user207421; 10.08.2015

Это код, в котором я соединил два Socket без каких-либо ServerSocket:

package primary;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class Main {
    private static Object locker;
    public static void main(String[] args) {

        locker = new Object();
        final int[][] a = new int[6][];
        final int[][] b = new int[6][];
        final int[][] c;
        a[0] = new int[] {12340, 12341};
        a[1] = new int[] {12342, 12344};
        a[2] = new int[] {12342, 12343};
        a[3] = new int[] {12340, 12345};
        a[4] = new int[] {12344, 12345};
        a[5] = new int[] {12341, 12343};

        b[0] = new int[] {22340, 22341};
        b[1] = new int[] {22342, 22344};
        b[2] = new int[] {22342, 22343};
        b[3] = new int[] {22340, 22345};
        b[4] = new int[] {22344, 22345};
        b[5] = new int[] {22341, 22343};

        c = a;
        SwingUtilities.invokeLater(
                new Runnable() {

            @Override
            public void run() {
                Client client1 = new Client("client1", c[0], c[1]);
                client1.exe();
                client1.setLocation(0, 200);
                client1.setVisible(true);
                client1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            }
        });
        SwingUtilities.invokeLater(
                new Runnable() {

            @Override
            public void run() {
                Client client2 = new Client("client2", c[2], c[3]);
                client2.exe();
                client2.setLocation(400, 200);
                client2.setVisible(true);
                client2.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            }
        });
        SwingUtilities.invokeLater(
                new Runnable() {

            @Override
            public void run() {
                Client client3 = new Client("client3", c[4], c[5]);
                client3.exe();
                client3.setLocation(800, 200);
                client3.setVisible(true);
                client3.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

            }
        });
    }
}

package primary;

import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.*;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class Client extends JFrame implements Runnable {
    private final String myName;
    private ServerSocket listener;
    private Socket connection1;
    private Socket connection2;
    private ObjectOutputStream output1;
    private ObjectOutputStream output2;
    private ObjectInputStream input1;
    private ObjectInputStream input2;
    private Object receiveObject;
    private Object1 sendObject1;
    private Object2 sendObject2;
    private final int[] myLocalPort;
    private final int[] connectionPort;
    private ExecutorService service;
    private Future<Boolean> future1;
    private Future<Boolean> future2;

    public Client(final String myName, int[] myLocalPort, int[] connectionPort) {
        super(myName);
        this.myName = myName;
        this.myLocalPort = myLocalPort;
        this.connectionPort = connectionPort;
        sendObject1 = new Object1("string1", "string2", myName);
        sendObject2 = new Object2("string1", 2.5, 2, true, myName);
        initComponents();
    }
    public void exe() {
        ExecutorService eService = Executors.newCachedThreadPool();
        eService.execute(this);
    }

    @Override
    public void run() {
        try {
                displayMessage("Attempting connection\n");
                try {
                    connection1  = new Socket(InetAddress.getByName("localhost"), connectionPort[0], InetAddress.getByName("localhost"), myLocalPort[0]);
                    displayMessage(myName + " connection1\n");
                } catch (Exception e) {
                    displayMessage("failed1\n");
                    System.err.println("1" + myName + e.getMessage() + "\n");
                }
                try {
                    connection2  = new Socket(InetAddress.getByName("localhost"), connectionPort[1], InetAddress.getByName("localhost"), myLocalPort[1]);
                    displayMessage(myName + " connection2\n");
                } catch (Exception e) {
                    displayMessage("failed2\n");
                    System.err.println("2" + myName + e.getMessage() + "\n");
                }
            displayMessage("Connected to: " + connection1.getInetAddress().getHostName() + "\n\tport: "
                + connection1.getPort() + "\n\tlocal port: " + connection1.getLocalPort() + "\n"
                + connection2.getInetAddress().getHostName() + "\n\tport: " + connection2.getPort()
                + "\n\tlocal port: " + connection2.getLocalPort() + "\n\n");
            output1 = new ObjectOutputStream(connection1.getOutputStream());
            output1.flush();
            output2 = new ObjectOutputStream(connection2.getOutputStream());
            output2.flush();
            input1 = new ObjectInputStream(connection1.getInputStream());
            input2 = new ObjectInputStream(connection2.getInputStream());
            displayMessage("Got I/O stream\n");
            setTextFieldEditable(true);
            service = Executors.newFixedThreadPool(2);
            future1 = service.submit(
                    new Callable<Boolean>() {

                @Override
                public Boolean call() throws Exception {
                    try {
                        processConnection(input1);
                        displayMessage("input1 finished");
                    } catch (IOException e) {
                        displayMessage("blah");
                    }
                    return true;
                }
            });
            future2 = service.submit(
                    new Callable<Boolean>() {

                @Override
                public Boolean call() throws Exception {
                    try {
                        processConnection(input2);
                        displayMessage("input2 finished");
                    } catch (IOException e) {
                        displayMessage("foo");
                    }
                    return true;
                }
            });
        } catch (UnknownHostException e) {
            displayMessage("UnknownHostException\n");
            e.printStackTrace();
        } catch (EOFException e) {
            displayMessage("EOFException\n");
            e.printStackTrace();
        } catch (IOException e) {
            displayMessage("IOException\n");
            e.printStackTrace();
        } catch(NullPointerException e) {
            System.err.println("asdf " + e.getMessage());
        } finally {
            try {
                displayMessage("i'm here\n");
                if((future1 != null && future1.get()) && (future2 != null && future2.get())) {
                    displayMessage(future1.get() + " " + future2.get() + "\n");
                    displayMessage("Closing Connection\n");
                    setTextFieldEditable(false);
                    if(!connection1.isClosed()) {
                        output1.close();
                        input1.close();
                        connection1.close();
                    }
                    if(!connection2.isClosed()) {
                        output2.close();
                        input2.close();
                        connection2.close();
                    }
                    displayMessage("connection closed\n");
                }
            } catch (IOException e) {
                displayMessage("IOException on closing");
            } catch (InterruptedException e) {
                displayMessage("InterruptedException on closing");
            } catch (ExecutionException e) {
                displayMessage("ExecutionException on closing");
            }
        }
    }//method run ends
    private void processConnection(ObjectInputStream input) throws IOException {
        String message = "";
        do {
            try {
                receiveObject = input.readObject();
                if(receiveObject instanceof String) {
                    message = (String) receiveObject;
                    displayMessage(message + "\n");
                } else if (receiveObject instanceof Object1) {
                    Object1 receiveObject1 = (Object1) receiveObject;
                    displayMessage(receiveObject1.getString1() + " " + receiveObject1.getString2()
                        + " " + receiveObject1.toString() + "\n");
                } else if (receiveObject instanceof Object2) {
                    Object2 receiveObject2 = (Object2) receiveObject;
                    displayMessage(receiveObject2.getString1() + " " + receiveObject2.getD()
                        + " " + receiveObject2.getI() + " " + receiveObject2.toString() + "\n");
                }
            } catch (ClassNotFoundException e) {
                displayMessage("Unknown object type received.\n");
            }
            displayMessage(Boolean.toString(message.equals("terminate\n")));
        } while(!message.equals("terminate"));
        displayMessage("finished\n");
        input = null;
    }
/**
 * This method is called from within the constructor to initialize the form.
 * WARNING: Do NOT modify this code. The content of this method is always
 * regenerated by the Form Editor.
 */
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">                          
private void initComponents() {

    dataField = new javax.swing.JTextField();
    sendButton1 = new javax.swing.JButton();
    sendButton2 = new javax.swing.JButton();
    jScrollPane1 = new javax.swing.JScrollPane();
    resultArea = new javax.swing.JTextArea();

    setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

    dataField.setEditable(false);
    dataField.addActionListener(new java.awt.event.ActionListener() {
        public void actionPerformed(java.awt.event.ActionEvent evt) {
            dataFieldActionPerformed(evt);
        }
    });

    sendButton1.setText("Send Object 1");
    sendButton1.addActionListener(new java.awt.event.ActionListener() {
        public void actionPerformed(java.awt.event.ActionEvent evt) {
            sendButton1ActionPerformed(evt);
        }
    });

    sendButton2.setText("Send Object 2");
    sendButton2.addActionListener(new java.awt.event.ActionListener() {
        public void actionPerformed(java.awt.event.ActionEvent evt) {
            sendButton2ActionPerformed(evt);
        }
    });

    resultArea.setColumns(20);
    resultArea.setEditable(false);
    resultArea.setRows(5);
    jScrollPane1.setViewportView(resultArea);

    javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
    getContentPane().setLayout(layout);
    layout.setHorizontalGroup(
        layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
            .addContainerGap()
            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                .addComponent(jScrollPane1)
                .addComponent(dataField, javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
                    .addComponent(sendButton1)
                    .addGap(18, 18, 18)
                    .addComponent(sendButton2)
                    .addGap(0, 115, Short.MAX_VALUE)))
            .addContainerGap())
    );
    layout.setVerticalGroup(
        layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addGroup(layout.createSequentialGroup()
            .addContainerGap()
            .addComponent(dataField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addGap(18, 18, 18)
            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                .addComponent(sendButton1)
                .addComponent(sendButton2))
            .addGap(18, 18, 18)
            .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 144, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
    );

    pack();
}// </editor-fold>                        

private void dataFieldActionPerformed(java.awt.event.ActionEvent evt) {                                          
    // TODO add your handling code here:
    sendData(evt.getActionCommand());
    dataField.setText("");
}                                         

private void sendButton1ActionPerformed(java.awt.event.ActionEvent evt) {                                            
    // TODO add your handling code here:
    sendData(sendObject1);
}                                           

private void sendButton2ActionPerformed(java.awt.event.ActionEvent evt) {                                            
    // TODO add your handling code here:
    sendData(sendObject2);
}                                           

/**
 * @param args the command line arguments
 */
private void displayMessage(final String messageToDisplay) {
    SwingUtilities.invokeLater(
            new Runnable() {
        @Override
                public void run() {
                    resultArea.append(messageToDisplay);
                }
            });
}
private void setTextFieldEditable(final boolean editable) {
    SwingUtilities.invokeLater(
            new Runnable() {

        @Override
        public void run() {
            dataField.setEditable(editable);
        }
    });
}
private void sendData(final Object object) {
    try {
        output1.writeObject(object);
        output1.flush();
        output2.writeObject(object);
        output2.flush();
        displayMessage(myName + ": " + object.toString() + "\n");
    } catch (IOException e) {
        displayMessage("Error writing object\n");
    }
}
// Variables declaration - do not modify                     
    private javax.swing.JTextField dataField;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JTextArea resultArea;
    private javax.swing.JButton sendButton1;
    private javax.swing.JButton sendButton2;
    // End of variables declaration                   
}

Здесь Object1 и Object2 - это всего лишь два Serializable Объекта. Вроде все розетки подключаются идеально. Если я System.exit () без вызова close() методов для сокетов и их входных, выходных потоков и повторного запуска, он все равно будет работать нормально. Но если я использую System.exit (), убедившись, что методы close() вызываются, и снова запускаю его снова, я получаю следующее:

1client2Address already in use: connect

1client3Address already in use: connect

2client3Address already in use: connect

asdf null
1client1Connection refused: connect

2client2Connection refused: connect

asdf null
2client1Connection refused: connect

asdf null

Я снова и снова бегаю снова и получаю это, если только я не подожду определенное время и не перезапущу снова, он работает так же хорошо, как и в первый раз.

person SagittariusAStar_MW    schedule 04.08.2012
comment
Ваше приложение действительно работает. Однако, если я запустил его с помощью простого connection1 = new Socket(with 4 params); в консольном приложении, он сразу же выйдет из строя с connection refused. Я не уверен, что является ключевым фактором для работы вашего приложения. - person Leon; 29.12.2017
comment
Не могли бы вы упростить его до двух клиентов и без графического интерфейса? - person Leon; 29.12.2017

Вы пытаетесь создать имитацию сокета? Если это так, издевательство над обеими сторонами трубы может быть немного сложнее, чем необходимо.

С другой стороны, если вы просто хотите создать канал данных между двумя потоками, вы можете использовать PipedInputStream и PipedOutputStream.

Однако, не имея дополнительной информации о том, чего вы пытаетесь достичь, я не могу сказать вам, подходит ли какой-либо из этих вариантов или лучше другое.

person Jim Rush    schedule 05.04.2010

socket (в терминах сети) состоит из 2 конечных точек (клиентское и серверное приложение) и 2 streams. Выходной поток клиента - это входной поток сервера и наоборот.

Теперь попробуйте представить, что произойдет, если поток записывает много данных в поток, пока никто не читает ... Есть буферы, правда, но они не безграничны и могут различаться по размеру. В конце концов, ваш поток записи достигнет предела буфера и будет блокироваться до тех пор, пока кто-нибудь не освободит буфер.

Сказав это, теперь вы должны знать, что для этого потребуются как минимум два разных потока на поток: один, который записывает, а другой, читает записанные байты.

Если ваш протокол является стилем запрос-ответ, вы можете использовать 2 потока на сокет, но не меньше.

Вы можете попробовать заменить сетевую часть своего приложения. Просто создайте абстрактный интерфейс, в котором вы можете скрыть всю сетевую часть, например:

interface MyCommunicator{
  public void send(MyObject object);
  public void addReader(MyReader reader);
}

interface MyReader{ //See Observer Pattern for more details
  public void received(MyObject object);
}

Таким образом, вы можете легко удалить всю сеть (включая кодирование и декодирование ваших объектов и т. Д.) И минимизировать потоки.

Если вам нужны двоичные данные, вы можете использовать вместо них каналы или реализовать свои собственные потоки для предотвращения потоковой передачи. Бизнес-логика или логика обработки не должны знать о сокетах, потоки достаточно низкоуровневые и, возможно, слишком много.

Но в любом случае: многопоточность - это неплохо, если вы не злоупотребляете ею.

person Hardcoded    schedule 08.06.2010

Я понимаю, что вам нужно - мне приходилось решать ту же проблему в ситуациях, когда сервер находился за маскирующим брандмауэром с динамическим IP. Я использовал небольшую бесплатную программу javaProxy, чтобы найти решение. Это заставляет сервер выглядеть как клиентский сокет - внутри он по-прежнему является сервером, но javaProxy предоставляет программу пересылки - в примере My App - которая создает клиентские соединения «с» сервера. Он также предоставляет прокси-сервер посередине (в примере средний сервер) для соединения двух клиентских концов вместе - клиентского сокета, перенаправленного с сервера, и клиентского сокета от реального клиента, пытающегося подключиться к серверу.

Средний сервер размещается за пределами брандмауэра на известном IP-адресе. (Несмотря на то, что мы можем притвориться, что делаем это без серверных сокетов, каждое соединение должно включать в себя клиент и сервер, поэтому мы должны убедиться, что средний сервер находится на IP-адресе, доступном для клиентов.) В моем случае я просто использовал простой хостинг-провайдер, который позволил мне запускать java из оболочки.

С помощью этой настройки я мог предоставить доступ к удаленному рабочему столу и другим службам, работающим за брандмауэром NAT с динамическим IP, с доступом с моей домашней машины, которая также находилась за NAT с динамическим IP. Единственный IP-адрес, который мне нужно было знать, - это IP-адрес среднего сервера.

Что касается потоковой передачи, библиотека javaproxy почти наверняка реализована с использованием потоков для перекачки данных между клиентскими сокетами, но они не потребляют никаких ресурсов ЦП (или мощности), пока они блокируют ожидание ввода-вывода. Когда java 7 выпускается с поддержкой асинхронного ввода-вывода, один поток на пару клиентских сокетов не нужен, но это больше касается производительности и избежания ограничений на максимальное количество потоков (пространство стека), а не энергопотребления.

Что касается реализации этого самостоятельно с двумя клиентскими сокетами в одном процессе, требуется использование потоков, если Java зависит от блокировки ввода-вывода. Модель извлекается из конца чтения и проталкивается в конец записи, поэтому для извлечения из конца чтения требуется поток. (Если бы у нас был толчок с конца чтения, то есть асинхронный ввод-вывод, тогда не понадобился бы выделенный поток для каждой пары сокетов.)

person mdma    schedule 14.06.2010
comment
Это первый ответ, который, как я прочитал, соответствует заданному мной вопросу, конечно, я не ищу приложение для выполнения этой работы, но как решить это из источника java, в любом случае это что-то! +1, буду работать и жду java 7! Благодарность - person Hernán Eche; 27.07.2010

Зачем нужен средний сервер? Если вы просто хотите открыть VNCServer. Почему бы не попробовать такую ​​архитектуру, как следующая

VNCServer(S) <-> (C)MyApp(S) <-> (C) User

(S) represents a server socket
(C) represents a client socket

В этом случае MyApp действует как клиент (для VNCServer) и как сервер (для пользователя). Таким образом, вам придется реализовать в MyApp как клиентские, так и серверные сокеты, а затем передавать данные.

Изменить: для связи с VNCServer MyApp должен знать IP-адрес VNCServer. Пользователь будет общаться только с MyApp, и ему нужно знать только IP-адрес MyApp. Пользователю не нужен IP-адрес VNCServer.

person Babar    schedule 08.06.2010
comment
Просто потому, что при таком подходе вам нужно будет знать IP-адрес VNCServer, и это именно одна из причин, по которой я это сделал, чтобы избежать этой потребности (и других, таких как динамический IP, частный IP, настройка маршрутизаторов, прокси и т. Д., Это действительно намного проще для доступа к сети, являющейся TCP-клиентом) - person Hernán Eche; 10.06.2010
comment
Для связи с VNCServer MyApp должен знать IP-адрес VNCServer. Даже в вашем случае [VNCServer ‹---MyApp---› | средний сервер | ‹--- Пользователь] MyApp потребуется IP-адрес VNCServer, верно? - person Babar; 10.06.2010
comment
Нет, в моем случае MyApp находится на той же машине - person Hernán Eche; 11.06.2010
comment
@ Эрнан: Но с вашим промежуточным сервером вы должны знать внешний IP-адрес этого промежуточного сервера, так в чем разница между знанием IP-адреса промежуточного сервера и IP-адреса VNCServers? - person Martijn Courteaux; 14.06.2010
comment
@Martijn Courteaux, у меня есть доступ к среднему серверу, и у него есть общедоступный статический IP-адрес, компьютер VNC может быть в любом месте, с любой неизвестной сетевой конфигурацией, динамическим IP-адресом, прокси-сервером и т. Д., Поэтому лучший способ выбраться оттуда - это являясь клиентским сокетом - person Hernán Eche; 15.06.2010

В C вы можете вызвать socketpair (2), чтобы получить пару подключенных сокетов, но я не уверен, есть ли в Java какой-либо встроенный способ сделать то же самое.

person Chris Dodd    schedule 14.06.2010
comment
Работает только для конечных точек, работающих на одном компьютере, и зависит от * nix. хотя. - person nos; 15.06.2010

Вообще говоря, TCP-сокет клиента имеет два конца (локальный и «удаленный»), а TCP-сокет сервера - один конец (потому что он ожидает подключения к нему клиента). Когда клиент подключается к серверу, сервер внутренне порождает клиентский сокет, чтобы сформировать пару подключенных клиентских сокетов, которые представляют канал связи; это пара, потому что каждый сокет просматривает канал с одного конца. Так работает TCP (на высоком уровне).

У вас не может быть двух клиентских сокетов, соединенных друг с другом в TCP, так как протокол низкоуровневого соединения не работает. (Вы можете создать пару подключенных сокетов таким образом в Unix, но это не отображается в Java и они не TCP-сокеты.) Что вы можете сделать, так это закрыть серверный сокет, как только вы принял соединение; для простых случаев этого может быть достаточно.

UDP-сокеты, конечно, разные, но они работают с дейтаграммами, а не с потоками. Это совсем другая модель общения.

person Donal Fellows    schedule 15.06.2010

Если вам нужно peer-to-peer соединение, вы можете рассмотреть возможность использования UDP.

UDP может получать от чего угодно, не устанавливая сначала соединение, но вам все равно понадобится сервер, чтобы сообщить клиентам, от кого они получают данные.

Надеюсь, это помогло.

person Kevin    schedule 30.08.2010

Классический подход Java к обмену данными через сокеты на основе подключения состоит в том, чтобы настроить ServerSocket на известный IP-адрес и порт и заблокировать его вызов accept, который (после успешной попытки подключения) возвращает новый Socket с портом, определяемым реализацией (отличным от порта ServerSocket). Обычно возвращенный сокет передается обработчику, реализующему Runnable. Обработчики временно связаны с определенным подключением. Обработчики могут использоваться повторно и обычно связаны с конкретным потоком на время существования соединения. Блокирующий характер классического ввода-вывода сокетов Java очень затрудняет соединение двух сокетов, обслуживаемых одним потоком.

Однако возможно, и это не является необычным, обрабатывать как входные, так и выходные потоки сокета в одном потоке, а поддержка одного соединения за раз позволяет отбросить требование Runnable, т. Е. Отсутствие дополнительного потока. требуется для обработчика, и вызов ServerSocket accept откладывается до закрытия текущего соединения.

Фактически, если вы используете NIO, вы можете легко обрабатывать множество подключений одновременно в одном потоке с помощью механизма выбора. Это одна из наиболее важных функций NIO, неблокирующий ввод-вывод для отделения потоков от соединений (позволяющий обрабатывать очень большое количество соединений небольшими пулами потоков).

Что касается топологии вашей системы, извините, я еще не понимаю, что вам нужно, но похоже, что это работа либо для службы NAT, либо для какого-то прокси-сервера, соединяющего общедоступный IP-адрес. на частный IP.

person Don Mackenzie    schedule 15.06.2010
comment
Он НЕ отличается от порта ServerSocket. - person user207421; 10.08.2015