Подключите два клиентских сокета
Предположим, что 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
может получить от чего-либо, не установив соединение первым, вам все равно потребуется сервер, чтобы сообщить клиентам, которым они получают данные.
Надеюсь, что это помогло.
Ответ 12
Это проект, который использует метод SocketBindThread.bind для внутреннего связывания двух сокетов.
Ответ 13
Классический подход Java к соединению на основе сокетов основан на настройке ServerSocket на известном IP-адресе и порту и блокировании на нем принимающего вызова, который (после успешной попытки подключения) возвращает новый Socket с определенным портом реализации (отличным от порта ServerSocket). Обычно возвращаемый сокет передается обработчику, реализующему Runnable. Обработчики временно связаны с определенным соединением. Обработчики могут быть повторно использованы и связаны с конкретным потоком, как правило, на весь срок службы соединения. Блокирующая природа классического Java-сокета IO очень затрудняет подключение двух сокетов, обслуживаемых одним и тем же потоком.
Однако, возможно, и не необычно, обрабатывать как входные, так и выходные потоки сокета в одном потоке и поддерживать одно соединение за раз, позволяет отказаться от требования Runnable, т.е. для обработчика дополнительный поток не требуется, а вызов ServerSocket accept переносится до тех пор, пока текущее соединение не будет закрыто.
Фактически, если вы используете NIO, вы можете легко обрабатывать множество подключений в одном потоке с помощью механизма Selector. Это одна из наиболее важных функций NIO, неблокирующих ввода/вывода для отграничения потоков от подключений (что позволяет обрабатывать очень большое количество подключений небольшими пулами потоков).
Что касается топологии вашей системы, извините, я еще не понял, что вам нужно, но это похоже на работу для службы NAT или какого-то прокси-моста публичный IP для частного IP.