首页 文章

连接两个客户端套接字

提问于
浏览
12

假设Java有两种套接字:

  • 服务器套接字"ServerSocket"

  • 客户端套接字或只是"Socket"

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

X =客户
Y =服务器

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

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

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

但...
如果我在同一个进程中创建两个客户端套接字,我想让它们"interconnected"怎么办?

......甚至可能?

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

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

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


Edit:

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

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

12 回答

  • -1

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

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

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

    • 服务器应具有静态公共IP .

    • 连接路由器后的服务器必须执行"port forwarding" . (见UPnP

    • 客户端必须知道它必须连接到哪个主机(公共IP)

    中间服务器
    我不知道你想用第三台服务器做什么 . 也许持有VNCServer的公共IP? * Elister *写道,你想在客户端和VNCServer之间 Build 一个brigde . 我没有看到它的优点 .
    为什么不立即 Build 与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 . 这当然是来自VNCServer的IP .
    (5) 正在运行的VNCServer是服务器以接受客户端 .

    现在可以启动桌面共享 .

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

  • 0

    你为什么要这么做?

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

    ETA:原始问题中你所询问的内容并不完全清楚,但自从你的编辑以后,你似乎想要创建一种proxy server .

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

  • 1

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

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

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

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

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

  • 16

    这是我连接两个 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                   
    }
    

    这里 Object1Object2 只是两个 Serializable 对象 . 似乎所有插座都完美连接 . 如果我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
    

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

  • 2

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

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

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

  • 0

    socket (在网络术语中)由2个 endpoints (客户端和服务器应用程序)和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);
    }
    

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

    如果你想要数据二进制文件,你可以改用管道或者实现你自己的流来防止线程化 . 业务或处理逻辑不应该知道套接字,流不够低,也可能太多 .

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

  • 2

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

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

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

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

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

  • 1

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

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

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

    Edit: 要与VNCServer通信,MyApp需要知道VNCServer的IP . 用户只会与MyApp通信,只需知道MyApp _1813736需要VNCServer的IP地址 .

  • 0

    在C中,您可以调用 socketpair(2) 来获取一对连接的套接字,但我不确定java是否有任何内置方式来执行相同的操作 .

  • 1

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

    你可以这样工作 . (您可以在Unix中以这种方式创建一对连接的套接字,但它不是TCP套接字 . )您可以做的是在接受连接后关闭服务器套接字;对于简单的情况,这可能足够好 .

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

  • 0

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

    UDP 可以从没有 Build 连接的任何东西接收,你仍然需要一个服务器告诉客户他们从哪里接收数据 .

    希望这有帮助 .

  • 5

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

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

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

    至于你的系统的拓扑结构,我还没有明确你所追求的是什么,但它听起来像是一个 NAT 服务或某种将公共IP桥接到私有IP的代理 .

相关问题