连接两个客户端套接字

假设Java有两种套接字:

  • 服务器套接字“ServerSocket”
  • 客户端套接字或只是“套接字”

想象一下两个过程的情况:

X =客户
Y =服务器

服务器进程Y:有一个“ServerSocket”,正在侦听TCP端口
客户端进程X:通过“Socket”向Y发送连接请求。

Y:然后accept()方法返回一个新的客户端类型“Socket”,
当它发生时,两个套接字变得“互连”,

所以:客户端进程中的套接字,与服务器进程中的套接字相连。
然后:通过套接字X读/写就像通过套接字Y读/写。
现在,两个客户端套接字相互连接!!

但…
如果我在同一个进程中创建两个Client套接字,我想让它们“互连”怎么办?

……甚至可能?

假设如何在不使用中间ServerSocket的情况下使两个客户端套接字互连?

我已经通过创建两个Thread来连续读取A和写入B,以及其他用于读取B和写入A来解决它…
但我认为可能是一种更好的方式……(使用客户端 – 服务器方法不需要那些世界耗能的线程)

任何帮助或建议将不胜感激! 谢谢


编辑:

应用程序示例:“现有服务器应用程序可以转换为客户端应用程序”,例如VNC服务器,一个客户端套接字连接到VNC服务器,并创建其他客户端套接字(连接到中间服务器),然后应用程序互连两个客户端导致VNC服务器是客户端应用程序! 然后,不需要公共IP。

VNCServer — MyApp —> |中间服务器| <—用户

首先,不要将接受客户端(服务器端)的套接字称为Client Socket 。 这非常令人困惑。

假设如何在不使用中间ServerSocket的情况下使两个客户端套接字互连?

那是不可能的。 你总是需要建立一个服务器端,它可以接受客户端。 现在的问题是:连接的哪一侧应该是服务器端?
你决定要考虑的事情:

  • 服务器应具有静态公共IP。
  • 连接路由器后的服务器必须进行“端口转发”。 (见UPnP )
  • 客户端必须知道它必须连接到哪个主机(公共IP)

中间服务器

我看不出你想用第三台服务器做什么。 也许持有VNCServer的公共IP? * Elister *写道,你想在客户端和VNCServer之间建立一个brigde。 我没有看到它的优势。

为什么不立即建立与VNCServer的连接?

但如果你真的想要它,你可以做出这样的情况:


       / VNCServer(服务器运行)<---。
      |  |
局域网 -  | 连接到VNCServer
      |  |
       \ MyApp(服务器运行 - >接受中间服务器)<------。
                                                                         |
                                                             (通过路由器)
                                                                         |
     中间服务器(服务器运行 - >接受客户端)--->连接到您的应用程序
                                              ^
                                              |
                                     (通过路由器)
                                              |
     客户端 - >连接到中间服务器 - °

这就是它没有第三台服务器的样子(我推荐你):


       / VNCServer(服务器运行)<---。
      |  |
局域网 -  | 连接到VNCServer
      |  |
       \ MyApp(服务器运行 - >接受客户端)<------。
                                                              |
                                                       (通过路由器)
                                                              |
     客户端 - >连接到MyApp --------------------------°


编辑:

我想我现在明白了:

我们必须像这样想象你的情况:

                             您的主服务器(您称为中间服务器)
                     (1)|  |  (2)
            同房及的设备,没输供
            |  |
      您的VNCServer <---------------------------->客户端
          (5)(3)

(1) VNCServer连接到主服务器。 那么,主服务器就获得了VNCServer的IP。
(2)客户端连接到主服务器。
(3)现在主服务器知道服务器和客户端在哪里。 然后他发送到服务器所在的客户端。 然后客户端将连接到他从主服务器收到的IP。 这当然是来自VNCServer的IP。
(5) VNCServer正在运行服务器以接受客户端。

现在可以启动桌面共享。

我认为这是你可以拥有的最值得推荐的情况。
当然用Java编写它是给你的。

你为什么要这样做?

如果你想拥有一个“点对点”类型的系统,那么你只需让每个客户端都运行一个客户端和一个服务器套接字 – 服务器套接字用于接受来自其他客户端的连接和客户端套接字以建立与其他客户端的连接。

ETA:你在原始问题中提出的问题并不完全清楚,但是自从你编辑以来,你似乎想要创建一种代理服务器 。

在您的示例中,您的应用程序将创建两个客户端套接字,一个连接到VNCServer,另一个连接到“中间服务器”。 然后,“中间服务器”将有两个服务器套接字(一个用于您的应用程序连接,另一个用于用户连接。在内部,它将需要知道如何匹配这些套接字并在两者之间传送数据。

ServerSocket允许您侦听特定端口上的连接。 当服务器套接字接受连接时,它会生成另一个线程,并将连接移动到另一个端口,因此原始端口仍可以侦听其他连接。

客户端在已知端口上启动连接。 然后,通常,客户端将发送一些请求,服务器将响应。 这将重复直到通信完成。 这是Web使用的简单客户端/服务器方法。

如果您不需要这种机制,并且请求可能随时来自任一套接字,那么以您的方式实现读写器线程似乎是合适的。

在内部,它们仍然使用等待机制,因此在等待数据到达时您不应该看到很多CPU使用率。

我认为你仍然需要一端作为服务器套接字,因为我不认为有可能让客户端套接字接受连接。 ClientSocket意味着TCP,它需要连接。 如果您使用DatagramSocket(意味着UDP),则可以在没有连接的情况下进行客户端到客户端的通信。

这是我在没有任何ServerSocket情况下连接两个Socket的代码:

 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 future1; private Future 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() { @Override public Boolean call() throws Exception { try { processConnection(input1); displayMessage("input1 finished"); } catch (IOException e) { displayMessage("blah"); } return true; } }); future2 = service.submit( new Callable() { @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") //  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(); }//  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 } 

这里Object1Object2只是两个Serializable对象。 似乎所有sockets都完美连接。 如果我System.exit()没有为套接字及其输入,输出流和重新运行调用close()方法,它仍然可以正常工作。 但是,如果我通过确保调用close()方法我的System.exit(),并且我再次重新运行,我得到这个:

 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 

我一次又一次地重新跑,我继续这样做,除非,我等了一段时间再重新跑,它第一次就好了。

你想创建一个模拟套接字吗? 如果是这样,模拟管道的两侧可能比必要的复杂一点。

另一方面,如果您只想在两个线程之间创建数据管道,则可以使用PipedInputStream和PipedOutputStream。

但是,如果没有关于你想要完成什么的更多信息,我无法告诉你这些选择中的任何一个是否合适或者其他什么会更好。

socket (在网络术语中)由2个端点(客户端和服务器应用程序)和2个streams 。 客户端的输出流是服务器的输入流,反之亦然。

现在试着想象如果一个线程将大量数据写入流而没有人读取会发生什么…有缓冲区,为真,但它们不是无限制的,它们的大小可能不同。 最后,您的写入线程将达到缓冲区的限制,并将阻塞,直到有人释放缓冲区。

话虽如此,您现在应该知道每个Stream至少需要两个不同的线程:一个写入,另一个读取写入的字节。

如果你的协议是请求 – 响应风格,你可以坚持每个插槽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); } 

这样,您可以轻松删除整个网络(包括对象的解码和解码等)并最大限度地减少线程。

如果您想要数据二进制文件,则可以使用管道代替或实现自己的流来防止线程化。 业务或处理逻辑不应该知道套接字,流是低级别的,也许是太多。

但无论哪种方式:线程都不错,只要你不过度使用它。

我明白你的意思 – 在服务器背后有一个动态IP的伪装防火墙的情况下,我不得不解决同样的问题。 我使用了一个免费的小程序javaProxy来提供解决方案。 它使服务器显示为客户端套接字 – 在内部,它仍然是服务器,但javaProxy提供转发程序 – 示例中的我的应用程序 – 从服务器创建客户端连接。 它还提供中间的代理(中间服务器,在示例中)将两个客户端连接在一起 – 从服务器转发的客户端套接字,以及尝试连接到服务器的实际客户端的客户端套接字。

中间服务器在防火墙外部托管在已知IP上。 (即使我们可以假装在没有服务器套接字的情况下执行此操作,每个连接必须涉及客户端和服务器端,因此我们确保中间服务器位于客户端可以访问的IP上。)在我的情况下,我只使用了一个简单的允许我从shell运行java的托管服务提供商。

通过这种设置,我可以提供对具有动态IP的NAT防火墙后面运行的远程桌面和其他服务的访问,可以从我的家用机器访问,该机器也在具有动态IP的NAT后面。 我需要知道的唯一IP地址是中间服务器的IP。

至于线程,javaproxy库几乎肯定是使用线程在客户端套接字之间泵送数据来实现的,但是当它们阻塞等待I / O时,它们不消耗任何CPU资源(或功率)。 当发布支持异步I / O的java 7时,每个客户端套接字对不需要一个线程,但这更多是关于性能并避免限制最大线程数(堆栈空间)而不是功耗。

至于在同一进程中使用两个客户端套接字自己实现它需要使用线程,只要java依赖于阻塞I / O. 模型从读取端拉出并推送到写入端,因此需要一个线程从读取端拉出。 (如果我们从读取端推送,即异步I / O,则不需要每个插槽对的专用线程。)

为什么我们需要中间服务器? 如果您只想公开VNCServer。 为什么不试试像以下这样的架构

 VNCServer(S) <-> (C)MyApp(S) <-> (C) User (S) represents a server socket (C) represents a client socket 

在这种情况下,MyApp既充当客户端(对于VNCServer)又充当服务器(对于用户)。 因此,您必须在MyApp中实现客户端和服务器套接字,然后中继数据。

编辑:要与VNCServer通信,MyApp需要知道VNCServer的IP。 用户只能与MyApp通信,只需知道MyApp的IP地址。 用户不需要VNCServer的IP地址。

在C中你可以调用socketpair(2)来获得一对连接的套接字,但我不确定java是否有任何内置的方法来做同样的事情。

一般来说,客户端TCP套接字有两端(本地和“远程”),服务器TCP套接字有一端(因为它正在等待客户端连接到它)。 当客户端连接到服务器时,服务器在内部生成一个客户端套接字,以形成代表通信通道的连接的一对客户端套接字; 它是一对,因为每个套接字从一端查看通道。 这是TCP如何工作(在高层次)。

您不能在TCP中将两个客户端套接字相互连接,因为低级别连接协议不能以这种方式工作。 (你可以在Unix中以一种方式创建一对连接的套接字,但它不是用Java公开的,而且它们不是TCP套接字。)你可以做的是在你接受连接后关闭服务器套接字; 对于简单的情况,这可能是足够好的。

当然,UDP套接字是不同的,但它们使用数据报而不是流。 这是一种非常不同的沟通模式。

如果您想要peer-to-peer连接,可能需要考虑使用UDP

UDP可以在没有建立连接的情况下从任何东西接收,但您仍然需要服务器来告诉客户他们从哪个接收数据。

希望这有帮助。

基于连接的套接字通信的经典Java方法是在已知的IP和端口上设置ServerSocket并阻塞它的accept调用,该调用(在成功连接尝试之后)返回一个具有实现确定端口的新Socket (不同于ServerSocket)的港口)。 通常,返回的套接字将传递给实现Runnable的处理程序。 处理程序暂时与特定连接相关联。 处理程序可以重复使用,并且通常在连接的生命周期内与特定线程相关联。 经典Java套接字IO的阻塞性质使得连接由同一线程服务的两个套接字变得非常困难。

但是,在同一线程上处理套接字的输入和输出流并且一次支持单个连接允许删除Runnable需求,即处理程序和处理程序不需要额外的线程,这是可能的,并且并不罕见。 ServerSocket接受呼叫被推迟,直到当前连接关闭。

事实上,如果你使用NIO,你可以使用Selector机制轻松地在同一个线程上同时处理许多连接。 这是NIO最重要的function之一,即从连接中分离线程的非阻塞I / O(允许小线程池处理非常多的连接)。

至于你的系统的拓扑结构,我很遗憾我还不清楚你在追求什么,但这听起来像是NAT服务或某种将公共IP桥接到私有IP的代理。