猿问

是否可以在同一端口上创建多个(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 对象吗?

文档说

ServerSocket 的构造函数在无法监听指定端口时抛出异常(例如,该端口已被使用

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

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

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

TCP 本身可以在接受连接方面领先于 TCP 服务器应用程序。它维护一个连接到侦听套接字的“积压队列”,TCP 本身已完成但尚未被应用程序接受。这个队列存在于底层 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

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


心有法竹
浏览 136回答 1
1回答

慕标5832272

以下是关于您的问题的一些想法:您不能listen()在同一个 IP+端口上使用多个ServerSocket. 如果可以,操作系统会将 SYN 数据包传输到哪个套接字?*TCP 确实维护了预先接受的连接的积压,因此调用将accept()(几乎)立即返回积压队列中的第一个(最旧的)套接字。它通过自动发送 SYN-ACK 数据包来回复客户端发送的 SYN,并等待回复 ACK(3 次握手)来实现。但是,正如@zero298 所建议的那样,尽可能快地接受连接通常不是问题。问题将是处理与您将接受的所有套接字的通信所产生的负载,这很可能会使您的服务器崩溃(这实际上是一次 DoS 攻击)。实际上这个参数通常在这里 太多并发连接在积压队列中等待太久backlogaccept()在到达您的应用程序之前,ed 将被 TCP 丢弃。我建议您使用ExecutorService线程池来运行某个最大数量的线程,而不是为每个客户端套接字创建一个线程,每个线程处理与一个客户端的通信。这允许系统资源的优雅降级,而不是创建数百万个线程,这反过来会导致线程饥饿、内存问题、文件描述符限制……再加上精心选择的积压值,您将能够获得您的服务器在不崩溃的情况下可以提供的最大吞吐量。run()如果您担心 SSL 上的 DoS,您的客户端线程方法应该做的第一件事就是调用startHandshake()新连接的套接字。关于 SSL 部分,TCP 本身不能做任何 SSL 预接受,因为它需要执行加密/解码、与密钥库对话等,这远远超出了它的规范。请注意,在这种情况下您还应该使用SSLServerSocket。积压队列用于 TCP 堆栈已完成但尚未被应用程序接受的连接。与 SSL 无关。JSSE 不会进行任何协商,直到您在已接受的套接字上执行一些 I/O,或在其上调用 startHandshake(),这两者都将在处理连接的线程中执行。我看不出如何利用它制造 DOS 漏洞,至少不是特定于 SSL 的漏洞。如果您遇到 DOS 情况,很可能您正在 accept() 线程中执行本应在连接处理线程中完成的 I/O。*:虽然Linux >=3.9 做了某种负载平衡,但仅适用于 UDP(所以不是 SSLServerSocket)并且有选项SO_REUSEPORT,这并不是在所有平台上都可用。
随时随地看视频慕课网APP

相关分类

Java
我要回答