【问题标题】:Writing multithreaded TCP server on Linux在 Linux 上编写多线程 TCP 服务器
【发布时间】:2012-06-15 03:41:36
【问题描述】:

在工作中,我的任务是实现 TCP 服务器作为 Modbus 从设备的一部分。我在堆栈交换和一般互联网(包括优秀的http://beej.us/guide/bgnet/)上都做了很多阅读,但我正在努力解决设计问题。总之,我的设备只能接受 2 个连接,并且每个连接上都会传入 modbus 请求,我必须在主控制器循环中处理这些请求,然后回复成功或失败状态。关于如何实现这一点,我有以下想法。

  1. 有一个侦听器线程来创建、绑定、侦听和接受连接,然后生成一个新的 pthread 来侦听连接上的传入数据并在空闲超时时间后关闭连接。如果当前活动线程数为 2,则立即关闭新连接以确保只允许 2 个。

  2. 不要从侦听器线程生成新线程,而是使用 select() 来检测传入的连接请求以及活动连接上的传入 modbus 连接(类似于 Beejs 指南中的方法)。

  3. 创建 2 个侦听器线程,每个线程创建一个套接字(相同的 IP 和端口号),它可以阻塞 accept() 调用,然后关闭套接字 fd 并处理连接。在这里,我(也许天真地)假设这将只允许最多 2 个连接,我可以使用阻塞读取来处理这些连接。

我已经使用 C++ 很长时间了,但我对 Linux 开发还很陌生。我真的很欢迎关于上述哪种方法最好(如果有的话)的任何建议,以及我对 Linux 的缺乏经验是否意味着它们中的任何一种都是非常糟糕的想法。我热衷于避免 fork() 并坚持使用 pthread,因为传入的 modbus 请求将被排队并定期读取主控制器循环。提前感谢您的任何建议。

【问题讨论】:

    标签: c++ linux multithreading sockets pthreads


    【解决方案1】:

    第三种方法不行,只能绑定一次本地地址。

    我可能会使用您的第二个替代方案,除非您需要进行大量处理,在这种情况下,第一个替代方案的组合可能会很有用。

    我正在考虑的两个第一个替代方案的组合是让主线程(程序启动时始终拥有的那个)创建两个工作线程,然后进行阻塞 accept 调用以等待新的联系。当新连接到达时,告诉其中一个线程开始处理新连接并返回阻塞accept。当第二个连接被接受时,你告诉另一个线程在那个连接上工作。如果两个连接都已打开,则要么在一个连接关闭之前不接受,要么等待新连接但立即关闭它们。

    【讨论】:

    • 我喜欢这个声音——唯一的问题是我的主循环绝对不能阻塞。它必须进行处理并定期处理来自侦听器线程的请求。考虑到这一点,您说选项 2. 最好?
    • @mathematician1975 你仍然可以使用我的方法,但不要阻塞 accept 使用短或无超时 select (或使侦听套接字非阻塞并使用 accept 并检查让EAGAIN/EWOULDBLOCK) 知道何时可以接受连接。
    • 我认为鉴于我的时间限制,这是我短期内追求的最佳解决方案。谢谢你的建议。
    【解决方案2】:

    您提出的所有设计选项都不是非常面向对象的,而且它们都更倾向于 C 而不是 C++。如果您的工作允许您使用 boost,那么 Boost.Asio 库非常适合制作简单(和复杂)的套接字服务器。您几乎可以采用他们的任何示例并将其简单地扩展为仅允许 2 个活动连接,并在打开所有其他连接后立即关闭它们。

    在我的脑海中,他们的简单 HTTP 服务器可以通过在连接类中保留一个静态计数器来进行修改(inc 在构造函数中,dec 在析构函数中),当创建一个新计数器时,检查计数并决定是否关闭连接。连接类也可以获得 boost::asio::deadline_timer 来跟踪超时。

    这将最类似于您的第一个设计选择,boost 可以在 1 个线程中执行此操作,并且在后台执行类似于select()(通常为epoll())的操作。但这是“C++ 方式”,我认为使用select() 和原始pthreads 是C 方式。

    【讨论】:

    • 感谢您的建议。关于它的 C++ 和 OO 方面,我完全同意你的看法。然而,考虑到我目前的时间限制,我认为我将不得不采用原始 linux API 方法,因为我需要时间来适应它并且需要快速制作原型。但是,一旦项目被接受,我想我肯定会喜欢这种方法
    【解决方案3】:

    由于您只处理 2 个连接,因此每个连接的线程非常适合此类应用程序。如果您需要扩展到数千个连接,则使用非阻塞或异步 I/O 的面向对象方法会更好。 2 个侦听器线程有意义,您无需关闭接受 fd。连接完成后回来接受就可以了。实际上,一种变体是让三个线程阻塞执行接受。如果其中两个线程正在积极处理连接,则第三个线程会重置新创建的连接(或返回忙碌响应,无论您的设备是否合适)。

    要让所有三个线程在接受时阻塞,您需要让主线程在三个线程启动之前创建并绑定您的套接字以进行接受/处理处理。

    man page for pthreads on Linux 表示接受是线程安全的。 (线程安全函数下的部分列出了非线程安全的函数,看图。)

    【讨论】:

    • 你的意思是我的问题中的选项 3?
    • @mathematician1975:是的,我在考虑选项 3 的变体,但接受三个线程。
    • 但这里的另一个答案说我只能绑定一次,这排除了选项 3。??
    • @mathematician1975:在主线程中绑定和监听一次,然后启动线程来接受。 Linux 支持这种模式,在其他操作系统上,您可能必须使用互斥锁来保护接受调用。
    • 啊,我明白了 - 所以将套接字 fd 传递给其他线程?所以在任何连接之前,我会有 2 个线程同时阻塞 accept() 吗?这是线程安全的吗?
    猜你喜欢
    • 1970-01-01
    • 2018-01-23
    • 1970-01-01
    • 2021-02-28
    • 2014-11-25
    • 1970-01-01
    • 2011-01-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多