boost库使用系列2------ asio的使用案例


     在使用网络socket的时候,由于操作系统的差别很大,其底层实现会有各种不同,其中最大量使用的非阻塞的tcp和udp更是各有不同,其中Linux用epoll,BSD用kqueue,windows用完成端口等方法。这会给网络编程带来一定的麻烦,实现通讯接口层代码级兼容的方法,最常听到的是ACE,这个框架无疑把网络通讯,线程通讯等方法都封装得很好很完美,代码亦非常优秀。但其复杂程度过高,并不适合于初学者或者追求简单为美的开发人员。而libev,libevnet这类库则是跨平台方面相对麻烦。

   故,若你喜欢匕首而非砍刀的时候,boost的asio库,会是你最好的选择。

       非阻塞IO一般用途是作为服务器端的大并发连接,或者客户端非阻塞的方式运行(试想一下,客户端总不能等网络数据过来的时候,一直卡死不动吧),下面选了boost中关于客户端编程的asio例子进行分析说明。

     选用的是chat_client,其实国内大部分tcp/ip的编程教程都选用echo server/client作为首选,简单倒是简单,但往往不是正确的选择。因为网络通讯中,除了RR模式(request/reply一问一答)之外,还存在大量的不确定的谁主动发送谁接收情况。这种情况最典型的就是聊天室,你永远不知道什么时候对方会说话,也就无法判断何时该调用recv。故,chat_client其实最适合作为案例解读


       在阅读客户端代码之前,我们首先了解一下基础的几个概念,以便于理解,写着写着突然觉得有很多东西要写,如同步异步,阻塞非阻塞,线程,线程池的使用场合等,这里只能做几个假设,假设读者完全了解以上概念及应用场合,否则说起来就很罗嗦。


1.  io_service类,官网上说是你的程序和OS之间的一个接口层,不妨把它看作你的socket操作类,通过操作io_serivce对socket进行发送,接收,关闭等操作。它是线程安全的。

2. boost::asio::ip::tcp::socket,真正的socket类,你可以通过下面的方法把socket绑定再io_service中,目前用到的是一对一的使用,一对多还没探究。

boost::asio::ip::tcp::socket socket(io_service);
3. boost::system::error_code 类,错误代码类,基本上OS返回的错误都会通过这个返回错误码,不会抛出异常

4. boost::bind()这个函数是用来绑定的,如F(b,c,d)的写法,用bind就是boost::bind(F, b,c,d),看代码时记得先查查bind的用法


chat_client,看代码之前,说一下此类的流程和结构

1. 定义好接收处理缓冲和发送队列

2. 注册(回调)连接处理函数

3. 连接成功后,注册(回调)包头处理函数

4. 接收到包头后,注册(回调)包体处理函数,即收包后的处理逻辑


大家会问,都是收包的逻辑,发包的呢,别急,继续看main()函数就知道了。

1. 实例化client的实例,并连接到服务上

2. 生成一个新的线程对象,把生成的io_service::run()方法作为新线程的执行入口,把io_service作为参数传入run()中,注意,此时整个程序已经有2个不同的线程,即主线程及刚生成的线程对象t,线程t绑定了io_service::run(),则主要为收包及处理收包的作用。

3. 主线程继续执行,就会看到死循环,不断的阻塞,输入,发送,即主线程中的主逻辑,我们可以需要执行的操作都写在这里。

4. 现在我们拥有了2个独立执行的线程,一个处理主逻辑,一个等待网络中发过来的数据包。即完成了从用户角度上看的,同时进行收发工作。我们的客户端算是可用鸟:)


程序如下,chat_client.cpp,至于chat_server的代码就不贴了,server的代码到处都是,没什么需要分析的。需要测试的同志们可以到boost主页下载编译执行。chat_client与chat_server

编译的方法为

g++ chat_client.cpp -o chat_client -lboost_system -lboost_thread 

g++ chat_server.cpp -o chat_server -lboost_system -lboost_thread 


#include <cstdlib>
#include <deque>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/thread/thread.hpp>
#include "chat_message.hpp"

using boost::asio::ip::tcp;

//定义了一个发送队列
typedef std::deque<chat_message> chat_message_queue;

class chat_client
{

private:
    boost::asio::io_service& io_service_;
    tcp::socket socket_;
    chat_message read_msg_;
    chat_message_queue write_msgs_;

public:
    chat_client(boost::asio::io_service& io_service,
        tcp::resolver::iterator endpoint_iterator)
        : io_service_(io_service), socket_(io_service) {

        //构造函数时期即对连接成功进行回调注册
        boost::asio::async_connect(socket_, endpoint_iterator,
            boost::bind(&chat_client::handle_connect, this,
            boost::asio::placeholders::error));
    }

    void write(const chat_message& msg) {
        //Post向队列中投递任务,然后激活空闲线程执行任务。
        io_service_.post(boost::bind(&chat_client::do_write, this, msg));
    }

    void close() {
        io_service_.post(boost::bind(&chat_client::do_close, this));
    }

private:

    /**
    * 连接处理函数,一旦连接上来,直接会调用此函数
    */
    void handle_connect(const boost::system::error_code& error) {
        if (!error) {
            //先保证读取足够整个头部,read_msg_.data()实质上是返回一个分配了缓冲的char *指针。
            boost::asio::async_read(socket_,
                boost::asio::buffer(read_msg_.data(), chat_message::header_length),
		//注册连接成功后,回调handle_read_header()函数
                boost::bind(&chat_client::handle_read_header, this,
                boost::asio::placeholders::error));
        }
    }

    /**
    * 连接成功后,调用此函数读取头部信息
    */
    void handle_read_header(const boost::system::error_code& error) {
        if (!error && read_msg_.decode_header()) {
            //然后读取剩余的包体长度,并注册读取包体的函数,read_msg_.body()实际上是返回了一个同样分配了缓冲的char *指针
            boost::asio::async_read(socket_,
                boost::asio::buffer(read_msg_.body(), read_msg_.body_length()),
		//注册读取头部完成后,回调handle_read_body函数
                boost::bind(&chat_client::handle_read_body, this,
                boost::asio::placeholders::error));
        } else {
            //出现错误则关闭
            do_close();
        }
    }
	void handle_read_body(const boost::system::error_code& error) {
        if (!error) {
	    //注意,这2句就是收到包之后直接显示内容,这里应该替换你所需要的解包逻辑及包处理逻辑2部分。
            std::cout.write(read_msg_.body(), read_msg_.body_length());
            std::cout << "\n";
            //回调完毕后,再次注册读取头部函数,这是必须的,保证每次处理完之后,重新注册读取头部函数
            boost::asio::async_read(socket_,
                boost::asio::buffer(read_msg_.data(), chat_message::header_length),
                boost::bind(&chat_client::handle_read_header, this,
                boost::asio::placeholders::error));
        } else {
            do_close();        }
    }


    void do_write(chat_message msg) {
        bool write_in_progress = !write_msgs_.empty();
        write_msgs_.push_back(msg);
        if (!write_in_progress) {
            //把队列为空作为发送完成的标记,以保证发送出错的时候,还能按发送顺序进行
            boost::asio::async_write(socket_,
                boost::asio::buffer(write_msgs_.front().data(),
                write_msgs_.front().length()),
                boost::bind(&chat_client::handle_write, this,
                boost::asio::placeholders::error));
        }
    }

    void handle_write(const boost::system::error_code& error) {
        if (!error) {
            write_msgs_.pop_front();
            if (!write_msgs_.empty()) {
                //注册回调,若有问题会在error 上返回,一旦出错就会断开连接
                boost::asio::async_write(socket_,
                    boost::asio::buffer(write_msgs_.front().data(),
                    write_msgs_.front().length()),
                    boost::bind(&chat_client::handle_write, this,
                    boost::asio::placeholders::error));
            } else {

            }
        } else {
            do_close();
        }
    }

    void do_close() {
        socket_.close();
    }

};
int main(int argc, char* argv[]) {
    try {
        if (argc != 3) {
            std::cerr << "Usage: chat_client <host> <port>\n";
            return 1;
        }

        //定义io_service,可以把它看作是socket类
        boost::asio::io_service io_service;

        //provides the ability to resolve a query to a list of endpoints.
        tcp::resolver resolver(io_service);
        //The ip::basic_resolver_query class template 
        tcp::resolver::query query(argv[1], argv[2]);
        //An iterator over the entries produced by a resolver.
        tcp::resolver::iterator iterator = resolver.resolve(query);

        //初始化一个对象c,作为client的实例
        chat_client c(io_service, iterator);

        //生成线程对象t, 绑定在io_service::run方法中, 把io_service作为桥梁在2个进程间进行通讯
	//目的是用另外一个线程对socket进行非阻塞回调的接收及处理,不影响主线程操作。
        boost::thread t(boost::bind(&boost::asio::io_service::run, &io_service));

        char line[chat_message::max_body_length + 1];
	//注意,下面的while循环则应该是程序的主循环,也应该封装成一个函数供调用为好
        while (std::cin.getline(line, chat_message::max_body_length + 1)) {
            using namespace std; // For strlen and memcpy.
            chat_message msg;
            msg.body_length(strlen(line));
            memcpy(msg.body(), line, msg.body_length());
            //构造包头(主要是长度,这是最简单实用的方法)
            msg.encode_header();
            //直接写入数据到socket
            c.write(msg);
        }

        c.close();
        t.join();
    } catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << "\n";
    }
    return 0;
}



补充:注意事项,由于asio库需要跨平台,某些OS是通过启动额外线程来实现非阻塞的。故有2点要求

1. 不要直接调用封装层内部的代码(asio)

2. 不要使用信号(这点必须注意)


后记:asio确实相当不错,支持TCP,UDP,ICMP,TIMER,串口和SSL等,对我来说已经足够使用了。若有问题留言即可。


下篇写点啥呢?



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值