Подключите два клиентских сокета

Предположим, что Java имеет два типа сокетов:

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

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

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

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

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

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

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

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

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

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

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


Edit:

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

VNCServer --- MyApp --- > | средний сервер | < --- пользователя

Ответ 1

Прежде всего, не вызывайте принятый клиент (серверный) его сокет a 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 --------------------------°


EDIT:

Думаю, я получил его сейчас:

Мы должны визуализировать вашу ситуацию следующим образом:

                             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 - это вам.

Ответ 2

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

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

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

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

Ответ 3

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

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

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

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

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

Ответ 4

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

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

Ответ 5

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

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

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

Ответ 6

A socket (в сетевых терминах) состоит из двух конечных точек (клиент и серверное приложение) и 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);
}

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

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

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

Ответ 7

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

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

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

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

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

Ответ 8

Зачем нам нужен средний сервер? Если вы просто хотите открыть 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.

Ответ 9

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

Ответ 10

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

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

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

Ответ 11

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

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

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

Ответ 13

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

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

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

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