为什么我应该使用非阻塞或阻塞套接字?

51

首先,我必须问一下,在哪些情况下是最好的?比如实时MMORPG服务器。如果我为每个客户端创建一个线程而不是使用非阻塞套接字会怎样?或者如果我使用一个包含所有非阻塞套接字的线程会怎样?您能解释一下它们的优点吗?

2个回答

50

您的问题值得更长的讨论,但这里是一个简短的回答:

  • 使用阻塞套接字意味着在任何一个线程中,仅有一个套接字可以处于活动状态(因为它会在等待活动时被阻塞)
  • 使用阻塞套接字通常比使用非阻塞套接字更容易(异步编程往往更加复杂)
  • 您可以像您所说的那样为每个套接字创建1个线程,但线程具有开销,并且与非阻塞解决方案相比极其低效;
  • 使用非阻塞套接字,您可以处理更大量的客户端:它可以扩展到单个进程中的数十万客户端 - 但代码会变得有点更复杂

在 Windows 上使用非阻塞套接字有几种选项:

  • 轮询
  • 基于事件
  • 重叠 I/O

重叠 I/O 将为您提供最佳性能(每个进程数千个套接字),但代价是最难理解和正确实现的模型。

基本上,这归结为性能与编程复杂性之间的折衷。

注意

以下是为什么使用线程/套接字模型是一个坏主意的更好的解释:

在 Windows 上,创建大量线程效率非常低,因为调度程序无法正确确定哪些线程应该接收处理器时间,哪些不应该。加上每个线程的内存开销,这意味着,在OS级别,您将在运行出处理套接字连接的能力之前就耗尽内存(因为堆栈空间)和处理器周期(因为管理线程的开销)。


10
我的观点,虽然表述不够清晰,但是意思是你不能仅仅将线程数超过20的任何数量都称之为“糟糕”,这是一条任意的界限。 - John Dibling
5
你不应该普遍地谴责线程/套接字模型。并不是所有东西都是Web服务器。 - John Dibling
@MikeDinescu你说过,“通常超过10-20个线程被认为是不好的实践”。这是指每个核心、每台机器还是什么? - PHcoDer
2
必须明确而坚定地指出,“10-20个线程”的说法是无稽之谈。实际上,任何Java程序都将具有十个线程,而且肯定有服务器使用数十万个线程。 - user207421
1
这可能是作者写作时的情况,但我非常怀疑自从W8以来改进了调度后是否仍然如此。我刚刚在同一台机器上(16/32核心)尝试了5000个服务器线程+ 5000个客户端线程(加上5000个额外的写入线程),所有线程都使用阻塞套接字,并且进行了1000次乒乓连接(每个连接约20个字符)。效果很棒-客户端的平均运行时间为52秒,最小值为44秒,最大值为62秒。几乎没有线程停顿或类似情况,工作负载在线程之间得到了良好的共享。你也可以试试。 - user2328447
显示剩余4条评论

16
我会明确地说,在几乎所有情况下(除了玩具程序),你都应该把非阻塞套接字作为惯例使用。
阻塞套接字会引起严重问题:如果另一端的计算机(或连接到它的任何部分)在阻塞调用期间发生故障,则你的代码将一直被阻塞,直到IP堆栈超时。在典型情况下,这需要大约2分钟,对于大多数目的来说完全不可接受。唯一的中止阻塞调用的方法是终止进行调用的线程--但是终止线程本身几乎总是不可接受的,因为几乎无法在其后清理并重新获取它分配的任何资源。非阻塞套接字使得在需要时轻松中止调用成为可能,而不需要对进行调用的线程做任何处理。
如果采用多进程模型,也可以使阻塞套接字工作得相当好。在这里,你只需为每个连接生成一个全新的进程。该进程使用阻塞套接字,如果出现问题,你只需终止整个进程。操作系统知道如何清理进程的资源,所以清理不是问题。它仍然有其他潜在问题:1)你几乎需要一个进程监视器来在需要时终止进程,2)生成进程通常要比仅创建套接字昂贵得多。尽管如此,这仍然可能是一个可行的选择,特别是如果:
1. 你只处理一小部分连接 2. 你通常为每个连接进行大量处理 3. 你只与本地主机打交道,所以你与它们的连接快速且可靠 4. 你更关心优化开发而非执行
注:技术上讲,并不是唯一的可能方法,但大多数替代方法相对较丑陋--更具体地说,我认为通过添加代码来判断是否存在问题,然后修复该问题,你可能比使用非阻塞套接字做更多的额外工作。

10
可以通过在另一个线程上下文中断开套接字来终止阻塞的套接字操作,而不会终止被阻塞的线程。这将导致阻塞的操作以错误代码失败,然后被阻塞的线程可以继续进行其他操作,如正常的清理工作。 - Remy Lebeau
这看起来至少非常激进。Linux recv() 的手册说,如果没有消息可用,它将在阻塞套接字上阻塞。我认为您只需要在接收之前使用 select()poll(),就不必担心本文提到的任何问题了。 - Błażej Michalik
但是手册进一步说明:“在Linux上,select()可能会将套接字文件描述符报告为“准备好读取”,而随后的读取仍然会阻塞。例如,当数据到达但检查和校验和错误时被丢弃时,就可能发生这种情况。在其他情况下,文件描述符可能会被虚假地报告为已准备好。因此,在不应阻塞的套接字上使用O_NONBLOCK可能更安全。”因此,阻塞套接字可能不是可行的选择。 - Haris

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接