代码之家  ›  专栏  ›  技术社区  ›  Hernán Eche Shankar ARUL

连接两个客户端套接字

  •  12
  • Hernán Eche Shankar ARUL  · 技术社区  · 15 年前

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

    X=客户端
    Y=服务器

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

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


    那么:通过套接字X读/写就像通过套接字Y读/写一样。
    现在,两个 顾客 插座互联!!

    但是。。。
    如果我在同一个进程中创建两个客户端套接字, 我想让他们“相互联系”?

    ... 甚至有可能?

    比如说,如何让两个客户机套接字在不使用中间ServerSocket的情况下相互连接?

    我已经解决了这个问题,创建了两个线程来连续读A和写B, 另一个用来读B和写A。。。
    但我认为这是一个更好的方法。。。 (客户机-服务器方法不需要那些消耗大量能源的线程)


    编辑:

    例如,VNC服务器,一个客户机套接字连接到VNC服务器,并创建另一个客户机套接字(连接到中间服务器),然后应用程序将两个客户机互连,从而使VNC服务器成为客户机应用程序!然后,不需要公共IP。

    12 回复  |  直到 15 年前
        1
  •  17
  •   Martijn Courteaux    15 年前

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

    那是不可能的。你总是要做一个服务器端,它可以接受客户端。现在的问题是:连接的哪一边应该是服务器端?
    通过这个决定你必须考虑的事情:

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

    我不明白你想用第三台服务器做什么。可能持有VNCServer的公共IP? *Elister写道,您希望在客户机和VNCServer之间建立一个brigde。我看不出这有什么好处。

    为什么不立即连接到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。这当然是来自VNCServer的IP。
    (5) VNCServer正在运行is server以接受客户机。

    现在桌面共享可以开始了。

    我认为这是你能遇到的最好的情况。
    当然,用Java编写它对您来说很重要。

        2
  •  5
  •   Eric Petroelje    15 年前

    你为什么要这么做?

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

    proxy server .

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

        3
  •  2
  •   Marcus Adams    15 年前

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

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

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

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

    我认为您仍然需要一端作为服务器套接字,因为我认为不可能让客户端套接字接受连接。ClientSocket意味着TCP,它需要一个连接。如果您使用DatagramSocket,这意味着UDP,那么您可以进行客户端到客户端的通信,而不需要连接。

        4
  •  2
  •   SagittariusAStar_MW    12 年前

    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 物体。 似乎所有的插座都连接得很好。如果我系统出口()而不调用 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
  •  1
  •   Jim Rush    15 年前

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

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

    然而,如果没有更多关于你想要达到的目标的信息,我无法告诉你这些选择中的任何一个是合适的,还是其他的更好。

        6
  •  1
  •   Hardcoded    15 年前

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

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

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

    如果您的协议是请求-响应样式,那么每个套接字可以使用2个线程,但不能少于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);
    }
    

    通过这种方式,您可以轻松地删除整个网络(包括对象的解码等)并最小化线程。

    如果需要二进制数据,可以使用管道,或者实现自己的流来防止线程化。

    但不管怎样:线程都不错,只要你不过度使用它。

        7
  •  1
  •   mdma    15 年前

    我知道你在追求什么-我已经解决了同样的问题,在这种情况下,服务器后面的伪装防火墙与动态IP。我用了一个免费的小程序, javaProxy 提供解决方案。它使服务器看起来像一个客户机套接字—在内部,它仍然是一个服务器,但是javaProxy提供了一个转发程序—在本例中是我的应用程序—从服务器创建客户机连接。它还提供了中间代理(在本例中为中间服务器)将两个客户端连接在一起—从服务器转发的客户端套接字和从尝试连接到服务器的实际客户端发送的客户端套接字。

    中间服务器位于防火墙之外的已知IP上。(尽管我们可以假装不使用服务器套接字,但每个连接都必须包含一个客户机和一个服务器端,因此我们确保中间服务器位于客户机可以访问的IP上。)在我的例子中,我使用了一个简单的托管提供程序,它允许我从shell运行java。

    通过这种设置,我可以访问远程桌面和其他运行在具有动态IP的NAT防火墙后面的服务,也可以访问位于具有动态IP的NAT后面的家庭计算机。我只需要知道中间服务器的IP地址。

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

    至于自己在同一进程中用两个客户端套接字来实现这一点,只要java依赖于阻塞I/O,就需要使用线程。模型是从读端拉到写端,因此需要一个线程从读端拉。(如果我们从读取端进行推送,即异步i/O,那么每个套接字对就不需要一个专用线程。)

        8
  •  0
  •   Babar    15 年前

    为什么我们需要一个中间服务器?如果您只想公开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地址。

        9
  •  0
  •   Chris Dodd    15 年前

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

        10
  •  0
  •   Donal Fellows    15 年前

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

    不能让两个客户机套接字在TCP中相互连接,因为低级连接协议不是这样工作的。(在Unix中可以用这种方式创建一对连接的套接字,但它在Java中没有公开,它们不是TCP套接字) 可以 你要做的是一旦你接受了一个连接就关闭服务器套接字;对于简单的情况,这可能就足够了。

    当然,UDP套接字是不同的,但是它们处理的是数据报而不是流。这是一种完全不同的沟通模式。

        11
  •  0
  •   Kevin    14 年前

    如果你想要一个 peer-to-peer UDP .

    UDP协议 如果不首先建立连接就可以接收任何数据,那么您仍然需要一个服务器来告诉客户他们从谁那里接收数据。

    希望这有帮助。

        12
  •  -1
  •   Don Mackenzie    15 年前

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

    可运行的 要求被删除,即处理程序和 服务器套接字接受 呼叫将推迟到当前连接关闭。

    NIO公司 您可以使用选择器机制轻松地同时处理同一线程上的多个连接。这是最重要的特征之一 ,非阻塞I/O将线程与连接解耦(允许由小线程池处理非常多的连接)。

    纳特 服务或某种代理将公共IP连接到私有IP。