简体   繁体   English

使用相同的TCP端口进行接受和连接

[英]Use same TCP port for accept and connect

Edited after comment on proper use of SO_REUSEADDR 在评论正确使用SO_REUSEADDR后编辑

I want to use same port for both inbound and outbound connections in java 我想在java中为入站和出站连接使用相同的端口

The purpose is to make a node in distributed environment. 目的是在分布式环境中创建节点。 But in Tcp I need to use two different ports for accepting and initiating connections. 但在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()

Now the problem is: 现在的问题是:

  • A starts listening on port a. A开始收听端口a。 B on b and C on c. B上的B和c上的C.
  • when A connects B (Using Socket.connect() ), A & B will keep the socket open for future message passing. 当A连接B(使用Socket.connect() )时,A&B将保持套接字打开以便将来传递消息。
  • B still doesn't know the port A is listening on because the the port from which b received the connection is different from a. B仍然不知道端口A正在侦听,因为b接收连接的端口不同于a。
  • when C connects B, B gives the socket address of A to C, But that port is a bound by a Socket() instance which doesn't have a accept() method 当C连接B时,B给出A到C的套接字地址,但该端口是由Socket()实例绑定的,该实例没有accept()方法

Of course, A can inform B about the port it is listening, but isn't there a direct way? 当然,A可以通知B关于它正在收听的端口,但是没有直接的方法吗?

How can I make this test to pass? 我怎样才能通过这个测试?

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.
}

There is no such problem in when I previously used udp. 我之前使用udp时没有这样的问题。 The same socket supports receive() and send() method. 同一个套接字支持receive()send()方法。 For udp, sharing address is easy. 对于udp,共享地址很容易。

  • when A connects B, B would save the socketAddress of A, 当A连接B时,B会保存A的socketAddress
  • When C connects B, B would send the address of A to C and C would connect to A 当C连接B时,B将A的地址发送给C,C将连接到A.

Edit: WON'T WORK IN JAVA 编辑:不会在JAVA工作

SO_REUSEADDR option shall be set on a socket before binding. 在绑定之前,应在套接字上设置SO_REUSEADDR选项。 I initially did my tests in Python (no access to a Java environment) and following script worked without errors on a Windows 10 system: 我最初在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))

with another process listening on port 8090 与另一个进程侦听端口8090

Unfortunately, in Java setReuseAddr javadoc says explicitely (emphasize mine): 不幸的是,在Java setReuseAddr javadoc明确地说(强调我的):

Enabling SO_REUSEADDR prior to binding the socket using bind(SocketAddress) allows the socket to be bound even though a previous connection is in a timeout state . 在使用bind(SocketAddress)绑定套接字之前启用SO_REUSEADDR允许套接字绑定,即使先前的连接处于超时状态

For reasons I cannot guess, Java is more restrictive here. 由于我无法猜测的原因,Java在这里更具限制性。 What looks even more weird, is that according to this other question it used to be allowed on older JRE versions (up to JRE 7U5) 看起来更奇怪的是,根据这个其他问题,它曾经被允许用于较旧的JRE版本(最高为JRE 7U5)


Original (and wrong) post follows: 原始(和错误)帖子如下:

The trick is to set the SO_REUSEADDR option before binding. 诀窍是在绑定之前设置SO_REUSEADDR选项。 That means that you will need to use a parameterless constructor for both ServerSocket et Socket . 这意味着您将需要为ServerSocketSocket使用无参数构造函数。 More or less: 或多或少:

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(...);

That way you can connect from your local listening port so that the peer will know how to reconnect after the connection will be closed. 这样,您就可以从本地侦听端口进行连接,以便对等端在连接关闭后知道如何重新连接。

Beware: untested... 小心:未经测试......

I've fixed your test - hope that it's the way you wanted it to be. 我已经修好了你的考试 - 希望这是你想要的方式。 Take a look at the code here: 看看这里的代码:

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.
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM