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

时间:2018-07-09 08:22:28

标签: java sockets tcp ip-address

在评论正确使用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时没有这种问题。相同的套接字支持receive()send()方法。对于udp,共享地址很容易。

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

2 个答案:

答案 0 :(得分:0)

编辑:在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(...);

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

当心:未经测试...

答案 1 :(得分:0)

我已修正您的测试-希望这就是您想要的方式。在这里看看代码:

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