Boost.Asio c++ 网络编程翻译(11)

来源:互联网 发布:无忧seo博客 编辑:程序博客网 时间:2024/04/29 06:32
*_at方法
这些方法在一个流上面做随机存取操作。你来指定read和write操作从什么地方开始(offset):
async_read_at(stream, offset, buffer [, completion], handler):这个方法在一个指定的流上从offset处开始执行一个异步的read操作,当操作结束时,他会调用handler。handler的格式为:void handler(const boost::system::error_code&  err, size_t bytes);。buffer可以是普通的wrapper()包装或者streambuf方法。如果你指定一个completion方法,它会在每次read操作成功之后调用,然后告诉Boost.Asio async_read_at操作已经完成(如果没有,则继续读取)。它的格式为:size_t  completion(const boost::system::error_code& err, size_t bytes);。当completion方法返回0时,我们认为read操作完成了;如果返回一个非零值。它代表了下一次调用流的async_read_some_at方法的最大读取字节数。
async_write_at(stream, offset, buffer [, completion], handler):这个方法执行一个异步的write操作。参数的意义和async_read_at是一样的
read_at(stream, offset, buffer [, completion]):这个方法在一个执行的流上,指定的offset处开始read。参数的意义和async_read_at是一样的
write_at(stream, offset, buffer [, completion]):这个方法在一个执行的流上,指定的offset处开始write。参数的意义和async_read_at是一样的
这些方法不支持套接字。它们用来处理流的随机访问;也就是说,流是可以随机访问的。套接字显然不是这样(套接字是不可返回的)
下面这个例子告诉你怎么从一个文件偏移为256的位置读取128个字节:
io_service service;
int main(int argc, char* argv[]) {
HANDLE file = ::CreateFile("readme.txt", GENERIC_READ, 0, 0,
OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
0);
windows::random_access_handle h(service, file);
streambuf buf;
read_at(h, 256, buf, transfer_exactly(128));
std::istream in(&buf);
std::string line;
std::getline(in, line);
std::cout << "first line: " << line << std::endl;
}
异步编程
这个部分对你在进行异步编程时可能碰到的一些问题进行了深入的探究。再读了一遍之后,我建议你在接下来学习这本书的时候,经常回过头来再读读,从而增强你对这部分的理解。
异步的需求
就像我之前所说的,同步编程比异步编程简单很多。这是因为,线性的思考是很简单的(调用A,调用A结束,调用B,调用B结束,然后继续,这是以事件处理的方式来思考)。在后面的你会碰到这种情况,比如:五件事情,你不知道它们执行的顺序,也不知道他们是否会执行!
尽管异步编程更难,但是你会更倾向于它,比如:写一个需要处理很多并发访问的服务端。并发访问越多,异步编程就比同步编程越简单。
假设:你有一个软件处理1000个并发访问,每个信息都从客户端发给服务端,然后再返回给客户端,以‘\n’结尾。
同步方式的代码,1个线程:
using namespace boost::asio;
struct client {
ip::tcp::socket sock;
char buff[1024]; // each msg is at maximum this size
int already_read; // how much have we already read?
};
std::vector<client> clients;
void handle_clients() {
while ( true)
for ( int i = 0; i < clients.size(); ++i)
if ( clients[i].sock.available() ) on_read(clients[i]);
}
void on_read(client & c) {
int to_read = std::min( 1024 - c.already_read, c.sock.
available());
c.sock.read_some( buffer(c.buff + c.already_read, to_read));
c.already_read += to_read;
if ( std::find(c.buff, c.buff + c.already_read, '\n') < c.buff +
c.already_read) {
int pos = std::find(c.buff, c.buff + c.already_read, '\n') -
c.buff;
std::string msg(c.buff, c.buff + pos);
std::copy(c.buff + pos, c.buff + 1024, c.buff);
c.already_read -= pos;
on_read_msg(c, msg);
}
}
void on_read_msg(client & c, const std::string & msg) {
// analyze message, and write back
if ( msg == "request_login")
c.sock.write( "request_ok\n");
else if ...
}
有一件事情是你在任何服务端(和任何基于网络的软件)都需要避免的,就是代码不再响应。在我们例子里,我们需要handle_ clients()方法尽可能少的阻塞。如果放在在任何点上阻塞,任何进来的信息都需要等待方法解除阻塞然后再去处理它们。
为了保持响应,我们只在一个套接字有数据的时候我们才读,也就是说,if ( clients[i].sock.available() ) on_read(clients[i])。在on_read时,我们只读可用的;调用read_until(c.sock, buffer(...),  '\n')会是一个非常糟糕的选择,因为直到我们从一个选定的客户端读取了完整的消息之前,它都会是阻塞的(我们永远不知道它什么时候会读取到完整的消息)
这里的瓶颈就是on_read_msg()方法;当它执行时,所以进来的消息都在等待。一个良好的on_read_msg()方法实现会保证这种情况基本不会发生,但是它还是会发生(有些时候向一个套接字写入数据时,当他的缓冲区满了,它会被阻塞)
同步方式的代码,10个线程
using namespace boost::asio;
struct client {
// ... same as before
bool set_reading() {
boost::mutex::scoped_lock lk(cs_);
if ( is_reading_) return false; // already reading
else { is_reading_ = true; return true; }
}
void unset_reading() {
boost::mutex::scoped_lock lk(cs_);
is_reading_ = false;
}
private:
boost::mutex cs_;
bool is_reading_;
};
std::vector<client> clients;
void handle_clients() {
for ( int i = 0; i < 10; ++i)
boost::thread( handle_clients_thread);
}
void handle_clients_thread() {
while ( true)
for ( int i = 0; i < clients.size(); ++i)
if ( clients[i].sock.available() )
if ( clients[i].set_reading()) {
on_read(clients[i]);
clients[i].unset_reading();
}
}
void on_read(client & c) {
// same as before
}
void on_read_msg(client & c, const std::string & msg) {
// same as before
}
为了使用多线程,我们需要对它进行同步,这就是set_reading()和set_unreading()所做的。set_reading()方法非常重要,你想要一步实现“测试读取然后标记为读取中”。如果你有两步(“测试读取”和“标记为读取中”),你可能会有两个线程同时为一个客户端做测试读取操作,然后你会有两个线程同时为一个客户端调用on_read,结果就是数据冲突甚至可能导致软件崩溃。
你会发现代码变得极其复杂。
同步编程有第三个选择,就是为每个连接开辟一个线程。当时当并发的线程增加时,这就变成了一个最不能出现的情况。
然后,让我们来看异步编程。我们不断的异步读取。当一个客户端请求某些东西时,on_read被调用,然后回应,然后等待下一个请求(然后开始另外一个异步的read操作)。
异步方式的代码,10个线程
using namespace boost::asio;
io_service service;
struct client {
ip::tcp::socket sock;
streambuf buff; // reads the answer from the client
}
std::vector<client> clients;
void handle_clients() {
for ( int i = 0; i < clients.size(); ++i)
async_read_until(clients[i].sock, clients[i].buff, '\n',
boost::bind(on_read, clients[i], _1, _2));
for ( int i = 0; i < 10; ++i)
boost::thread(handle_clients_thread);
}
void handle_clients_thread() {
service.run();
}
void on_read(client & c, const error_code & err, size_t read_bytes) {
std::istream in(&c.buff);
std::string msg;
std::getline(in, msg);
if ( msg == "request_login")
Boost.Asio Fundamentals
[ 44 ]
c.sock.async_write( "request_ok\n", on_write);
else if ...
...
// now, wait for the next read from the same client
async_read_until(c.sock, c.buff, '\n',
boost::bind(on_read, c, _1, _2));
}
发现代码变得有多简单了吧?client结构里面只有两个成员,handle_clients()仅仅调用了async_read_until,然后它创建了10个线程,每个都调用service.run()。这些线程会处理任何来自客户端的异步read操作,然后分发任何向客户端的异步write操作。另外一件需要注意的事情是:on_read()一直在为下一次异步read操作做准备(看最后一行代码)
0 0