15

假设 Java 有两种套接字:

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

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

X = 客户端
Y = 服务器

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

Y:那么该accept()方法返回一个新的客户端类型“Socket”,
当它发生时,两个 Socket 得到“互连”,

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

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

……甚至可能吗?

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

我已经通过创建两个线程来连续读取 A 和写入 B 以及另一个用于读取 B 和写入 A 来解决它......
但我认为可能是一个更好的方法......(那些世界能源消耗线程是不必要的使用客户端-服务器方法)

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


编辑:

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

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

4

12 回答 12

17

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

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

那是不可能的。您总是必须制作一个可以接受客户端的服务器端。现在的问题是:连接的哪一侧应该是服务器端?
这个决定你必须考虑的事情:

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

中间服务器

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

为什么不立即连接到 VNCServer?

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

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

这就是没有第三台服务器的情况(我推荐你):

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


编辑:

我想我现在明白了:

我们必须像这样可视化您的情况:

                             你的主服务器(你所谓的中间服务器)
                    (1) | | (2)
            /⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻/ \⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻\
           | |
      你的 VNCServer <----------------------------> 客户端
         (5) (3)

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

现在可以开始桌面共享。

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

于 2010-06-13T12:26:32.870 回答
5

为什么你需要这样做?

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

ETA:您在最初的问题中所问的内容并不完全清楚,但是自从您进行编辑以来,您似乎正在寻找一种代理服务器

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

于 2010-04-05T12:17:00.917 回答
2

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

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

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

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

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

于 2010-04-05T12:31:59.677 回答
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对象。似乎所有的插座都完美连接。如果我 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

我一次又一次地重新运行,我一直得到这个,除非我等待一定的时间并再次重新运行,它和第一次一样工作得很好。

于 2012-08-04T10:25:11.570 回答
1

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

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

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

于 2010-04-05T12:33:42.033 回答
1

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

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

如果你想要数据二进制,你可以使用管道代替或实现你自己的流来防止线程。业务或处理逻辑不应该知道套接字,流足够低级,可能太多了。

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

于 2010-06-08T15:50:16.333 回答
1

我了解您所追求的 - 在服务器位于具有动态 IP 的伪装防火墙后面的情况下,我必须解决同样的问题。我使用了一个免费的小型程序javaProxy来提供解决方案。它使服务器看起来像一个客户端套接字——在内部,它仍然是一个服务器,但 javaProxy 提供了转发程序——示例中的我的应用程序——它从服务器“创建”客户端连接。它还提供了中间的代理(在示例中为中间服务器)将两个客户端连接在一起 - 从服务器转发的客户端套接字,以及来自实际客户端的客户端套接字尝试连接到服务器。

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

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

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

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

于 2010-06-14T22:21:24.480 回答
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 地址。

于 2010-06-08T16:23:24.783 回答
0

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

于 2010-06-14T22:38:34.743 回答
0

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

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

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

于 2010-06-15T12:29:11.457 回答
0

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

UDP可以在不先建立连接的情况下接收任何内容,但您仍然需要一个服务器来告诉客户端他们从谁接收数据。

希望这有帮助。

于 2010-08-30T08:19:29.747 回答
-1

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

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

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

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

于 2010-06-15T00:18:14.813 回答