【问题标题】:using boost sockets, do I need only one io_service?使用 boost 套接字,我只需要一个 io_service 吗?
【发布时间】:2011-05-03 23:17:56
【问题描述】:

在几个不同的线程中有几个连接。我基本上是在做一个使用 boost/asio.hpp 和那里的 tcp 东西的基类。 现在我在读这个:http://www.boost.org/doc/libs/1_44_0/doc/html/boost_asio/tutorial/tutdaytime1.html 它说“所有使用 asio 的程序都需要至少有一个 io_service 对象。” 所以我的基类应该有一个静态的io_service(这意味着所有程序只有1个,所有不同的线程和连接都将使用相同的io_service对象) 还是让每个连接都有自己的 io_service?

谢谢前面!

更新: 好的,所以基本上我想做的是一个基本客户端的类,它将有一个套接字。 对于每个套接字,我将有一个始终接收的线程和一个有时发送数据包的不同线程。 在这里查看后:www.boost.org/doc/libs/1_44_0/doc/html/boost_asio/reference/ip__tcp/socket.html (不能制作超链接,因为我是新来的……所以每个帖子只有1个超链接)我可以看到那个套接字类不是完全线程安全的..

所以 2 个问题: 1.根据我刚刚写的设计,我需要为所有的sockets提供1个io_service(意思是让它成为一个静态类成员)还是每个都应该有一个? 2.我怎样才能让它线程安全?我应该将它放在“线程安全环境”中,这意味着创建一个新的套接字类,该类具有互斥锁和不允许您同时发送和接收的东西,还是您有其他建议? 3. 也许我应该进行异步设计? (ofc 每个套接字会有不同的线程,但发送和接收会在同一个线程上?)

澄清一下:我正在做一个连接到许多服务器的 tcp 客户端。

【问题讨论】:

  • “至少一个 io_service”表示“一个或多个”。
  • 我的问题是我应该怎么做,为什么?全部只有 1 个或每个只有 1 个,为什么?
  • 你是做同步io还是异步io?
  • 如果这是您的问题,我会使用另一个线程接收并将其全部放入线程安全缓冲区中
  • @grich 您是否使用async_readasync_writereadwrite 免费功能?

标签: c++ sockets boost boost-asio


【解决方案1】:

您需要先决定要使用哪种类型的套接字通信:

  1. 同步 - 意味着所有低级操作都是阻塞的,通常您需要一个线程来接受,然后是线程(读取线程或 io_service)来处理每个客户端。

  2. 异步——意味着所有的低级操作都是非阻塞的,这里你只需要一个线程(io_service),并且你需要能够在某些事情发生时处理回调(即接受,部分写入、读取结果等)

方法 1 的优点是编码 (??) 比 2 简单得多,但是我发现 2 最灵活,实际上使用 2,默认情况下您有一个单线程应用程序(内部事件回调在主调度线程的单独线程中完成),当然2的缺点是您的处理延迟会影响下一个读/写操作......当然您可以使用方法2制作多线程应用程序,但不是副反之亦然(即带有 1 的单线程)-因此具有灵活性...

所以,从根本上说,这一切都取决于风格的选择......

编辑:为新信息更新,这很长,我懒得写代码,boost docs中有很多,我将简单描述发生了什么为了您的利益...

[主线程] - 声明一个 io_service 的实例 - 对于您要连接的每台服务器(我假设此信息在启动时可用),创建一个类(例如ServerConnection),然后在该类中,使用相同的 io_service 实例创建一个 tcp::socket从上面,在构造函数本身,调用async_connect,注意:这个调用是调度连接请求而不是真正的连接操作(这直到以后才会发生) - 一旦所有 ServerConnection 对象(以及它们各自的 async_connects 排队),在 io_service 实例上调用 run()。现在主线程被阻塞在 io_service 队列中调度事件。

[asio thread] io_service 默认有一个线程在其中调用调度事件,你不控制这个线程,要实现“多线程”程序,你可以增加 io_service 使用的线程数,但暂时坚持一个,它会让你的生活变得简单......

asio 将调用 ServerConnection 类中的方法,具体取决于计划列表中准备好的事件。您排队的第一个事件(在调用 run() 之前)是 async_connect,现在 asio 会在与服务器建立连接时回调您,通常,您将实现一个 handle_connect 方法,该方法将被调用(您通过async_connect 调用中的方法)。在handle_connect 上,您所要做的就是安排下一个请求 - 在这种情况下,您想要读取一些数据(可能来自此套接字),因此您调用 async_read_some 并传入一个函数以在有数据。完成后,主 asio 调度线程将继续调度其他准备好的事件(这可能是其他连接请求,甚至是您添加的 async_read_some 请求)。

假设您被调用是因为其中一个服务器套接字上有一些数据,这是通过 async_read_some 的处理程序传递给您的 - 然后您可以按照需要处理这些数据,但是这是最重要的一点——一旦完成,安排下一个async_read_some,这样asio 将在可用时提供更多数据。非常重要的注意事项:如果您不再安排任何请求(即退出处理程序而不排队),那么 io_service 将用完要调度的事件,并且 run()(您在主线程中调用)将结束。

现在,至于写作,这有点棘手。如果您的所有写入都是作为读取调用数据处理的一部分完成的(即在 asio 线程中),那么您无需担心锁定(除非您的 io_service 有多个线程),否则在您的写入方法中,将数据附加到缓冲区,并安排 async_write_some 请求(使用 write_handler 将在缓冲区被部分或全部写入时调用)。当 asio 处理这个请求时,它会在数据写入后调用您的处理程序,如果缓冲区中还有更多数据或没有数据,您可以选择再次调用 async_write_some,您不必费心安排写入.在这一点上,我将提到一种技术,考虑双缓冲 - 我将保留它。如果你在 io_service 之外有一个完全不同的线程并且你想编写,你必须调用 io_service::post 方法并传入一个方法来执行(在你的 ServerConnection 类中)连同数据,io_service 将然后在可能的情况下调用此方法,然后在该方法中,您可以缓冲数据,如果当前未在进行中,则可以选择调用async_write_some

现在有一件非常重要的事情你必须小心,你绝不能安排async_read_someasync_write_some,如果有已经在进行中,即假设你在一个套接字上调用了async_read_some,在这个事件被asio调用之前,你不能安排另一个async_read_some,否则你的缓冲区中会有很多废话!

一个很好的起点是您在 boost 文档中找到的 asio 聊天服务器/客户端,它显示了如何使用 async_xxx 方法。请记住这一点,所有 async_xxx 调用都会立即返回(在几十微秒内),因此没有 阻塞 操作,这一切都是异步发生的。 http://www.boost.org/doc/libs/1_39_0/doc/html/boost_asio/example/chat/chat_client.cpp,就是我所指的例子。

现在如果你发现这种机制的性能太慢并且你想要线程,你需要做的就是增加主 io_service 可用的线程数并在你的读/写中实现适当的锁定ServerConnection 中的方法,你就完成了。

【讨论】:

  • 是的,我不明白的是:我想创建一个线程来始终接收,而我的“主线程”有时会发送数据包..所以我必须为两者使用相同的套接字操作,但问题是我看到这样做不是线程安全的,为什么......那我该怎么办?
  • 尽量不要考虑线程,而是用你打算做什么来更新上面的问题,你希望这个服务器如何工作?那么可能更容易回答......
  • 我不是在做服务器,而是在做一个连接到很多服务器的客户端。无论如何,我编辑了我的问题以更好地澄清一切,Tnx。
  • Tnx 很多,但我不明白你写的某些部分。首先,我的一些写作将作为对接收到的数据的响应,而其中一些不会作为响应而只是随机写作。我不明白你提供的双缓冲的东西..我想写,所以我用ServerConnection的方法调用io_service::post(让我们称之为sendMsg)......而sendMsg应该调用async_send (为什么你使用async_write_some btw?)这将使它成为线程安全的操作?在调用async_send 之前我应该​​锁定,之后我应该解锁?接收器呢?
  • @Nim hi m8 你能看一下我最后的评论,请回复:)? Tnx
【解决方案2】:

对于异步操作,您应该为整个程序使用单个 io_service 对象。它是类的静态成员,还是在其他地方实例化取决于您。多个线程可以调用它的run方法,这在Inverse's answer.中有描述

多个线程可能会调用 io_service::run() 设置一个池 完成处理程序的线程 可以调用。这种方法还可以 与 io_service::post() 一起使用 一种执行任何计算的方法 跨线程池的任务。

请注意,所有已加入的线程 考虑 io_service 的池 等效,并且 io_service 可能 在他们之间分配工作 任意时尚。

如果您有非线程安全的处理程序,请阅读strands

链被严格定义为 事件的顺序调用 处理程序(即没有并发 调用)。使用股线允许 在多线程中执行代码 程序无需显式 锁定(例如使用互斥锁)。

【讨论】:

  • 我不同意这个答案。我发现共享io_service 的多个线程可以互相阻塞。在我的例子中,我有两个线程,都添加了一个 TCP 读取和一个截止时间计时器,所以共享的 io_service 对象有 4 个完成处理程序要处理。这导致一个读取阻塞另一个读取,即使它们没有共享套接字。该问题已通过使用每线程 io_service 对象解决,每个对象只处理一次读取和一次超时。
【解决方案3】:

io_service 为您的连接调用所有处理函数。因此,您应该运行一个线程,以便跨线程分配工作。这是一个解释io_service和线程的页面:

Threads and Boost.Asio

【讨论】:

  • 等等,我需要一个用于“接收线程”和一个用于“发送线程”?还是每个插座一个?
  • 我发现您的答案与您链接到的文档之间存在差异。文档说run() 可以被多个线程调用来建立一个线程池。据我了解,这是关于一个实例方法的多次调用,而不是关于多个实例。
猜你喜欢
  • 2011-07-05
  • 2012-04-01
  • 1970-01-01
  • 2013-06-13
  • 1970-01-01
  • 2015-10-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多