是否可以在同一端口上创建多个(SSL)ServerSocket?

Is it possible to create multiple (SSL) ServerSocket on the same port?

public class ServerThread implements Runnable 
{

    private static final int port = 10000;

    @Override
    public void run() {
        ServerSocket serverSocket = new ServerSocket(port);

        while (true) {
            Socket clientSocket = serverSocket.accept();
            ClientThread clientThread = new ClientThread(clientSocket);
            // handle the client request in a separate thread
        }
    }
}

如果我有 10 个不同的线程 运行 ServerThread.run(),这会起作用吗?或者我应该为所有线程使用相同的 ServerSocket 对象吗?

docs 说:

The constructor for ServerSocket throws an exception if it can't listen on the specified port (for example, the port is already being used)

你可能想知道为什么我首先要这样做而不是简单地有一个线程 运行 ServerSocket.accept()。好吧,我的假设是(如果我错了请纠正我)accept() 方法可能需要一些时间才能完成建立连接,特别是如果 ServerSocket 是 SSL(因为握手)。所以如果两个客户端同时想要连接,一个必须等​​待另一个。这对于高流量服务器来说非常糟糕。

更新: 似乎一旦建立属于队列的连接,accept() 方法就会return。这意味着如果有一排客户端等待连接,服务器线程可以以最快的方式处理请求并且只需要一个线程。 (除了为每个请求创建一个新线程并启动线程所花费的时间之外,但是使用线程池时该时间可以忽略不计)

ServerSocket还有一个参数叫"backlog",你可以在这里设置队列中的最大连接数。据书"Fundamental Networking in Java"3.3.3

TCP itself can get ahead of a TCP server application in accepting connections. It maintains a ‘backlog queue’ of connections to a listening socket which TCP iself has completed but which have not yet been accepted by the application. This queue exists between the underlying TCP implementation and the server process which created the listening socket. The purpose of pre-completing connections is to speed up the connection phase, but the queue is limited in length so as not to pre-form too many connections to servers which are not accepting them at the same rate for any reason. When an incoming connection request is received and the backlog queue is not full, TCP completes the connection protocol and adds the connection to the backlog queue. At this point, the client application is fully connected, but the server application has not yet received the connection as a result value of ServerSocket.accept. When it does so, the entry is removed from the queue.

我仍然不确定在 SSL 的情况下,握手是否也由 ServerSocket.accept() 并行完成以进行同时连接。

更新 2 ServerSocket.accept() 方法本身根本不做任何真正的网络。一旦操作系统建立了新的 TCP 连接,它将 return。操作系统本身持有一个等待TCP连接的队列,可以通过ServerSocket构造函数中的"backlog"参数控制:

ServerSocket serverSocket = new ServerSocket(port, 50);
//this will create a server socket with a maximum of 50 connections in the queue

客户端调用 Socket.connect() 后 完成 SSL 握手。因此 ServerSocket.accept() 一个线程就足够了。

以下是关于您的问题的一些想法:

您不能 listen() 与多个 ServerSocket 在同一个 IP+端口上。如果可以,OS 会将 SYN 数据包传输到哪个套接字?*

TCP 确实维护了预先接受的连接的积压,因此对 accept() 的调用将 return(几乎)立即成为积压队列中的第一个(最旧的)套接字。它通过自动发送 SYN-ACK 数据包来回复客户端发送的 SYN,并等待回复 ACK(3-way handshake)。 但是,正如@zero298 所建议的那样,尽可能快地接受连接通常不是问题。问题将是 loadprocessing 与您将接受的所有套接字通信引起的,这很可能会使您的服务器崩溃(这实际上是一次 DoS 攻击)。事实上,backlog 参数通常在这里,因此太多并发连接在积压队列中等待太久而无法 accept()ed 将在到达您的应用程序之前被 TCP 丢弃。

我建议您使用 ExecutorService 线程池 运行 一些最大数量的线程,而不是为每个客户端套接字创建一个线程,每个线程处理与一个客户端的通信。这允许系统资源的优雅降级,而不是创建数百万个线程,这反过来会导致线程饥饿、内存问题、文件描述符限制……再加上精心选择的积压值,您将能够获得您的服务器在不崩溃的情况下可以提供的最大吞吐量。如果您担心 SSL 上的 DoS,客户端线程的 run() 方法应该做的第一件事就是在新连接的套接字上调用 startHandshake()

关于 SSL 部分,TCP 本身不能做任何 SSL 预接受,因为它需要执行 encryption/decoding、与密钥库对话等,这远远超出了它的规范。请注意,在这种情况下,您还应该使用 SSLServerSocket

要解决您提供的用例(客户端愿意延迟对您的服务器的 DoS 握手),您将有兴趣阅读 Oracle forum post about it 其中 EJP(再次)回答:

The backlog queue is for connections that have been completed by the TCP stack but not yet accepted by the application. Nothing to do with SSL yet. JSSE doesn't do any negotiation until you do some I/O on the accepted socket, or call startHandshake() on it, both of which you would do in the thread dealing with the connection. I don't see how you can make a DOS vulnerability out of that, at least not an SSL-specific one. If you are experiencing DOS conditions, most probably you are doing I/O in the accept() thread that should be done in the connection-handling thread.

*:尽管 Linux >=3.9 does some kind of load-balancing,但仅适用于 UDP(因此 not SSLServerSocketand with option SO_REUSEPORT,反正也不是所有平台都可用。