连接两个客户端套接字

15
假设Java有两种套接字类型:
  • 服务器套接字 "ServerSocket"
  • 客户端套接字或简称为 "Socket"
设想两个进程的情况:
X = 客户端 Y = 服务器
服务器进程 Y : 拥有一个"ServerSocket",正在监听一个TCP端口。 客户端进程 X : 通过 "Socket" 向 Y发送连接请求。
当 accept() 方法被调用时,Y将返回一个新的客户端类型 "Socket",此时这两个套接字将互相连接,
因此,客户端进程中的 socket 与服务器进程中的 socket 连接了起来, 然后通过 socket X 进行读取/写入就像通过 socket Y 进行读取/写入一样。 现在,两个客户端套接字得到了连接!
但是... 如果我在同一个进程中创建两个客户端套接字,并且想让它们互相连接呢? 这是否可行?
我们看看如何让两个客户端套接字在不使用中间 ServerSocket 的情况下互相连接。
我通过创建两个线程来实现持续读取 A 并写入 B,以及读取 B 并写入 A... 但我认为可能还有更好的方法... (采用客户端-服务器方式即可避免那些消耗世界能源的线程)
任何帮助和建议都将不胜感激!谢谢!
编辑:
应用示例:"现有的服务器应用程序可以转换为客户端应用程序", 例如VNC服务器,一个客户端套接字连接到VNC服务器,另一个客户端套接字被创建(连接到中间服务器),然后应用程序将这两个客户端套接字互相连接,结果是 VNC 服务器成为了客户端应用程序!然后,就不需要公共 IP 地址了。
VNCServer---MyApp---> |middle server| <---User

1
谢谢您的评论,我已经稍微整理了一下!并且避免使用大写字母。由于这是一个易错的话题,我尝试慢慢解释,感谢您。 - Hernán Eche
2
请纠正我如果我理解错了,这是我的理解:你在一个服务器上运行MyApp,在另一个服务器上运行VNCServer,而在第三个服务器上运行middleServer。用户连接到middle-server(我猜它有一个公共IP地址)。你想从middle-server连接到VNCServer,但是你的VNCServer位于某个内部网络中,而middle-server位于该网络之外。因此,你想要建立桥接连接。为了做到这一点,你编写了myApp(位于内部网络中),打开到middle-server的连接,然后到VNCServer,你想要传递数据并在middle-server和VNCServer之间来回传递。这样说对吗? - Elister
3
@Hernán Eche,感谢您整理并提供更多信息!我撤回了我的负投票。 - Bart Kiers
@Elister,是的,这很接近了(VNCServer和MyApp甚至可以在同一台机器上),而且VNCServer可以在内部网络中,我会桥接连接,我有两个“MyApp”正在运行,一个在内部网络中与中间服务器相互连接(比如客户端套接字到客户端套接字插头),另一个在中间服务器上重新创建一个服务器(比如服务器套接字到服务器套接字插头),我甚至称它们为“适配器”,因为它让我想起了女-女、男-男音频或任何插头适配器 =) - Hernán Eche
如果你首先发布代码,别人才能帮助你优化它。 - Babar
是的!我想我明白了!我想我知道你想要什么。就像TeamViewer一样。他们有一个服务器,保存在线用户的信息。那个主服务器有一个静态的公共IP地址,所有客户端都可以连接到它。 这意味着“VNCServer”并不是一个真正的大型服务器机器。它也是网络中的客户端,但它运行着一个ServerSocket。因此,VNCServer可以告诉主服务器它在哪里(公共IP)。 - Martijn Courteaux
12个回答

18

首先,不要将被接受的客户端(服务端)套接字称为“Client Socket”,这非常令人困惑。

如果不使用中间的ServerSocket,如何让两个客户端套接字相互连接?

这是不可能的。您总是需要创建一个服务器端,能够接受客户端。现在的问题是:连接的哪一端应该是服务器端?
您需要考虑以下内容:

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

中间服务器

我不知道您希望使用第三个服务器来做什么。也许保留VNCServer的公共IP地址? Elister说,您想在客户端和VNCServer之间建立一个桥梁。但我看不出这样做的优势。

那为什么不直接与VNCServer建立连接呢?

但是,如果您真的想这样做,可以创建如下情况:

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

这是没有第三个服务器的情况下的样子(我建议您这样做):

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

@Hernán Eche:+1 因为现在我明白你的问题了;-) - Martijn Courteaux
1
它被选为最佳答案,因为ASCII艺术!!=S 奇怪...这是我最后一次使用悬赏,它很糟糕...而且@Martijn Corteaux,不,你的解决方案不可能,标记为(3)的连接无法完成,因为客户端和VNCServer可能在NAT/防火墙/等后面,你仍然认为它们可以作为服务器访问,但事实并非如此。 - Hernán Eche

5
为什么需要这样做?
如果您想要一个“点对点”类型的系统,那么您只需让每个客户端同时运行客户端和服务器套接字 - 服务器套接字用于接受其他客户端的连接,客户端套接字用于建立与其他人的连接。
预计时间:在原始问题中不太清楚您的问题,但由于您的编辑,似乎您正在寻找创建某种代理服务器
在您的示例中,您的应用程序将创建两个客户端套接字,一个连接到VNCServer,另一个连接到“中间服务器”。然后,“中间服务器”将具有两个服务器套接字(一个用于应用程序连接,另一个用于用户连接)。在内部,它需要知道如何匹配这些套接字并在两者之间传输数据。

这是将任何服务器应用程序转换为客户端的过程。它可以正常工作,但我怀疑使用两个线程的方法是否是一个好方法,或者是否可以通过其他语言、库或操作系统功能来解决这个问题。 - Hernán Eche
1
@Hernán - 我不会担心使用两个线程的方法。只要你通过将它们放入套接字的等待状态来有效地管理线程,你就应该没问题。 - Eric Petroelje

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

我一遍又一遍地重新运行,但每次都会出现这个问题,除非我等待一定的时间后再次运行,那么它就像第一次一样正常工作。


你的应用程序确实有效。但是,如果我在控制台应用程序中使用简单的connection1 = new Socket(with 4 params);运行它,它会立即失败并显示“连接被拒绝”。我不确定你的应用程序工作的关键因素是什么。 - Leon
请将其简化为两个客户端且不带 GUI,可以吗? - Leon

2
ServerSocket允许您在特定端口上监听连接。当服务器套接字接受连接时,它会生成另一个线程,并将连接移动到另一个端口,以便原始端口仍然可以监听其他连接。
客户端在已知端口上启动连接。然后,通常情况下,客户端将发送一些请求,而服务器将响应。这将重复直到通信完成。这是Web使用的简单客户端/服务器方法。
如果您不需要此机制,并且请求可能随时来自任一套接字,则实现读取器和写入器线程的方式似乎是合适的。
在内部,它们仍然使用等待机制,因此在等待数据到达时,您不应该看到太多CPU使用率。
我认为仍然需要一个端口成为服务器套接字,因为我认为不可能让客户端套接字接受连接。ClientSocket意味着TCP,它需要连接。如果您使用DatagramSocket,它意味着UDP,您可以进行客户端到客户端的通信,而无需连接。

不会 将连接移动到另一个端口。这是一种都市传说。可以查看任何服务器上的 netstat 输出。 - user207421

1

在网络编程中,一个 socket 包含两个端点(客户端和服务器应用程序)和两个 streams。客户端的输出流是服务器的输入流,反之亦然。

现在试想一下,如果一个线程向流中写入大量数据而没有人读取会发生什么...确实有缓冲区,但它们并不是无限的,大小也可能不同。最终,你的写入线程将达到缓冲区的极限,并且会阻塞直到有人释放缓冲区。

话虽如此,你现在应该意识到每个流至少需要两个不同的线程:一个写入线程和一个读取已写入字节的线程。

如果你的协议是请求-响应式的,你可以使用每个 socket 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);
}

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

如果您想要二进制数据,可以使用管道,或者实现自己的流以防止线程。业务或处理逻辑不应该知道套接字,流足够低级,但可能过于底层。

但无论如何:只要不过度使用,线程并不是坏事。


1
你是否正在尝试创建一个模拟的套接字?如果是这样,模拟管道的两端可能比必要的更加复杂。
另一方面,如果你只想在两个线程之间创建一个数据管道,你可以使用PipedInputStream和PipedOutputStream。
然而,如果没有更多关于你想要实现什么的信息,我无法告诉你这些选择是否适合或者是否有更好的选择。

1
我理解你的需求- 在服务器位于伪装防火墙后面且具有动态IP的情况下,我曾不得不解决相同的问题。我使用了一个小型免费程序javaProxy来提供解决方案。它使服务器看起来像是客户端套接字- 内部仍然是服务器,但javaProxy提供转发程序-在示例中为My App-创建“从”服务器的客户端连接。它还提供了代理(在示例中为Middle Server)以将两个客户端端点连接在一起-从服务器转发的客户端套接字和试图连接到服务器的实际客户端的客户端套接字。
中间服务器托管在防火墙外的已知IP上。(虽然我们可以假装没有服务器套接字就能做到这一点,但每个连接必须涉及客户端和服务器端点,因此我们确保中间服务器位于客户端可以访问的IP上。)在我的情况下,我只是使用了一个简单的托管提供商,让我从shell中运行java。
有了这个设置,我可以提供对运行在NAT防火墙后面且具有动态IP的远程桌面和其他服务的访问,从我的家庭机器访问,该机器也位于NAT后面且具有动态IP。我唯一需要知道的IP地址是中间服务器的IP。
关于线程,javaproxy库几乎肯定是使用线程在客户端套接字之间传输数据,但是这些线程在等待I/O时不会消耗任何CPU资源(或电源)。当Java 7发布支持异步I/O时,每个客户端套接字对应一个线程将不再必要,但这更多是为了提高性能和避免最大线程数(堆栈空间)的限制,而不是节省功耗。
如果您想在同一进程中使用两个客户端套接字来实现此功能,则需要使用线程,只要Java依赖于阻塞I/O。该模型是从读端拉取并推送到写端,因此需要一个线程从读端拉取。(如果我们从读端推送,即异步I/O,则不需要每个套接字对应一个专用线程。)

这是我读到的第一个符合我提出的问题的答案,当然我不是在寻找一个应用程序来完成这项工作,而是如何从Java源代码中解决它,无论如何还是有所收获! +1,我会继续努力,并期待Java 7的发布!谢谢 - Hernán Eche

0

为什么我们需要一个中间服务器?如果你只想暴露VNCServer,为什么不尝试以下架构呢?

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

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

在这种情况下,MyApp既充当VNCServer的客户端,又充当User的服务器。因此,您将需要在MyApp中实现客户端和服务器套接字,然后中继数据。
编辑:为了与VNCServer通信,MyApp需要知道VNCServer的IP地址。User只与MyApp通信,只需知道MyApp的IP地址。 User不需要VNCServer的IP地址。

仅仅因为在那种方法中,你需要知道VNCServer的IP地址,这也正是我制作这个工具的原因之一,避免这种需求(以及其他像动态IP、私有IP、配置路由器、代理等等...成为TCP客户端更容易访问网络)。 - Hernán Eche
为了与VNCServer通信,MyApp需要知道VNCServer的IP地址。即使在您的情况下 [VNCServer<---MyApp--->|middle server|<---User],MyApp仍然需要VNCServer IP地址,对吧? - Babar
不,就我的情况而言,MyApp在同一台机器上。 - Hernán Eche
@Hernán:但是,通过您的中间服务器,您必须知道该中间服务器的外部IP,那么知道中间服务器的IP和VNCServers的IP之间有什么区别? - Martijn Courteaux
@Martijn Courteaux,我可以访问中间服务器,它有公共静态IP。VNC计算机可以在任何地方,具有任何未知的网络配置、动态IP、代理等等。因此,最好的方法是成为客户端套接字以离开那里。 - Hernán Eche

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

1
仅适用于在同一台机器上运行的端点,并且是*nix特定的。 - nos

0

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

TCP中不能让两个客户端套接字相互连接,因为低级别的连接协议不支持这种方式。(在Unix中可以创建这样的已连接套接字对,但在Java中没有暴露出来,它们也不是TCP套接字。)你可以在接受连接后关闭服务器套接字;对于简单情况,这可能足够了。

UDP套接字当然是不同的,但它们使用数据报而不是流进行工作。这是一种非常不同的通信模型。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接