连接两个客户端套接字

发布于 2024-08-27 12:27:14 字数 890 浏览 9 评论 0原文

假设 Java 有两种套接字:

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

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

X = Client
Y = Server

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

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

因此:客户端进程中的套接字与服务器进程中的套接字连接。
那么:通过套接字X读/写就像通过套接字Y读/写一样。
现在,两个客户端套接字已互连!

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

...甚至可能吗?

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

我通过创建两个线程来连续读取 A 和写入 B 来解决这个问题, 以及其他用于阅读 B 和写作 A...
但我认为可能是更好的方法... (那些消耗世界能源的线程对于客户端-服务器方法来说是不必要的)

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


编辑:

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

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

Let's say Java has two kind of sockets:

  • server sockets "ServerSocket"
  • client sockets or just "Socket"

Imagine the situation of two processes:

X = Client
Y = Server

The server process Y : has a "ServerSocket", that is listening to a TCP port
The client process X : sends a connection request through a "Socket" to Y.

Y: Then the accept() method returns a new client type "Socket",
when it occurs, the two Sockets get "interconnected",

So: the socket in client process, is connected with the socket in the server process.
Then: reading/writing through socket X is like reading/writing through socket Y.
Now, two Client Sockets get interconnected!!

But...
What if I create the two Client sockets in same process,
and I want to get them "interconnected" ?

... even possible?

Let's say how to have two client socket get interconnected without using an intermediate ServerSocket?

I've solved it by creating two Threads for continuously reading A and writing B,
and other for reading B and writng A...
But I think could be a better way...
(Those world-energy-consuming threads are not necessary with the client-server approach)

Any help or advice would be appreciated!! Thanks


Edit:

Example of application: "An existent server application could be converted to a client one",
For example VNC server, one client socket connects to the VNC server, and other client socket is created (to connect to a middle server), then the application interconnects the two client resulting the VNC server is a client application! And then, no public IP is needed.

VNCServer---MyApp---> |middle server| <---User

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

梦行七里 2024-09-03 12:27:14

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

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

那是不可能的。您始终必须创建一个可以接受客户端的服务器端。现在的问题是:连接的哪一端应该是服务器端?
做这个决定时你必须考虑的事情:

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

中间服务器


I don't see what you want to do with that third server. Maybe holding the VNCServer's public IP?
*Elister* wrote, you want to make a brigde between the client and the VNCServer. I don't see the advantage of it.

为什么不立即连接到 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写是给你的。

First of all, don't call an accepted client (server-side) its socket a Client Socket. That is very confusing.

Let's say how to have two client socket get interconnected without using an intermediate ServerSocket?

That is impossible. You always have to make a server-side, which can accept clients. Now the question is: which side of the connection should be the server-side?
Things you have to think about by this decision:

  • A server should have a static public IP.
  • A server, which is after a router connected, has to do "port forwarding". (See UPnP)
  • A client has to know which host it has to connect to (public IP)

Middle server


I don't see what you want to do with that third server. Maybe holding the VNCServer's public IP?
*Elister* wrote, you want to make a brigde between the client and the VNCServer. I don't see the advantage of it.

Why don't make immediately a connection to the VNCServer?

But if you really want it, you can make a situation like this:

      /   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 --°

And this is how it looks without the third server (What I recommend you):

      /   VNCServer (Server Running)  <---.
     |                                     |
LAN -|                             Connects to VNCServer
     |                                     |
      \   MyApp (Server Running --> Accepts Clients) <------.
                                                             |
                                                      (Through a router)
                                                             |
     Client --> Connects to MyApp --------------------------°


EDIT:

I think I got it now:

We have to visualize your situation like this:

                             Your Main Server (What you called middle server)
                    (1)         |       |      (2)
            /⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻/         \⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻\
           |                                                |
      Your VNCServer   <---------------------------->   The client
         (5)                        (3)

(1) The VNCServer connects to the main server. So, then the main server got the VNCServer its IP.
(2) The client connects to the main server.
(3) Now the main server knows where server and client are. Then he sends to the client where the server is. Then the client will connect to the IP he received from the main server. That is of course the IP from the VNCServer.
(5) The VNCServer is running is server to accept the client.

Now desktop sharing can start.

I think this is the most recommend situation you can have.
Of course writing it in Java is to you.

空城之時有危險 2024-09-03 12:27:14

为什么你需要这样做?

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

ETA:尚不完全清楚您在原始问题中提出的问题,但自从您编辑以来,您似乎正在寻求创建一种 代理服务器

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

Why would you need to do that?

If you want to have a "peer-to-peer" type system, then you just have each client run both a client and a server socket - the server socket for accepting connections from other clients and the client socket for establishing connections to others.

ETA: It wasn't entirely clear what you were asking in the original question, but since your edit, it seems like you are looking to create a sort of proxy server.

In your example, your app would create two client sockets, one connecting to the VNCServer and the other connecting to the "middle server". The "middle server" would then have two server sockets (one for your app to connect to and one for the user to connect to. Internally it would then need to know how to match those sockets up and shuttle data between the two.

无远思近则忧 2024-09-03 12:27:14

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

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

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

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

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

The ServerSocket allows you to listen for connections on a particular port. When a server socket accepts a connection, it spawns another thread, and moves the connection to another port, so the original port can still listen for additional connections.

The client initiates the connection on the known port. Then, typically, the client will send some request, and the server will respond. This will repeat until the communication is complete. This is the simple client/server approach that the web uses.

If you don't need this mechanism, and requests may come from either socket at any time, then implementing the reader and writer threads the way you have seems appropriate.

Internally, they still use wait mechanisms, so you shouldn't see much CPU usage while they wait for data to arrive.

I think you still need one end to be a server socket because I don't think it's possible to have a client socket accept a connection. ClientSocket implies TCP, which requires a connection. If you used DatagramSocket, which implies UDP, you could have client to client communication, without a connection.

在梵高的星空下 2024-09-03 12:27:14

这是我在没有任何 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<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 只是两个 >可序列化对象。
看起来所有的插座都连接得很好。如果我 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

我一次又一次地重新运行,我不断收到此信息,除非我等待一定时间并再次重新运行,否则它会像第一次一样正常工作。

This is the code where I connected two Socket without any 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                   
}

Here Object1 and Object2 are just two Serializable Objects.
All the sockets connect perfectly, it seems. If I System.exit() without calling the close() methods for the sockets and their input, output streams and re-run, it works fine still. But if I System.exit() by making sure that the close() methods are called, and I re-run again, I get this:

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

I re-run again and again, I keep getting this, unless, I wait a certain amount of time and re-run again, it works just fine as the first time.

独闯女儿国 2024-09-03 12:27:14

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

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

然而,如果没有更多关于您想要实现的目标的信息,我无法告诉您这些选择是否合适或者其他选择是否会更好。

Are you trying to created a mocked socket ? If so, mocking both sides of the pipe may be a bit more complicated than necessary.

On the other hand, if you just want to create a data pipe between two threads, you could use PipedInputStream and PipedOutputStream.

However, without more information about what your trying to accomplish, I cannot tell you if either of these choices is a good fit or if something else would be better.

长途伴 2024-09-03 12:27:14

一个套接字(用网络术语来说)由2个端点(客户端和服务器应用程序)和2个组成。客户端的输出流是服务器的输入流,反之亦然。

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

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

如果您的协议是请求-响应风格,您可以坚持每个套接字 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);
}

这样您可以轻松删除整个网络(包括对象的编码和解码等)并最小化线程。

如果您想要数据二进制文件,您可以使用管道或实现自己的流来防止线程化。
业务或处理逻辑不应该了解套接字,流已经足够低级,甚至可能太多。

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

A socket (in networking terms) consists of 2 endpoints (The client and the server app) and 2 streams. The output stream of the client is the input stream of the server and vice versa.

Now try to imagine what happens if a thread writes a lot of data to a stream while no one reads... There are buffers, true, but they aren't unlimited and they can vary in size. In the end your writing thread will hit the limit of the buffer and will block until someone frees the buffer.

Having said that, you should now be aware that this will need at least two different threads per Stream: one that writes and one that reads the written bytes.

If your protocol is request-response style, you could stick with 2 threads per socket, but no less.

You could try to replace the networking part of your application. Just create an abstract interface where you can hide the whole networking part, like:

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

This way you could easily remove the whole networking (including en- & decoding of your objects, etc) and minimize threading.

If you want the data binary, you could use pipes instead or implement your own streams to prevent threading.
The business or processing logic should not know about sockets, streams are low-level enough and maybe too much.

But either way: Threading isn't bad, as long as you don't overuse it.

时光清浅 2024-09-03 12:27:14

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

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

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

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

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

I understand what you are after - I have had to solve the same problem in situations where the server was behind a masquerading firewall with a dynamic IP. I used a small freely available program, javaProxy to provide a solution. It makes the server appear as a client socket - internally, it is still a server, but javaProxy provides forwarding program - My App in the example - that creates client connections "from" the server. It also provides the proxy in the middle (Middle Server, in the example) to join the two client ends together - the client socket forwarded from the server, and the client socket from the actual client trying to connect to the server.

The Middle Server is hosted outside the firewall on a known IP. (Even though we can pretend to do this without server sockets, each connection must involve a client and a server end and so we make sure the Middle Server is on an IP that the clients can reach.) In my case I just used a simple hosting provider that let me run a java from the shell.

With this setup, I could provide access to remote desktop and other services running behind a NAT firewall with dynamic IP, with access from my home machine which also was behind a NAT with dynamic IP. The only IP address I needed to know was the IP of the Middle Server.

As to threading, the javaproxy library is almost certainly implemented using threads to pump data between the client sockets, but these do not consume any CPU resources (or power) while they are blocking waiting for I/O. When java 7 is released with support for asynchronous I/O then one thread per client socket pair will not be necessary, but this is more about performance and avoiding limits on the maximum number of threads (stack space) rather than power consumption.

As to implementing this yourself with two client sockets in the same process requires the use of threads so long as java is dependent upon blocking I/O. The model is pull from the read end and push to the write end, so a thread is needed to pull from the read end. (If we had push from the read end, i.e asynchornous I/O then a dedicated thread per socket pair would not be needed.)

你是我的挚爱i 2024-09-03 12:27:14

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

Why do we need a middle server? If you just want to expose VNCServer. Why not try an architecture like following

VNCServer(S) <-> (C)MyApp(S) <-> (C) User

(S) represents a server socket
(C) represents a client socket

In this case MyApp acts both as a client (for VNCServer) and as a server(for User). So you will have to implement both client and server sockets in MyApp and then relay the data.

Edit: To communicate with VNCServer, MyApp needs to know the IP of the VNCServer. User will only be communicating with MyApp and only needs to know MyApp's IP Address. User doesn't need VNCServer's ip address.

温暖的光 2024-09-03 12:27:14

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

In C you can call socketpair(2) to get a pair of connected sockets, but I'm not sure if java has any built-in way of doing the same thing.

不羁少年 2024-09-03 12:27:14

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

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

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

Generally speaking, a client TCP socket has two ends (local and “remote”) and a server TCP socket has one end (because it is waiting for a client to connect to it). When a client connects to the server, the server internally spawns a client socket to form connected pair of client sockets that represent the communications channel; it's a pair because each socket views the channel from one end. This is How TCP Works (at a high level).

You can't have two client sockets connect to each other in TCP, as the low-level connection protocol doesn't work that way. (You can have a connected pair of sockets created that way in Unix, but it's not exposed in Java and they're not TCP sockets.) What you can do is close the server socket once you've accepted a connection; for simple cases, that might be good enough.

UDP sockets are different, of course, but they work with datagrams and not streams. That's a very different model of communication.

乖乖 2024-09-03 12:27:14

如果您想要点对点连接,您可能需要考虑使用UDP

UDP 可以从任何地方接收数据,而无需先建立连接,但您仍然需要服务器来告诉客户端他们正在从谁接收数据。

希望这有帮助。

If you want a peer-to-peer connection you might want to consider using UDP.

UDP can receive from anything without establishing a connection first, you will still need a server to tell the clients who they're receiving data from though.

Hope this helped.

雪化雨蝶 2024-09-03 12:27:14

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

然而,在同一个线程上处理套接字的输入和输出流是可能的,并且很常见,并且一次支持单个连接允许删除Runnable要求,即没有额外的线程处理程序需要此信息,ServerSocket Accept 调用将被推迟,直到当前连接关闭。

事实上,如果您使用NIO,您可以使用选择器机制轻松地在同一线程上同时处理多个连接。这是 NIO 最重要的特性之一,非阻塞 I/O 将线程与连接解耦(允许小型线程池处理大量连接)。

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

The classic Java approach to connection based socket communication is to set up a ServerSocket on a known IP and port and block on it's accept call, which (following a successful connection attempt) returns a new Socket with an implementation determined port (different from the ServerSocket's port). Typically the returned socket is passed to a handler implementing Runnable. Handlers are temporarily associated with a particular connection. Handlers can be reused and are associated with a particular thread usually for the lifetime of the connection. The blocking nature of classic Java socket IO makes connecting two sockets served by the same thread very difficult.

However it is possible, and not unusual, to process both input and output streams of a socket on the same thread and supporting a single connection at a time allows the Runnable requirement to be dropped, i.e. no additional thread is needed for the handler and the ServerSocket accept call is postponed until the current connection is closed.

In fact, if you use NIO you can easily handle many connections simultaniously on the same thread using the Selector mechanism. This is one of the most important features of NIO, non-blocking I/O to decouple threads from connections (allowing very high numbers of connections to be handled by small thread pools).

As far as the topology of your system, I'm sorry I'm not yet clear what you are after but it sounds like a job for either a NAT service or some sort of proxy bridging the public IP to the private IP.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文