【问题标题】:Use same TCP port for accept and connect使用相同的 TCP 端口接受和连接
【发布时间】:2018-12-16 20:48:49
【问题描述】:

在评论正确使用 SO_REUSEADDR 后编辑

我想在 java 中为入站和出站连接使用相同的端口

目的是在分布式环境中做一个节点。但在 Tcp 中,我需要使用两个不同的端口来接受和启动连接。

// accept incoming connection on one port
ServerSocket.accept()
// connect to remote, the port used will be different from the one used for accepting
Socket.connect()

现在的问题是:

  • A 开始监听端口 a。 B 上的 B 和 c 上的 C。
  • 当 A 连接 B(使用 Socket.connect())时,A 和 B 将保持套接字打开以供将来的消息传递。
  • B 仍然不知道端口 A 正在侦听,因为 b 接收连接的端口与 a 不同。
  • 当 C 连接 B 时,B 将 A 的套接字地址提供给 C,但该端口被 Socket() 实例绑定,该实例没有 accept() 方法

当然,A 可以通知 B 它正在侦听的端口,但是没有直接的方法吗?

我怎样才能使这个测试通过?

import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.DatagramSocket;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class DualSocketTest {
    ExecutorService service= Executors.newFixedThreadPool(10);
    int echoServerport=8080;
    int localServerport=8090;

    @Test
    public void testConnectivity() throws IOException {
        // create a echo server on  port 8080
        startEcho();

        // create a local Server instance
        ServerSocket localServer=new ServerSocket();

        // set the reuseAddress to true
        localServer.setReuseAddress(true);

        // bind the serverSocket
        localServer.bind(new InetSocketAddress(localServerport));

        // create a socket to connect the echo server using the same port used by localServer
        Socket socket = new Socket();
        socket.setReuseAddress(true);
        // but this will throw SocketBindException
        socket.bind(new InetSocketAddress(localServerport));
        socket.connect(new InetSocketAddress(echoServerport));

        // write hello
        socket.getOutputStream().write("Hello !".getBytes());
        byte[] result=new byte[100];

        // receive hello
        String ans=new String(result,0,socket.getInputStream().read(result));
        System.out.println("Server replied with : "+ans);

        // what was written and what was received must be same.
        assert(ans.equals("Hello !"));

    }
    // start a echo server listening on the specified port
    private void startEcho() throws IOException {
        ServerSocket echoServer=new ServerSocket(echoServerport);
        service.submit(()->{
            try {
                while(!echoServer.isClosed()) {
                    Socket socket = echoServer.accept();
                    System.out.println("connected with :" + socket.getInetAddress().toString() + ":" + socket.getPort());

                    InputStream inputStream = socket.getInputStream();
                    OutputStream outputStream = socket.getOutputStream();

                    service.submit(() -> {
                        while (socket.isConnected()) {
                            try {
                                outputStream.write(inputStream.read());
                            } catch (IOException e) {
                                break;
                            }
                        }
                        System.out.println("The Client has closed connection.");
                    });
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        Thread.yield();

    }
// Write something to the socket.
}

我以前使用udp的时候没有这个问题。同一个socket支持receive()send()方法。对于udp,分享地址很简单。

  • 当A连接B时,B会保存A的socketAddress
  • 当 C 连接 B 时,B 将 A 的地址发送给 C,C 将连接到 A

【问题讨论】:

  • “但是 A 不能接受来自与 B 通信的同一个套接字的新连接”。你在说什么。如果A 正在监听端口a,则BC 都可以通过端口A 连接到A
  • @Kayaman 我已经编辑了这个问题。问题是 b 看到的端口与 A 正在侦听的端口不同。
  • 您似乎使这变得不必要地困难。要么为监听套接字使用标准端口,因此您不需要传递该信息,或者将 IP/端口组合传递给新节点。
  • '我想在 java 中为入站和出站连接使用相同的端口。'为什么?
  • NB 调用setReuseAddress() 你绑定了套接字是没有意义的。

标签: java sockets tcp ip-address


【解决方案1】:

编辑:在 JAVA 中不起作用

SO_REUSEADDR 选项应在绑定之前在套接字上设置。我最初在 Python 中进行了测试(无法访问 Java 环境),并且以下脚本在 Windows 10 系统上正常运行:

import socket

serv = socket.socket()          # set a listening socket
serv.bind(('0.0.0.0',8080))
serv.listen(5)

s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('0.0.0.0',8080))
s.connect(('127.0.0.1', 8090))

另一个进程正在监听端口 8090

不幸的是,在 Java 中 setReuseAddr javadoc 明确地说(强调我的):

在使用 bind(SocketAddress) 绑定套接字之前启用 SO_REUSEADDR 允许绑定套接字,即使 先前的连接处于超时状态

由于我无法猜测的原因,Java 在这里的限制更大。看起来更奇怪的是,根据其他 question 的说法,它曾经允许在较旧的 JRE 版本上使用(直到 JRE 7U5)


原始(和错误)帖子如下:

诀窍是在绑定之前设置 SO_REUSEADDR 选项。这意味着您需要为ServerSocketSocket 使用无参数构造函数。或多或少:

ServerSocket localServer = new ServerSocket();
localServer.setReuseAddress(true);
localServer.bind(InetSocketAddress(localServerport));
...      // Ok listening...

Socket socket = new Socket();
socket.setReuseAddress(true);
socket.bind(InetSocketAddress(localServerport));
socket.connect(...);

这样您就可以从本地侦听端口进行连接,以便对等方在连接关闭后知道如何重新连接。

注意:未经测试...

【讨论】:

  • 你还没有实际尝试过,发布它的意义何在?
  • 我在socket.bind() 调用中测试并发现异常BindException : Address already in use
  • @EJP:我可以测试它在 Python 中是否有效,所以我知道必须在绑定之前调用 setReuseAddress。但我目前没有可用的 Java 环境。只是希望它可以帮助...
  • @Serge Ballesta 你的意思是我想要实现的目标在 Python 中是可能的吗?如果你能提供python代码,可能会有所帮助。
  • 我认为这与 java vs python 没有任何关系。以这种方式使用SO_REUSEADDR 将两个套接字绑定到同一个(活动)端口是Windows 特定的。它不适用于任何其他系统。 (为什么它在 Windows 上以这种方式实现对我来说是个谜。这是一个带有负面安全影响的愚蠢想法,并导致需要第二个标志 - SO_EXCLUSIVEADDRUSE - 来解决它。我怀疑原来的Winsock 实现者没看懂SO_REUSEADDR 的意思,就是允许绑定到TIME_WAIT 状态的端口。)
【解决方案2】:

我已经修正了你的测试 - 希望它是你想要的方式。看看这里的代码:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.junit.Test;

public class DualSocketTest {

    ExecutorService service = Executors.newFixedThreadPool(10);
    int echoServerport = 8080;
    int localServerport = 8080;

    @Test
    public void testConnectivity() throws IOException {
        // create a echo server on  port 8080
        startEcho();

        // create a socket to connect the echo server using the same port used by localServer
        Socket socket = new Socket();
        // but this will throw SocketBindException
        socket.connect(new InetSocketAddress(echoServerport));

        // write hello
        socket.getOutputStream().write("Hello !".getBytes());
        socket.getOutputStream().flush();
        byte[] result = new byte[100];

        // receive hello
        String ans = new String(result, 0, socket.getInputStream().read(result));
        System.out.println("Server replied with : " + ans);

        // what was written and what was received must be same.
        assert (ans.equals("Hello !"));

    }

    // start a echo server listening on the specified port
    private void startEcho() throws IOException {
        ServerSocket echoServer = new ServerSocket(echoServerport);
        service.submit(() -> {
            try {
                while (!echoServer.isClosed()) {
                    Socket socket = echoServer.accept();
                    System.out.println("connected with :" + socket.getInetAddress().toString() + ":" + socket.getPort());

                    InputStream inputStream = socket.getInputStream();
                    OutputStream outputStream = socket.getOutputStream();

                    service.submit(() -> {
                        while (socket.isConnected()) {
                            try {
                                byte[] buffer = new byte[1024];
                                int read = -1;
                                while ((read = inputStream.read(buffer)) != -1) {
                                    outputStream.write(buffer, 0, read);
                                }
                            } catch (IOException e) {
                                break;
                            }
                        }
                        System.out.println("The Client has closed connection.");
                    });
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        Thread.yield();

    }
// Write something to the socket.
}

【讨论】:

  • 您的测试工作正常,但它错过了我想要实现的目标。我要做的是创建与ServerSocket共享相同端口的连接套接字
  • 你能告诉我原因吗?用例是什么?您可以使用我认为的此代码-sn-p 实现各种“节点”架构
猜你喜欢
  • 2014-06-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-20
  • 1970-01-01
相关资源
最近更新 更多