Подключите два клиентских сокета - PullRequest
13 голосов
/ 05 апреля 2010

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

  • серверные сокеты "ServerSocket"
  • клиентские розетки или просто "Сокет"

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

X = Клиент
Y = Сервер

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

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

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

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

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

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

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

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


Edit:

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

VNCServer --- MyApp ---> | middle server | <--- Пользователь </p>

Ответы [ 12 ]

17 голосов
/ 13 июня 2010

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

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

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

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

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

Я не понимаю, что вы хотите сделать с этим третьим сервером.Может быть, проведение публичного IP-адреса VNCServer?* Elister * написал, что вы хотите создать мост между клиентом и VNCServer.Я не вижу в этом преимущества.

Почему бы сразу не установить соединение с 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 подключается к главному серверу.Итак, главный сервер получил VNCServer свой IP.
(2) Клиент подключается к главному серверу.
(3) Теперь главный сервер знает, где находятся сервер и клиент.Затем он отправляет клиенту, где находится сервер.Затем клиент подключится к IP-адресу, который он получил от основного сервера.Это, конечно, IP-адрес от VNCServer.
(5) VNCServer - это сервер для приема клиента.

Теперь можно начать общий доступ к рабочему столу.

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

5 голосов
/ 05 апреля 2010

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

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

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

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

2 голосов
/ 04 августа 2012

Это код, к которому я подключил два 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

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

2 голосов
/ 05 апреля 2010

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

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

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

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

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

1 голос
/ 15 июня 2010

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

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

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

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

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

1 голос
/ 08 июня 2010

A 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);
}

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

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

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

1 голос
/ 05 апреля 2010

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

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

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

0 голосов
/ 30 августа 2010

Если вы хотите peer-to-peer соединение, которое вы можете рассмотреть с помощью UDP.

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

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

0 голосов
/ 15 июня 2010

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

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

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

0 голосов
/ 15 июня 2010

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

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

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

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

...