boost.asio服务器使用io_service作为work pool

2023-10-29

使用io_service作为处理工作的work pool,可以看到,就是通过io_service.post投递一个Handler到io_service的队列,Handler在这个io_service.run内部得到执行,有可能你会发现,io_services.dispatch的接口也和io_service.post一样,但不同的是它是直接调用而不是经过push到队列然后在io_services.run中执行,而在这个示例当中,显然我们需要把工作交到另一个线程去完成,这样才不会影响网络接收线程池的工作以达到高效率的接收数据,这种设计与前面的netsever其实相同,这就是典型的Half Sync/Half Async。二者的区别就是netsever自己实现了工作队列,而不是直接使用io_service,这种设计实际上在win下是使用了iocp作为工作队列。

不过我更倾向于前一种设计,因为那样做,代码一切都在自己的掌握中,而io_service则是经过许多封装代码,并且本身设计只是用于处理网络完成事件的。

无论如何使用,都能感觉到使用boost.asio实现服务器,不仅是一件非常轻松的事,而且代码很漂亮,逻辑也相当清晰,这点上很不同于ACE。

 
 
  1. #include <stdio.h> 
  2. #include <cstdlib> 
  3. #include <iostream> 
  4. #include <boost/thread.hpp> 
  5. #include <boost/aligned_storage.hpp> 
  6. #include <boost/array.hpp> 
  7. #include <boost/bind.hpp> 
  8. #include <boost/enable_shared_from_this.hpp> 
  9. #include <boost/noncopyable.hpp> 
  10. #include <boost/shared_ptr.hpp> 
  11. #include <boost/asio.hpp> 
  12.  
  13. using boost::asio::ip::tcp; 
  14.  
  15. class handler_allocator 
  16.     : private boost::noncopyable 
  17. public
  18.     handler_allocator() 
  19.         : in_use_(false
  20.     { 
  21.     } 
  22.  
  23.     void* allocate(std::size_t size) 
  24.     { 
  25.         if (!in_use_ && size < storage_.size) 
  26.         { 
  27.             in_use_ = true
  28.             return storage_.address(); 
  29.         } 
  30.         else 
  31.         { 
  32.             return ::operator new(size); 
  33.         } 
  34.     } 
  35.  
  36.     void deallocate(void* pointer) 
  37.     { 
  38.         if (pointer == storage_.address()) 
  39.         { 
  40.             in_use_ = false
  41.         } 
  42.         else 
  43.         { 
  44.             ::operator delete(pointer); 
  45.         } 
  46.     } 
  47.  
  48. private
  49.     // Storage space used for handler-based custom memory allocation. 
  50.     boost::aligned_storage<1024> storage_; 
  51.  
  52.     // Whether the handler-based custom allocation storage has been used. 
  53.     bool in_use_; 
  54. }; 
  55.  
  56. template <typename Handler> 
  57. class custom_alloc_handler 
  58. public
  59.     custom_alloc_handler(handler_allocator& a, Handler h) 
  60.         : allocator_(a), 
  61.         handler_(h) 
  62.     { 
  63.     } 
  64.  
  65.     template <typename Arg1> 
  66.     void operator()(Arg1 arg1) 
  67.     { 
  68.         handler_(arg1); 
  69.     } 
  70.  
  71.     template <typename Arg1, typename Arg2> 
  72.     void operator()(Arg1 arg1, Arg2 arg2) 
  73.     { 
  74.         handler_(arg1, arg2); 
  75.     } 
  76.  
  77.     friend void* asio_handler_allocate(std::size_t size, 
  78.         custom_alloc_handler<Handler>* this_handler) 
  79.     { 
  80.         return this_handler->allocator_.allocate(size); 
  81.     } 
  82.  
  83.     friend void asio_handler_deallocate(void* pointer, std::size_t /*size*/
  84.         custom_alloc_handler<Handler>* this_handler) 
  85.     { 
  86.         this_handler->allocator_.deallocate(pointer); 
  87.     } 
  88.  
  89. private
  90.     handler_allocator& allocator_; 
  91.     Handler handler_; 
  92. }; 
  93.  
  94. // Helper function to wrap a handler object to add custom allocation. 
  95. template <typename Handler> 
  96. inline custom_alloc_handler<Handler> make_custom_alloc_handler( 
  97.     handler_allocator& a, Handler h) 
  98.     return custom_alloc_handler<Handler>(a, h); 
  99.  
  100. /// A pool of io_service objects. 
  101. class io_service_pool 
  102.     : private boost::noncopyable 
  103. public
  104.     /// Construct the io_service pool. 
  105.     explicit io_service_pool(std::size_t pool_size) : next_io_service_(0) 
  106.     { 
  107.         if (pool_size == 0) 
  108.             throw std::runtime_error("io_service_pool size is 0"); 
  109.  
  110. // Give all the io_services work to do so that their run() functions will not 
  111. // exit until they are explicitly stopped. 
  112.         for (std::size_t i = 0; i < pool_size; ++i) 
  113.         { 
  114.             io_service_ptr io_service(new boost::asio::io_service); 
  115.             work_ptr work(new boost::asio::io_service::work(*io_service)); 
  116.             io_services_.push_back(io_service); 
  117.             work_.push_back(work); 
  118.         } 
  119.     } 
  120.  
  121. // Run all io_service objects in the pool. 
  122.     void run() 
  123.     { 
  124. // Create a pool of threads to run all of the io_services. 
  125.         std::vector<boost::shared_ptr<boost::thread> > threads; 
  126.         for (std::size_t i = 0; i < io_services_.size(); ++i) 
  127.         { 
  128.             boost::shared_ptr<boost::threadthread(new boost::thread
  129. boost::bind(&boost::asio::io_service::run, io_services_[i]))); 
  130.             threads.push_back(thread); 
  131.         } 
  132.  
  133. // Wait for all threads in the pool to exit. 
  134.         for (std::size_t i = 0; i < threads.size(); ++i) 
  135.             threads[i]->join(); 
  136.     } 
  137.  
  138. // Stop all io_service objects in the pool. 
  139.     void stop() 
  140.     { 
  141. // Explicitly stop all io_services. 
  142.         for (std::size_t i = 0; i < io_services_.size(); ++i) 
  143.             io_services_[i]->stop(); 
  144.     } 
  145.  
  146. // Get an io_service to use. 
  147.     boost::asio::io_service& get_io_service() 
  148.     { 
  149. // Use a round-robin scheme to choose the next io_service to use. 
  150.         boost::asio::io_service& io_service = *io_services_[next_io_service_]; 
  151.         ++next_io_service_; 
  152.         if (next_io_service_ == io_services_.size()) 
  153.             next_io_service_ = 0; 
  154.         return io_service; 
  155.     } 
  156.  
  157. private
  158.     typedef boost::shared_ptr<boost::asio::io_service> io_service_ptr; 
  159.     typedef boost::shared_ptr<boost::asio::io_service::work> work_ptr; 
  160.  
  161.     /// The pool of io_services. 
  162.     std::vector<io_service_ptr> io_services_; 
  163.  
  164.     /// The work that keeps the io_services running. 
  165.     std::vector<work_ptr> work_; 
  166.  
  167.     /// The next io_service to use for a connection. 
  168.     std::size_t next_io_service_; 
  169. }; 
  170.  
  171. class session 
  172.     : public boost::enable_shared_from_this<session> 
  173. public
  174.     session(boost::asio::io_service& work_service
  175. , boost::asio::io_service& io_service) 
  176.         : socket_(io_service) 
  177.         , io_work_service(work_service) 
  178.     { 
  179.     } 
  180.  
  181.     tcp::socket& socket() 
  182.     { 
  183.         return socket_; 
  184.     } 
  185.  
  186.     void start() 
  187.     { 
  188.         socket_.async_read_some(boost::asio::buffer(data_), 
  189.             make_custom_alloc_handler(allocator_, 
  190.             boost::bind(&session::handle_read, 
  191.             shared_from_this(), 
  192.             boost::asio::placeholders::error, 
  193.             boost::asio::placeholders::bytes_transferred))); 
  194.     } 
  195.  
  196.     void handle_read(const boost::system::error_code& error, 
  197.         size_t bytes_transferred) 
  198.     { 
  199.         if (!error) 
  200.         { 
  201.             boost::shared_ptr<std::vector<char> > buf(new std::vector<char>); 
  202.  
  203.             buf->resize(bytes_transferred); 
  204.             std::copy(data_.begin(), data_.begin() + bytes_transferred, buf->begin()); 
  205.             io_work_service.post(boost::bind(&session::on_receive
  206. , shared_from_this(), buf, bytes_transferred)); 
  207.  
  208.             socket_.async_read_some(boost::asio::buffer(data_), 
  209.                 make_custom_alloc_handler(allocator_, 
  210.                 boost::bind(&session::handle_read, 
  211.                 shared_from_this(), 
  212.                 boost::asio::placeholders::error, 
  213.                 boost::asio::placeholders::bytes_transferred))); 
  214.         } 
  215.     } 
  216.  
  217.     void handle_write(const boost::system::error_code& error) 
  218.     { 
  219.         if (!error) 
  220.         { 
  221.         } 
  222.     } 
  223.  
  224.     void on_receive(boost::shared_ptr<std::vector<char> > buffers
  225. size_t bytes_transferred) 
  226.     { 
  227.         char* data_stream = &(*buffers->begin()); 
  228. // in here finish the work. 
  229.         std::cout << "receive :" << bytes_transferred << " bytes." << 
  230. "message :" << data_stream << std::endl; 
  231.     } 
  232.  
  233. private
  234. // The io_service used to finish the work. 
  235.     boost::asio::io_service& io_work_service; 
  236.  
  237. // The socket used to communicate with the client. 
  238.     tcp::socket socket_; 
  239.  
  240. // Buffer used to store data received from the client. 
  241.     boost::array<char, 1024> data_; 
  242.  
  243. // The allocator to use for handler-based custom memory allocation. 
  244.     handler_allocator allocator_; 
  245. }; 
  246.  
  247. typedef boost::shared_ptr<session> session_ptr; 
  248.  
  249. class server 
  250. public
  251.     server(short port, std::size_t io_service_pool_size) 
  252.         : io_service_pool_(io_service_pool_size) 
  253.         , io_service_work_pool_(io_service_pool_size) 
  254.         , acceptor_(io_service_pool_.get_io_service(), tcp::endpoint(tcp::v4(), port)) 
  255.     { 
  256.         session_ptr new_session(new session(io_service_work_pool_.get_io_service()
  257. , io_service_pool_.get_io_service())); 
  258.         acceptor_.async_accept(new_session->socket(), 
  259.             boost::bind(&server::handle_accept, this, new_session, 
  260.             boost::asio::placeholders::error)); 
  261.     } 
  262.  
  263.     void handle_accept(session_ptr new_session, 
  264.         const boost::system::error_code& error) 
  265.     { 
  266.         if (!error) 
  267.         { 
  268.             new_session->start(); 
  269.             new_session.reset(new session(io_service_work_pool_.get_io_service()
  270. , io_service_pool_.get_io_service())); 
  271.             acceptor_.async_accept(new_session->socket(), 
  272.                 boost::bind(&server::handle_accept, this, new_session, 
  273.                 boost::asio::placeholders::error)); 
  274.         } 
  275.     } 
  276.  
  277.     void run() 
  278.     { 
  279.         io_thread_.reset(new boost::thread(boost::bind(&io_service_pool::run
  280. , &io_service_pool_))); 
  281.         work_thread_.reset(new boost::thread(boost::bind(&io_service_pool::run
  282. , &io_service_work_pool_))); 
  283.     } 
  284.  
  285.     void stop() 
  286.     { 
  287.         io_service_pool_.stop(); 
  288.         io_service_work_pool_.stop(); 
  289.  
  290.         io_thread_->join(); 
  291.         work_thread_->join(); 
  292.     } 
  293.  
  294. private
  295.     boost::shared_ptr<boost::thread> io_thread_; 
  296.     boost::shared_ptr<boost::thread> work_thread_; 
  297.     io_service_pool io_service_pool_; 
  298.     io_service_pool io_service_work_pool_; 
  299.     tcp::acceptor acceptor_; 
  300. }; 
  301.  
  302. int main(int argc, char* argv[]) 
  303.     try 
  304.     { 
  305.         if (argc != 2) 
  306.         { 
  307.             std::cerr << "Usage: server <port>/n"
  308.             return 1; 
  309.         } 
  310.  
  311.         using namespace std; // For atoi. 
  312.         server s(atoi(argv[1]), 10); 
  313.  
  314.         s.run(); 
  315.  
  316.         getchar(); 
  317.  
  318.         s.stop(); 
  319.     } 
  320.     catch (std::exception& e) 
  321.     { 
  322.         std::cerr << "Exception: " << e.what() << "/n"
  323.     } 
  324.  
  325.     return 0; 
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

boost.asio服务器使用io_service作为work pool 的相关文章

  • 更新写入 java 文本文件的对象

    将 Java 对象或列表写入文本文件是可以的 但我想知道如何更新或重写以前写入的对象而不再次写入对象 例如 假设有一个 java util List 有一组对象 然后将该列表写入文本文件 然后稍后该文件将被再次读取并从列表中获取所有对象 然
  • 尝试从输入流检索文本时应用程序挂起

    情况 我确实查看了您的代码 正如我怀疑的那样 您的问题与您发布的代码完全无关 您的 GUI 完全忽略 Swing 线程规则 并在主 Swing 事件线程 称为Event Dispatch T螺纹或EDT 由于该线程负责所有 Swing 绘图
  • boost::serialization 序列化期间内存消耗较高

    正如主题所示 在将大量数据序列化到文件时 我遇到了 boost serialization 的一个小问题 问题在于应用程序序列化部分的内存占用量大约是要序列化的对象内存的 3 到 3 5 倍 值得注意的是 我拥有的数据结构是基类指针和指向该
  • aSmack 即服务

    基本上我有一个运行整个项目的主类 该代码运行完美 尽管一旦应用程序失去焦点 它就会变得不活动 我想知道如何将其变成一项服务 一个会在启动时启动的 该应用程序将是一个用于通知的单向消息系统 IE 桌面客户端 gt Openfire 服务器 g
  • 如何通过pthreads管理两个或多个消费者?

    我有一个正在寻求解决的通用问题 即从标准输入或常规文件流发送到应用程序的二进制数据块 应用程序又将二进制数据转换为文本 使用线程 我想在将文本传输到下一个应用程序之前对其进行处理 该应用程序会进一步修改该文本 依此类推 作为一个简单的测试用
  • Xcode 4 上的 Boost 库静态链接

    我在 OS X 上使用 Xcode 使用 Boost 库 Boost 使用 macports 安装在我的系统上 通过将我需要的 3 个 boost 库 例如 libboost thread mt a 添加到 Targets Link Bin
  • 存储库和服务层之间的区别

    我查看了一些相关的问题 但仍然没有看到存储库和服务层之间有太大区别 所以给出的例子我想它应该看起来像这样 如果不是请告诉我为什么 public interface ProductRepository extends CrudReposito
  • 本地计算机上的服务启动然后停止,某些服务如果没有被其他服务或程序使用则自动停止

    我创建了一个示例 Windows 服务并成功安装了我的服务 但是在要启动服务时 我收到以下错误 本地计算机上的此服务启动然后停止 如果其他服务或程序未使用某些服务 则会自动停止 我的配置文件代码
  • 有没有办法查看 OSGi 应用程序中注册的服务?

    我有一个运行 Equinox 的 OSGi 应用程序 我想查看该应用程序提供的服务 我怎样才能做到这一点 从 gogo shell 类型 inspect cap service 这将显示所有捆绑包注册的所有服务 如果您想显示特定捆绑包的服务
  • 如何从 haskell 中的 IOError 获取 errno?

    我在 haskell 平台上 GHC 6 12 1 作为 apt get 安装在 Debian Squeeze 上 鉴于我需要在与最初引发它的线程不同的线程上使用它 如何从 IOError 中获取底层 errno 我需要这个的原因是因为我正
  • 返回一个需要由智能指针保存的“指针”

    我有一个项目 我想更多地使用智能指针 总的来说 我在这个目标上取得了成功 然而 我遇到了一些我不确定 最佳实践 是什么的事情 基本上我想从函数返回一个 指针 但是require用户将其放在智能指针中 不仅如此 我不想强 制使用特定的智能指针
  • 如何为 Visual Studio 代码设置 boost

    所以我基本上构建了整个boost库并将它们放在C boost include boost 1 73中 但我有一种感觉 在使用这个库之前我必须包含更多步骤 我一直遵循本指南 直到将其设置为 Visual Studio 代码 所以现在我正在互联
  • SC创建binpath错误

    我正在尝试在 PowerShell 中运行以下命令 sc create StrongSwan binpath C Users Kanishk Desktop Strong Strong stronswan strongswan 5 6 3
  • Autowired spring bean 不是代理

    我正在开发一个连接到 MySQL 数据库的非常小的应用程序 我正在尝试创建表记录 但收到 没有正在进行的事务 我已经准备好了所有正确的东西 服务接口 MyService 及其实现 MyServiceImpl 我已经用 Service 注释了
  • 为什么 .NET 异步等待文件复制比同步 File.Copy() 调用消耗更多 CPU?

    为什么下面的代码会产生 public static class Program public static void Main params string args var sourceFileName C Users ehoua Desk
  • 无法删除临时文件夹(有时)

    当我启动应用程序时 我创建一个临时文件夹 public static File createTempDir String name throws IOException File tempDir File createTempFile na
  • 如何从标准输入读取一行,阻塞直到找到换行符?

    我试图从命令行的标准输入一次读取任意长度的一行 我不确定是否能够包含 GNU readline 并且更喜欢使用库函数 我读过的文档表明getline应该可以工作 但在我的实验中它不会阻塞 我的示例程序 include
  • 使用taskkill停止Windows服务

    我需要帮助来使用 C 终止 Windows 服务 现在要终止该服务 请使用以下选项 从命令 sc queryex ServiceName 发现后PID服务的 taskkill pid 1234 exemple f 为了便于阅读 但如果您明白
  • Boost + Visual Studio 2010 + Windows 平台 SDK 7.1

    有人可以告诉我 bjam 的命令行开关或其他可以使用新的 Windows Platform SDK 7 1 工具链使用 VS2010 进行 boost 编译的东西吗 您可以在普通的视觉工作室项目中设置该选项 默认值是 v100 是平台 7
  • 使用 boost 异步发送和接收自定义数据包?

    我正在尝试使用 boost 异步发送和接收自定义数据包 根据我当前的实现 我有一些问题 tcpclient cpp include tcpclient h include

随机推荐

  • 带有加密功能的 SQLite Qt 插件

    Qt 已经内置了一个 SQLite 数据库 方便我们开发桌面应用 但是这个 SQLite 是官方提供的开源版本 这意味着这个版本的 SQLite 实际是没有加密功能的 对于一般的桌面应用 数据库加密有时是比较重要的 特别是当你需要对所存储的
  • Java -NIO简介

    Java NIO 在1 4版本之前 Java IO类库是阻塞IO 从1 4版本开始 引进了新的异步IO库 被称为Java New IO类库 简称为JAVA NIO New IO类库的目标 就是要让Java支持非阻塞IO 基于这个原因 更多的
  • Altium Design圆弧走线与敷铜

    Altium Design圆弧走线与敷铜 基本步骤 设置AD优选项 设置输入法 美国键盘 打开Windows设置 选择时间和语言 点击语言 添加首选语言 并安装English 美国 语言包 完成安装 切换输入法 基本步骤 对于我们大部分微软
  • 硬件设计检查事项

    1 原理图逻辑框图 2 原理图电气连接 3 原理图检查 4 PCB外框 5 PCB元件布局 6 PCB布线 7 PCB覆铜 8 DRC校验 9 BGA盘中孔同心 10 元件位号 标注 11 有源期间极性 标注 12 名称 版本 日期 人员
  • splint的安装与使用

    简介 splint是一个GNU免费授权的 Lint程序 是一个动态检查C语言程序安全弱点和编写错误的程序 Splint会进行多种常规检查 包括未使用的变量 类型不一致 使用未定义变量 无法执行的代码 忽略返回值 执行路径未返回 无限循环等错
  • 前端开发者必须知道的 10 个 GitHub 仓库

    内容整理自 ravikmmr 的 Twitter Thread 1 Developer roadmap 初学者如果想学习前端开发 但是不知道从何学起 推荐查看此仓库 你可以获得有关开发的所有学习路线 笔者在之前的文章中对其进行过翻译 2 F
  • Python构建SVM分类器(线性)

    1 SVM建立线性分类器 SVM用来构建分类器和回归器的监督学习模型 SVM通过对数学方程组的求解 可以找出两组数据之间的最佳分割边界 2 准备工作 我们首先对数据进行可视化 使用的文件来自学习书籍配套管网 首先增加以下代码 import
  • 腾讯云阿里云服务器被打进黑洞怎么办

    当腾讯云腾讯云服务器被打进黑洞了我们该怎么办 首先我们要知道以下的这些 黑洞 是什么 黑洞是指服务器受攻击流量超过本机房黑洞阈值时 云计算服务商屏蔽服务器的外网访问 当服务器进入黑洞一段时间后 如果系统监控到攻击流量停止 黑洞会自动解封 进
  • 程序员微信名昵称_微信名字大全

    微信名字 好听的微信名字大全 只求一份安定 无可置疑 吥 恠侑嗳 丶演绎悲伤 一生承诺 简单灬爱 流年灬未亡 舞动D 灵魂 别在我面前犯贱 没有背景丶只有背影 乂日光倾城 丶猫猫er 雪花 飞舞 在哪跌倒 就在哪躺下 淡抹丶悲伤 稀饭你的笑
  • 考研算法题:最短边数最短路

    题目 一个图有很多条最短路 求所有最短路里面的边数最少的最短路的边数 思路1 先求最短路 然后BFS倒推寻找最短边数的最短路的边数 找到直接返回cnt值 include
  • 机器学习- CS 760 Machine Learning

    代码后台私我
  • 【Spring】ERR_INCOMPLETE_CHUNKED_ENCODING 200 (OK) 问题解决

    1 概述 转载 ERR INCOMPLETE CHUNKED ENCODING 200 OK 问题解决 我是在做这个项目的时候遇到这个报错 Spring Spring 网络原因导致日志下载失败 2 简述 浏览器调用接口报错 net ERR
  • Python使用threading.Timer实现执行可循环的定时任务

    前言 Python中使用threading Timer执行定时任务时 执行任务是一次性的 类似于JS中的setTimeout方法 我们对其在封装 改造成可循环的定时器 类似于JS中setInterval方法的效果 值得注意的是 thread
  • 关于distinct——去除重复记录

    distinct译为 不同的 有区别的 在SQL语句中表示去除重复记录的意思 举例 在员工表emp中查询所有的工作岗位 分析 在员工表中的工作岗位字段下有重复的工作岗位 我们在查询的时候就希望将重复的工作岗位显示出一个来就行 在不使用关键字
  • 【模块介绍】6×6矩阵键盘(硬件部分和扫描方式)

    目录 概述 原理图 扫描方式 扫描法 单个按键按下 多个按键按下 行反转法 图解 成品 概述 矩阵键盘非常常见 就是利用键盘组成矩阵来减少IO口的使用 做成6 6的矩阵键盘可以使用12个IO口读取36个按键 矩阵键盘的优势在于成本低 无需其
  • Java中switch case的使用

    Java switch case语句 switch case用来判断一个变量与一系列值中某个值是否相等 每个值称为一个分支 switch case规则 switch语句中变量类型可以是 byte short int char 从Java S
  • 网上疯传的《阿里Java架构师成长之路》!,网友瞬间沸腾了

    工作1 5年开发经验 当你们提出涨工资的时候 或者要offer的时候底气怎么样 是不是底气十足 不给涨工资就辞职 是不是有自信提出来主管 或者是项目经理都能同意 他们相当设法把你留住 如果这样你才是成功 什么技术都没有何谈工资 给你分析一下
  • Algo_math、判断两圆包含

    给定一个圆A X Y 圆心 R为半径 圆B x y 圆心 r为半径 判断 圆B 是否在 圆A 的内部 上图 则不包含 等价于 绿线长度 lt R X x
  • Java面试题详解:什么是面向对象编程

    参考答案 一般我们可以围绕面向对象的几个特征去展开 封装 继承 抽象 多态 个人理解 面向对象编程有点类似于数学建模 一般用于解决一个复杂的问题 解决这个问题通常涉及到多个物理或抽象概念 并且它们之间会有各种关系及交互行为 面向对象编程其实
  • boost.asio服务器使用io_service作为work pool

    使用io service作为处理工作的work pool 可以看到 就是通过io service post投递一个Handler到io service的队列 Handler在这个io service run内部得到执行 有可能你会发现 io