编写Linux守护进程的最佳方法

6

我需要写一个TCP守护程序来响应我们的客户端软件,想知道有没有什么关于这方面的最佳实践。

我是否应该为每个新连接fork一个进程,还是像通常一样使用线程?

7个回答

10

这取决于你的应用程序。线程和forking都可以是完全有效的方法,以及单线程事件驱动模型的第三种选择。如果你能更详细地解释一下你正在编写的内容,那么在给出建议时会更有帮助。

值得一提的是,以下是一些一般性的指导方针:

  • 如果没有共享状态,请使用forking。
  • 如果有共享状态,请使用线程或事件驱动系统。
  • 如果需要在非常大量的连接下实现高性能,请避免使用forking,因为它具有较高的开销(特别是内存使用)。而是使用线程、事件循环或几个事件循环线程(通常每个CPU一个)。

通常来说,forking将是最容易实现的,因为一旦你fork,你就可以基本上忽略所有其他连接;线程是因为需要额外的同步要求而更困难;事件循环由于需要将处理转换为状态机而更加困难;多个线程运行事件循环则是最困难的(由于结合了其他因素)。


fork的内存开销并不太大,因为进程内存是通过写时复制方式进行复制的。主要缺点是fork()所需的时间,但如果您不频繁创建和销毁连接,那么这不应该成为一个阻碍因素。 - caf
真的,但它可能不仅仅是一个带有文件描述符和输入缓冲区的小结构体 :) - bdonlan

4
我建议优先选择使用fork而不是线程来进行连接。线程的问题在于共享内存空间,以及另一个线程很容易篡改内存。使用分叉进程时,进程之间的通信必须由您有意地完成。
我刚搜索并找到了这个SO答案:What is the purpose of fork?。显然你已经知道了答案,但该线程中排名第一的答案提出了fork()的优点。

1
另一个好处是,中止的线程可能会被搁置或导致整个应用程序崩溃。如另一个答案所建议的那样,您可以设置一组工作进程池。考虑使用一个简单的委托进程来排队工作,由工作进程协作地出队列。这种设置使您可以更好地控制系统资源。 - Bill Hoag

1
除了@hobodave的好答案之外,“每个连接分叉”的另一个好处是,您可以通过使用inetdtcpserver等非常简单地实现服务器:然后可以使用标准输入和标准输出与套接字进行通信,并且不必进行任何侦听套接字管理(侦听连接等)。

1

当然,另一个选择是预先派生多个守护进程的副本,并使每个副本保持活动状态并继续响应请求。这完全取决于您的应用程序、预期负载和性能要求等因素。

最简单和最简单的方法是编写基于inetd的守护进程;您的软件可以忽略它正在通过TCP连接运行的事实,并通过stdin/stdout处理输入/输出。在绝大多数情况下,这种方式都很有效。


1

如果你不打算每秒处理大量的新连接,考虑从inetd运行。否则...

下载OpenSSH源代码。他们在特权分离方面做得非常好,它是可移植的,并且已经接受了比其他任何东西都更多的安全审查。

根据您的需求进行调整,您可能可以扔掉大部分内容。当然要遵守许可协议。使用良好的SCC跟随未来的补丁。

在没有充分证据表明这是一个真正的问题之前,不要担心fork进程与线程的性能差异。Apache使用简单的每个客户端模型运行最繁忙的站点多年。

如果你真的雄心勃勃,你可以使用某种非阻塞异步IO模型。我喜欢Boost.Asio,但我很重视C++。

确保您的代码正确处理信号。HUP重新加载配置。TERM优雅地关闭。

不要尝试编写自己的日志文件。仅使用syslog,或者只写入可以重定向到syslog的stderr。在所有记录略有不同的自制服务器上设置logrotate真的很麻烦。


1
如果您想完全避免线程/分叉,我建议使用所有非阻塞I/O以及libevent。 Libevent作为一种高性能的事件驱动编程解决方案而广为人知。

0

看看ACE (C++/Java)。它有许多线程、事件和分叉TCP反应器,可以满足您的通信需求。您还可以查看Boost ASIO,它也有类似的功能。


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