C 为提高性能而设计服务器的最佳方法是什么?

C 为提高性能而设计服务器的最佳方法是什么?,c,network-programming,C,Network Programming,我正在尝试创建一个我期望具有高性能要求的服务器。这个问题涉及服务器核心。什么编程思想最能支持快速性能 您是否将套接字拆分为不同的线程,并在每个线程上调用阻塞recv? 您是否有一个线程位于select循环中,然后通知另一个线程处理各个端口? 您是否有一个线程处理select和响应? 您是使用2个还是3个端口,但使用端口群集而不是所有端口? 如果使用如上所述的select,使用阻塞端口与非阻塞端口是否重要? 什么setsockopt可以提高性能:TCP_节点延迟,其他? 我意识到其中一些取决于用例

我正在尝试创建一个我期望具有高性能要求的服务器。这个问题涉及服务器核心。什么编程思想最能支持快速性能

您是否将套接字拆分为不同的线程,并在每个线程上调用阻塞recv? 您是否有一个线程位于select循环中,然后通知另一个线程处理各个端口? 您是否有一个线程处理select和响应? 您是使用2个还是3个端口,但使用端口群集而不是所有端口? 如果使用如上所述的select,使用阻塞端口与非阻塞端口是否重要? 什么setsockopt可以提高性能:TCP_节点延迟,其他?
我意识到其中一些取决于用例。例如,如果存在大量小数据包,关闭TCP_节点延迟的6将产生负面影响。3听起来如果响应很小可能会更快。我还将考虑任何其他影响性能的问题。

我将从单线程方法开始:在Linux上使用非阻塞I/O和边缘触发epoll等快速轮询机制。其他平台也有类似的技术。以轮询循环为中心大大简化了程序设计,因此我肯定也会在其中添加signalFD、timeRFD和eventFD。然后一切都由一个中央循环处理


如果需要使用多线程,这可能与同时运行主循环几次一样简单。如果将事件设置为“一次触发”,则它们将从轮询中禁用,直到重新启动,因此处理事件的线程可以安全地假定为唯一执行此操作的线程,并在最后重新配置事件。您只需要同步程序不同部分之间的通信或共享数据访问,但轮询器已经完成了很多同步工作。

我将从单线程方法开始:使用非阻塞I/O和快速轮询机制,如Linux上的边缘触发epoll。其他平台也有类似的技术。以轮询循环为中心大大简化了程序设计,因此我肯定也会在其中添加signalFD、timeRFD和eventFD。然后一切都由一个中央循环处理

如果需要使用多线程,这可能与同时运行主循环几次一样简单。如果将事件设置为“一次触发”,则它们将从轮询中禁用,直到重新启动,因此处理事件的线程可以安全地假定为唯一执行此操作的线程,并在最后重新配置事件。您只需要同步程序不同部分之间的通信或共享数据访问,但轮询器已经完成了很多同步工作。

这取决于您的情况

这类问题很难回答,;这将是项目本身的角色之一。您需要测量服务器在将要面对的工作负载下的性能,然后查看哪些选项最适合您的用例

例如,设置TCP_节点延迟将减少请求的延迟,但该选项的存在是有原因的;您将通过设置TCP_节点延迟来降低吞吐量

以下网站有一些您应该查看的信息:。其中的一些现在有点老了几年,但是它包含了您应该考虑使用的技术列表:EPOLL,异步I/O

您应该开始以模块化的方式设计系统,这样您的工作人员就不会被绑定到特定的实现select/poll/epoll。像setsockopt这样的东西以后可以很容易地更改,您根本不必担心它们

先让它工作,然后让它快;不管你说的快是什么意思。如果你想要一个可伸缩的东西,那么请注意^2上算法的大O。。。等等。

视情况而定

这类问题很难回答,;这将是项目本身的角色之一。您需要测量服务器在将要面对的工作负载下的性能,然后查看哪些选项最适合您的用例

例如,设置TCP_节点延迟将减少请求的延迟,但该选项的存在是有原因的;您将通过设置TCP_节点延迟来降低吞吐量

以下网站有一些您应该查看的信息:。其中的一些现在有点老了几年,但是它包含了您应该考虑使用的技术列表:EPOLL,异步I/O

您应该开始以模块化的方式设计系统,这样您的工作人员就不会被绑定到特定的实现select/poll/epoll。像setsockopt这样的东西以后可以很容易地更改,您根本不必担心它们

先让它工作,然后让它快;不管你说的快是什么意思。如果你想要一个可伸缩的东西,那么请注意^2上算法的大O。。。等
在我看来,编写代码最简单的方法是使用阻塞I/O为每个连接编写一个线程。使用您最喜欢的线程模型编写可移植的代码也很容易

多路复用非阻塞I/O的问题在于维护每个连接的状态。例如,我想写1024字节,但只写900字节。。。因此,现在必须记住124字节,以便稍后写入它们。这只是在原始状态下发送一个缓冲区级别;考虑一下整个协议的状态,它很快就会变得复杂起来。当然,没有什么是不可能的,但如果连接不需要彼此进行太多交互,那么只使用阻塞调用要简单得多

我已经将这种方法用于少量的几十个连接,并在一对10GbE链路上以每秒超过1千兆字节的速度移动数据。Linux内核的调度程序非常擅长处理这个范围内的线程数

对于一个Web服务器类型的东西,服务数千或上万个客户端。。。嗯,我没有亲自尝试过。我已经读到,在这种情况下,多路复用技术epoll等速度更快。因此,正如其他人所说,这取决于您的应用程序


但是,如果您的应用程序与我的应用程序一样,连接数量适中,它们之间的交互有限,那么我认为,每个连接一个线程的方法可以轻而易举地获得成功。

在我看来,最容易编写代码的是使用阻塞I/O的每个连接一个线程。使用您最喜欢的线程模型编写可移植的代码也很容易

多路复用非阻塞I/O的问题在于维护每个连接的状态。例如,我想写1024字节,但只写900字节。。。因此,现在必须记住124字节,以便稍后写入它们。这只是在原始状态下发送一个缓冲区级别;考虑一下整个协议的状态,它很快就会变得复杂起来。当然,没有什么是不可能的,但如果连接不需要彼此进行太多交互,那么只使用阻塞调用要简单得多

我已经将这种方法用于少量的几十个连接,并在一对10GbE链路上以每秒超过1千兆字节的速度移动数据。Linux内核的调度程序非常擅长处理这个范围内的线程数

对于一个Web服务器类型的东西,服务数千或上万个客户端。。。嗯,我没有亲自尝试过。我已经读到,在这种情况下,多路复用技术epoll等速度更快。因此,正如其他人所说,这取决于您的应用程序


但是,如果您的应用程序与我的应用程序一样,连接数量适中,它们之间的交互有限,那么每个连接一个线程的方法可以轻而易举地获胜,我想。

意识到其中一些取决于用例-是的,它确实如此。。确切的用例是什么?有很多同时的客户吗?你是否做CPU密集型工作,等等…意识到其中一些取决于用例-是的,确实如此。。确切的用例是什么?有很多同时的客户吗?你做CPU密集型的工作吗,等等…这个epoll类似于Window的CreateIOCompletionPort吗?我不认为这是一个好的开始;大多数人对状态机设计不够熟练,认为这很容易,即使在概念上对您来说很容易,但接收到的缓冲区管理部分消息、写缓冲区满等是一个巨大的痛苦。传统上,对于多进程服务器,这种方法在缓慢的上下文切换中是最快的,但是在同一进程中的线程之间切换要比大多数系统调用在测量时看到的快,所以现在这种方法是有竞争力的。@R..:也许吧。这可能不是最简单的方法,但它非常强大,您可以在不需要多线程的情况下从中获得很多好处——如果您决定使用多线程,则可以自然地向上扩展它。是的,它很复杂,但是C++是解决复杂问题的理想语言,它可以分解成更小的子问题。一个罕见的写队列?如果您的写入失败,并且当套接字轮询为准备写入时,您将首先耗尽队列。。。所有这些都可以很好地隐藏在一个合适的类中,听起来很容易,直到你考虑失败案例。当分配更多缓冲区空间失败时,您会怎么做?在多线程方法中,连接的完整状态在调用线程的堆栈上,如果写操作无法完成,线程只会阻塞。当内存不可用时,如果没有线程准备好接受新连接,则接受新连接可能会失败或暂停,但您不必处理如何处理无法继续的现有连接的复杂性。此epoll类似于Windows的CreateIOCompletionPort吗?我不认为这是一个好的开始;大多数人都不太精通状态机设计,认为这很容易,即使在概念上对您来说很容易,但接收到的缓冲区管理部分消息、写缓冲区满等是一个巨大的痛苦。
. 传统上,对于多进程服务器,这种方法在缓慢的上下文切换中是最快的,但是在同一进程中的线程之间切换要比大多数系统调用在测量时看到的快,所以现在这种方法是有竞争力的。@R..:也许吧。这可能不是最简单的方法,但它非常强大,您可以在不需要多线程的情况下从中获得很多好处——如果您决定使用多线程,则可以自然地向上扩展它。是的,它很复杂,但是C++是解决复杂问题的理想语言,它可以分解成更小的子问题。一个罕见的写队列?如果您的写入失败,并且当套接字轮询为准备写入时,您将首先耗尽队列。。。所有这些都可以很好地隐藏在一个合适的类中,听起来很容易,直到你考虑失败案例。当分配更多缓冲区空间失败时,您会怎么做?在多线程方法中,连接的完整状态在调用线程的堆栈上,如果写操作无法完成,线程只会阻塞。当内存不可用时,如果没有线程准备好接受新连接,则接受新连接可能会失败或暂停,但您不必处理如何处理无法继续的现有连接的复杂性。这绝对是最简单的设计,此外,它还可能成为性能最高、用户/内核转换次数最少的产品,特别是在您不经常添加和删除连接的情况下。虽然我很欣赏其关于假装用心的建议,但问题是性能,而不是可编码性。@chacham15:是的,我应该说得更清楚。在我的应用程序中,我需要移动商品(即廉价硬件)上维持的1+千兆字节/秒的数据。每个连接一个线程可以轻松地使10GbE链路饱和,最多可连接100个左右。除此之外,我没有个人经验。。。但正如R.所指出的,有理由期望这种设计在许多情况下表现最好。我自己的就是其中之一。另外,重点不是胆小,重点是编写可维护的代码。@Nemo是的,我想我应该详细说明我的应用程序的更多特性。我没有这样做,因为我想大致了解这些选择是什么以及它们是如何变化的。TBH,今天的代码完全实现了您所描述的,完全出于可维护性的原因,但我想继续前进,如果有必要走另一条路,我就不必自食其果了。这绝对是最简单的设计,此外,它还可能成为性能最高、用户/内核转换次数最少的产品,特别是在您不经常添加和删除连接的情况下。虽然我很欣赏其关于假装用心的建议,但问题是性能,而不是可编码性。@chacham15:是的,我应该说得更清楚。在我的应用程序中,我需要移动商品(即廉价硬件)上维持的1+千兆字节/秒的数据。每个连接一个线程可以轻松地使10GbE链路饱和,最多可连接100个左右。除此之外,我没有个人经验。。。但正如R.所指出的,有理由期望这种设计在许多情况下表现最好。我自己的就是其中之一。另外,重点不是胆小,重点是编写可维护的代码。@Nemo是的,我想我应该详细说明我的应用程序的更多特性。我没有这样做,因为我想大致了解这些选择是什么以及它们是如何变化的。TBH,今天的代码实现了您所描述的,完全是出于可维护性的原因,但是我想继续前进,如果有必要走另一条路,我就不必自食其果了。