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
}
}
}如果我们假设运行了ServerThread.run()的10个不同线程,这会成功吗?还是应该对所有线程使用相同的ServerSocket对象?
文档说:
如果ServerSocket的构造函数不能侦听指定端口(例如,端口已经在使用),则会抛出异常。
您可能会想知道,为什么我一开始就想这样做,而不是简单地让一个线程运行ServerSocket.accept()。好吧,我的假设是(如果我错了,请纠正我的看法),accept()方法可能需要一些时间才能完成连接的建立,特别是如果ServerSocket是SSL (因为握手)。因此,如果两个客户想同时连接,一个必须等待另一个客户端。对于高流量服务器来说,这将是非常糟糕的。
更新:似乎一旦建立了属于队列的连接,()方法就会返回。这意味着如果有一个等待连接的客户机队列,服务器线程可以以最快的方式处理请求,并且只需要一个线程。(除了为每个请求创建一个新线程和启动线程所需的时间外,当使用线程池时,时间是可以忽略不计的)
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 queueSSL握手是在客户端调用Socket.connect()之后完成的。因此,ServerSocket.accept()的一个线程总是足够的。
发布于 2019-07-24 22:59:16
下面是一些关于你的问题的一些想法:
您不能在同一个listen()上使用多个ServerSocket。如果可以,操作系统将向哪个套接字传输SYN数据包?*
实际上,TCP维护着预先接受的连接的待办事项,因此对accept()的调用将立即返回(几乎)待办事项队列中的第一个(最老的)套接字。它通过自动发送SYN数据包来响应客户端发送的SYN,并等待reply-ACK ( 三通握手)。但是,正如@zero298 298所暗示的,尽可能快地接受连接通常不是问题。问题将是处理与您将接受的所有套接字的通信所产生的负载,这些套接字很可能会使您的服务器陷入瘫痪(实际上是DoS攻击)。实际上,backlog参数通常在这里,因此在等待accept()编辑的待定队列中等待太长时间的同时连接将被TCP删除,然后再到达应用程序。
与其为每个客户端套接字创建一个线程,我建议您使用一个ExecutorService线程池来运行一些最大数量的线程,每个线程都处理与一个客户端的通信。这允许系统资源的优雅退化,而不是创建数百万个线程,这些线程反过来会造成线程饥饿、内存问题、文件描述符限制、.再加上精心选择的积压值,您将能够获得服务器可以提供的最大吞吐量,而不会使其崩溃。如果您担心SSL上的DoS,那么客户端线程的run()方法应该做的第一件事就是在新连接的套接字上调用startHandshake()。
关于SSL部分,TCP本身不能执行任何SSL预接受操作,因为它需要执行加密/解码、与密钥存储库( keystore )等超出其规范的操作。注意,在这种情况下还应该使用SSLServerSocket。
为了查看您给出的用例(客户端愿意延迟与DoS服务器的握手),您将有兴趣阅读EJP (再次)回答的甲骨文论坛发帖:
待办事项队列用于TCP堆栈已完成但应用程序尚未接受的连接。与SSL没有任何关系。在对接受的套接字执行一些I/O操作或调用startHandshake()之前,JSSE不会进行任何协商,在处理连接的线程中都会这样做。我看不出你怎么能利用这个漏洞制造DOS漏洞,至少不是SSL特有的漏洞。如果您正处于DOS条件下,很可能您正在接受()线程中执行I/O操作,而这个I/O应该在连接处理线程中执行。
*:虽然是Linux >=3.9 does some kind of load-balancing,但只适用于UDP (所以不适用于SSLServerSocket)和选项SO_REUSEPORT,这在所有平台上都是不可用的。
https://stackoverflow.com/questions/57190490
复制相似问题