我正在用C语言写一个TCP服务器,作为一项任务,我知道明年我会得到。我实现了一个使用单个线程处理所有连接的核心,它使用epoll工具来完成此任务。核心为程序的其余部分提供了一个API,就像C#提供的异步网络一样;一切都很好,经过一些epoll古怪 - 我突然不得不使用文件描述符重复来保持它的快乐,但幸运的是,我开始明白为什么 - 我设法得到它的bug和无泄漏。但是,我不确定如何停止服务器。目前,我编写了清理核心数据结构的清理例程,但从未调用它们。主epoll_wait
循环永远不会结束。如何停止循环?
我已经想到了以下内容:
我使用由互斥锁保护的全局标志,每次在调用epoll_wait()
之前由核心线程检查。当标志变为真时,核心线程从其循环中断并清除所有内容。这在我使用核心线程来设置标志时有效,例如当它通过回调退出核心上下文时(例如,当一个新的连接被发送到侦听套接字时调用的回调;核心对此一无所知)这些回调的实现,在这些回调中,我可以设置停止标志。在这种情况下,互斥锁甚至不是必需的。)但是,当我想要一个外部线程(GUI事件调度线程)时,这并不总是有效或许?如果我想编写一个GUI来监视服务器运行时,可以派上用场来设置该标志。当然,核心线程一旦发生再次检查标志就会停止服务器,但问题是如果服务器空闲(例如没有客户端且没有新连接时),它将不会经常检查该标志。然后epoll_wait()
将被阻止,我将不得不杀死服务器 - 使用Ctrl-C或类似的东西。那不是我想要的。
为了解决这个问题,我考虑创建一个管道,并在启动时将该管道fd添加到服务器的epoll上下文中,这样每当某个线程写入时,它就会响应并从epoll_wait()
返回(不相关)该管道的数据。这可能允许某些线程强制核心检查其标志并退出。但这感觉就像一个混乱的解决方案。 有更好的选择吗?如果没有,这个管道解决方案会起作用吗?
提前感谢,像往常一样,欢迎任何评论(如果有建设性的话)!
答案 0 :(得分:3)
您可以使用常用语言结构之一停止循环,例如break
或while
或for
循环中的条件。
但我怀疑你要问的是何时停止循环。您可能希望在程序收到信号时执行此操作,即SIGTERM(例如,当systemd等服务管理器希望您的守护程序死机时)或SIGINT(例如,当您按CTRL + C时)。如果你没有对程序中的信号做任何事情,当你收到这两个中的任何一个时,你的程序将立即停止,并且你没有机会进行清理。
到目前为止,在Linux中捕获信号的最简单方法是使用signalfd
工具(参见手册页)。这样,您就不需要处理信号处理程序,以及由于信号导致的竞争条件,死锁和随机系统调用等众多问题。相反,您将获得一个文件描述符,您可以在epoll
实例中注册该文件描述符以获取可读性通知。当信号准备就绪时,文件描述符将是可读的,您可以将信号信息读入结构中。在这里你有机会突破主循环并进行清理。
但请记住,有很好的库(比如libuv)可以抽象出所有这些细节并支持多个平台。
<强>更新
如果要从另一个线程停止服务器,那么您有一个更普遍的问题的实例:将信息传递给事件循环。虽然你向自己发送信号的想法会起作用,但这有点像黑客。至少有两个“正确”的解决方案:pipe
和eventfd
,后者是特定于Linux的,专门针对此问题而设计。为了您的使用,它们都足够了。
例如,对于管道,您可以让事件循环监视读取结束,并让另一个线程将单个字节写入写入结束。但是要小心将管道设置为非阻塞,否则写入线程可能阻止写入管道,并且根据设计,您的程序可能会死锁。
如果您需要将更多信息从线程传递到事件循环(您不是为了停止服务器),可以将其放在内存中,可能是队列中,并且具有正确的锁定。