该写点一部网络通信的东西了..前面也说过,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, 网络编程