asio学习笔记5

该写点一部网络通信的东西了..前面也说过,asio的异步网络函数和同步的差别不大,只是异步和同步的思想的差别。如果前面的同步的函数都搞懂了,那只需要转换一下思维方式就可以了——把要做的事提交给io_service,在run中执行他们,在事件完成的通知中进行下一步的操作。继续上代码:

// asio_sample.cpp : 定义控制台应用程序的入口点。
//

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <iostream>

boost::asio::io_service ios;
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), 1234);
boost::asio::ip::tcp::acceptor acceptor(ios, endpoint);

char data[1024];

void handle_read(boost::asio::ip::tcp::socket* sock,
    const boost::system::error_code& error,
    size_t bytes_transferred);

void handle_write(boost::asio::ip::tcp::socket* sock,
    const boost::system::error_code& error,
    size_t bytes_transferred)
{
    if (error)
    {
        std::cout << "write发生错误:" << error.message() << std::endl;
        delete sock;
        return;
    }

    sock->async_read_some(boost::asio::buffer(data),
        boost::bind(handle_read, sock,
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred));
}

void handle_read(boost::asio::ip::tcp::socket* sock,
    const boost::system::error_code& error,
    size_t bytes_transferred)
{
    if (error)
    {
        std::cout << "read发生错误:" << error.message() << std::endl;
        delete sock;
        return;
    }

    boost::asio::async_write(*sock, boost::asio::buffer(data, bytes_transferred),
        boost::bind(handle_write, sock,
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred));
}

void session(boost::asio::ip::tcp::socket* sock,
    const boost::system::error_code& error)
{
    if (error)
    {
        delete sock;
    }
    else
    {
        std::cout << "有客户端连接" << std::endl;

        sock->async_read_some(boost::asio::buffer(data),
            boost::bind(handle_read, sock,
            boost::asio::placeholders::error,
            boost::asio::placeholders::bytes_transferred));
    }

    boost::asio::ip::tcp::socket* s = new boost::asio::ip::tcp::socket(ios);
    acceptor.async_accept(*s, boost::bind(session, s,
        boost::asio::placeholders::error));
}

int main(int argc, char* argv[])
{
    boost::asio::ip::tcp::socket* s = new boost::asio::ip::tcp::socket(ios);
    acceptor.async_accept(*s, boost::bind(session, s, boost::asio::placeholders::error));

    ios.run();
    return 0;
}

跟同步的那个echo server是一样的东西功能,就不截图了。而且为了容易看清条理实现的比较丑陋。可以看出来这里并没有使用线程,却实现了跟同步多线程的echo server相同的功能——可以同时跟多个客户端通信。下面就详细解释一下代码。

为了使用方便我把boost::asio::io_service、boost::asio::ip::tcp::endpoint和boost::asio::ip::tcp::acceptor定义成了全局变量,这三个类的使用和同步程序中的一模一样就不多说了。下面依然是每个boost::asio::ip::tcp::socket代表收到的一个请求,不同的是这次没有开新的线程,而是直接在async_accept的handler(也就是session函数)中进行的处理,在session函数中,我们先发了一个异步读取数据的“任务”(sock->async_read_some)给io_service,然后又发了一个异步接收请求的“任务”(acceptor.async_accept)给io_service,因为只是发任务,函数式立即返回的,因此我们可以认为这两个任务在同时进行。发读取数据的任务是为了接受客户端发来的数据,发accept任务是为了等待其他的客户端的连接。在同步的程序中,不开多个线程是不可能做到的,因为我们如果先执行read任务的话,而客户端一直不发送数据,我们会一直阻塞在read函数里面;accept也是同样的道理。

发了这两个任务之后,我们就进入了两个“循环”中,不明白这两个循环怎么来的请仔细看这里,一个循环一直等待客户的连接,另一个循环则重复执行着读取数据->返回给客户端->继续读取->返回给客户端....直到客户端下线或者其他错误发生时,就不再发异步任务,循环也就停止了。这样就实现了一个线程,多个循环。

应该很容易就能看出其实异步的网络函数和同步的差别不大,不过是多了个完成后的回调,结果都在回调中得知。你的回调需要哪方面的结果,就bind上boost::asio::placeholders命名空间下的对应的项。其中比较常用的是boost::asio::placeholders::error和boost::asio::placeholders::bytes_transferred,分别表示异步事件执行是否出错以及传送的数据的字节数。其他的项我也没用过,以后用到再说。

另外说一下异步的几个“坑”,如果被坑到会是很蛋疼的事。如下面的示例:

//异步读取完成的handler
void handle_read(boost::asio::ip::tcp::socket* sock,size_t size,
    const boost::system::error_code& error,
    size_t bytes_transferred);

......
//某个函数中
    size_t size = 0;
    boost::asio::async_read(*sock,boost::asio::buffer(&size,sizeof(size)),
        boost::bind(handle_read,sock,size,
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred));
.......

这段代码有两个严重的错误:

1.要确定异步读取的目标缓冲区的存活时间不能比事件的完成时间短,不管是异步读取还是异步写入。因为事件完成几乎总是在发起异步事件的函数(调用 async_read或async_write的函数)完成之后才会触发,这时候上面代码中的size已经析构了,异步读取或写入会写入到一个不存在的 buffer中,局部变量是放在栈上,这时这个变量的位置可能已经变成了另一个变量了,这肯定不是我们想要的结果。

2.不要把异步读取写入的目标bind到读取完成的handler上。因为bind实际上是吧size的值(0)保存了下来,所以,不管读取到的实际大小是多少,handler中始终会得到大小为0;

3.还有一个坑,就不写代码了。在同一个socket句柄上,不可以同时有多于一个异步读取或异步写入事件,必须等一个完成才能发下一个。原因很简单,一起读的话来了数据算谁的?一起写的话数据都写混乱了。

客户端就不写了,明白了这个客户端也就很好理解了。下一次写写更好的管理异步连接的方法,一堆全局变量,socket指针到处传还要自己手动delete的代码太不雅观了。

标签:  boost,  asio,  异步,  async,  网络编程


asio学习笔记5