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

时间:2019-07-24 20:05:14

标签: java ssl serversocket

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说:

  

ServerSocket的构造函数无法在指定端口上侦听(例如,该端口已被使用

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

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

ServerSocket还具有一个名为“ backlog”的参数,您可以在其中设置队列中的最大连接数。根据《 Java基础网络》 3.3.3

  

TCP本身在接受连接方面可以领先于TCP服务器应用程序。它   维护与侦听套接字(TCP iself)的连接的“积压队列”   已完成,但尚未被申请接受。这个   基础TCP实现和服务器进程之间存在队列   这创建了监听套接字。预完成连接的目的   是为了加快连接阶段,但是队列的长度受到限制,以便   不要与不接受它们的服务器建立太多连接   出于任何原因都保持相同的汇率。收到传入的连接请求时   并且积压队列未满,TCP完成连接协议,并且   将连接添加到积压队列。此时,客户端应用程序已完全连接,但由于ServerSocket.accept的结果值,服务器应用程序尚未收到连接。这样做时,将从队列中删除该条目。

我仍然不确定是否使用SSL,是否也通过ServerSocket.accept()并行进行握手以进行同时连接。

更新2 ServerSocket.accept()方法本身根本不进行任何实际的联网。操作系统建立新的TCP连接后,它将立即返回。操作系统本身拥有一个等待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()的一个线程总是足够的。

1 个答案:

答案 0 :(得分:2)

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

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

TCP确实维护了预先接受的连接的待办事项,因此对accept()的调用将立即(几乎)立即返回待办事项队列中的第一个(最早的)套接字。它是通过自动发送SYN-ACK数据包以响应客户端发送的SYN来实现的,并等待答复ACK(3-way handshake)。 但是,正如@ zero298所建议的那样,尽快接受连接通常不是问题。问题将是与所有接受的套接字进行处理通信而产生的负载,这很可能使您的服务器瘫痪(实际上是DoS攻击)。实际上,这里通常使用backlog参数,因此在到达您的应用程序之前,TCP将丢弃太多等待同时进行的积压队列中accept()的并发连接。

建议您不要使用ExecutorService线程池,该线程池运行一些最大数量的线程,每个线程处理与一个客户端的通信,而不是为每个客户端套接字创建一个线程。这样可以适度地减少系统资源,而无需创建数百万个线程,而这又会导致线程匮乏,内存问题,文件描述符限制等。...加上精心选择的积压值,您将能够获得服务器可以提供的最大吞吐量而不会崩溃。而且,如果您担心SSL上的DoS,客户端线程的run()方法应该做的第一件事就是在新连接的套接字上调用startHandshake()

关于SSL部分,TCP本身无法进行任何SSL预接受,因为它需要执行加密/解码,与密钥库进行对话等,这远远超出了其规范。请注意,在这种情况下,您还应该使用SSLServerSocket

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

  

积压队列用于TCP堆栈已完成但应用程序尚未接受的连接。与SSL无关。 JSSE不会进行任何协商,直到您在接受的套接字上执行一些I / O或在其上调用startHandshake()为止,这两者都将在处理连接的线程中进行。我看不出如何才能从中制造出DOS漏洞,至少不是特定于SSL的漏洞。如果遇到DOS条件,则很可能是在accept()线程中执行I / O,应该在连接处理线程中完成。

*:虽然是Linux >=3.9 does some kind of load-balancing,但仅适用于UDP(所以不是 SSLServerSocket,带有选项SO_REUSEPORT,这是并非在所有平台上都可用。