连接两个客户端套接字

时间:2010-04-05 12:08:18

标签: java sockets client serversocket

假设Java有两种套接字:

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

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

X =客户

Y =服务器

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

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

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

但是...
如果我在同一个进程中创建两个Client套接字, 我想让他们“互联”吗?

......甚至可能?

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

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

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


修改

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

的vncserver --- --- MyApp的GT&; |中间服务器| < ---用户

12 个答案:

答案 0 :(得分:17)

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

  

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

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

  • 服务器应具有静态公共IP。
  • 连接路由器后的服务器必须进行“端口转发”。 (见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是服务器以接受客户端。

现在可以开始桌面共享。

我认为这是您可以提供的最佳建议情况 当然用Java编写它是给你的。

答案 1 :(得分:5)

你为什么要这样做?

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

ETA:原始问题中您所询问的内容并不完全清楚,但自编辑以来,您似乎希望创建一种proxy server

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

答案 2 :(得分:2)

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

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

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

在内部,他们仍然使用等待机制,所以在等待数据到达时你不应该看到很多CPU使用率。

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

答案 3 :(得分:2)

这是我连接两个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个对象。 似乎所有插座都完美连接。如果我没有调用套接字及其输入的close()方法,输出流并重新运行,那么System.exit()仍可正常工作。但是如果我通过确保调用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

我一次又一次地重新跑,我一直都这样,除非,我等了一段时间再重新跑,它第一次运作得很好。

答案 4 :(得分:1)

您是否尝试创建模拟套接字?如果是这样的话,嘲笑管道的两侧可能比必要的要复杂一些。

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

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

答案 5 :(得分:1)

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);
}

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

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

但无论如何:只要你不过度使用它,线程就不错了。

答案 6 :(得分:1)

我明白你的意思 - 在服务器背后是一个带有动态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则不需要每个插槽对的专用线程。)

答案 7 :(得分:0)

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

答案 8 :(得分:0)

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

答案 9 :(得分:0)

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

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

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

答案 10 :(得分:0)

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

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

希望这会有所帮助。

答案 11 :(得分:-1)

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

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

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

就系统的拓扑结构而言,对不起,我还不清楚你的目标是什么,但这对于 NAT 服务或某种代理桥接来说听起来像是一项工作公共IP到私有IP。