网络通讯
asio库支持TCP、UDP、ICMP通讯协议,它在名字空间boost::asio::ip里提供了大量的网络通讯方面的函数和类,很好地封装了原始的Berkeley Socket Api,展示给asio用户一个方便易用且健壮的网络通讯库。ios
ip::tcp类是asio网络通讯(TCP)部分主要的类,但它自己并无太多的功能,而是定义了数个用于TCP通讯的typedef类型,用来协做完成网络通讯。这些typedef包括端点类endpoint、套接字类socket、流类iostream,以及接收器acceptor、解析器resolver等等。从某种程度上来看,ip::tcp类更像是一个名字空间。数组
一、IP地址和端点
IP地址独立于TCP、UDP等通讯协议,asio库使用类ip::address来表示IP地址,能够同时支持ipv4和ipv6两种地址。服务器
[cpp] view plain copy网络
print?
app
- #include "stdafx.h"
- #include "boost/asio.hpp"
- #include "boost/date_time/posix_time/posix_time.hpp"
- #include "boost/bind.hpp"
- #include "boost/function.hpp"
- #include "iostream"
- using namespace std;
-
-
- int _tmain(int argc, _TCHAR* argv[])
- {
- boost::asio::ip::address addr; // 声明一个ip地址对象
- addr = addr.from_string("127.0.0.1"); // 从字符串产生IP地址
- assert(addr.is_v4()); // ipv4的地址
- cout << addr.to_string() << endl;
-
- addr = addr.from_string("2000:0000:0000:0000:0001:2345:6789:abcd");
- assert(addr.is_v6());
- cout << addr.to_string() << endl;
-
- return 0;
- }
有了IP地址,再加上通讯用的端口号就构成了一个socket端点,在asio库中用ip::tcp::endpoint类来表示。它的主要用法就是经过构造函数建立一个可用于socket通讯的端点对象,端点的地址和端口号能够用address()和port()得到:异步
[cpp] view plain copysocket
print?
async
- #include "stdafx.h"
- #include "boost/asio.hpp"
- #include "boost/date_time/posix_time/posix_time.hpp"
- #include "boost/bind.hpp"
- #include "boost/function.hpp"
- #include "iostream"
- using namespace std;
-
-
- int _tmain(int argc, _TCHAR* argv[])
- {
- boost::asio::ip::address addr; // 声明一个ip地址对象
- addr = addr.from_string("127.0.0.1"); // 从字符串产生IP地址
-
- boost::asio::ip::tcp::endpoint ep(addr, 6688);
-
- assert(ep.address() == addr);
- assert(ep.port() == 6688);
-
- return 0;
- }
二、同步socket处理
ip::tcp的内部类型socket、acceptor和resolver是asio库TCP通讯中最核心的一组类,它们封装了socket的链接、断开和数据收发功能,使用它们能够很容易地编写出socket程序。tcp
socket类是TCP通讯的基本类,调用成员函数connect()能够链接到一个指定的通讯端点,链接成功后用local_endpoint()和remote_endpoint()得到链接两端的端点信息,用read_some()和write_some()阻塞读写数据,当操做完成后使用close()函数关闭socket。若是不关闭socket,那么在socket对象析构时也会自动调用close()关闭。函数
acceptor类对应socketAPI的accept()函数功能,它用于服务器端,在指定的端口号接受链接,必须配合socket类才能完成通讯。
resolver类对应socketAPI的getaddrinfo()系列函数,用于客户端解析网址得到可用的IP地址,解析获得的IP地址可使用socket对象链接。
下面是一个使用socket类和acceptor类来实现一对同步通讯的服务器和客户端程序:
服务器端(它使用一个acceptor对象在6688端口接受链接,当有链接时使用一个socket对象发送一个字符串):
server.cpp:
[cpp] view plain copy
print?

- // server.cpp : 定义控制台应用程序的入口点。
- //
-
- #include "stdafx.h"
- #include "boost/asio.hpp"
- #include "boost/date_time/posix_time/posix_time.hpp"
- #include "boost/bind.hpp"
- #include "boost/function.hpp"
- #include "iostream"
- using namespace std;
-
-
- int _tmain(int argc, _TCHAR* argv[])
- {
- try
- {
- cout << "server start" << endl;
- boost::asio::io_service ios;
-
- boost::asio::ip::tcp::acceptor acceptor(ios,
- boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 6688));
-
- cout << acceptor.local_endpoint().address() << endl;
-
- while (true)
- {
- boost::asio::ip::tcp::socket sock(ios);
- acceptor.accept(sock);
-
- cout << "client : ";
- cout << sock.remote_endpoint().address() << endl;
-
- sock.write_some(boost::asio::buffer("hello asio"));
- }
- }
-
- catch (std::exception& e)
- {
- cout << e.what() << endl;
- }
-
- return 0;
- }
服务器端程序里要注意的是自由函数buffer(),他能够包装不少种类的容器成为asio组件可用的缓冲区类型。一般不能直接把数组、vercor等容器用做asio的读写参数,必须使用buffer()函数包装
client:
[cpp] view plain copy
print?

- // client.cpp : 定义控制台应用程序的入口点。
- //
-
- #include "stdafx.h"
- #include "boost/asio.hpp"
- #include "boost/date_time/posix_time/posix_time.hpp"
- #include "boost/bind.hpp"
- #include "boost/function.hpp"
- #include "iostream"
- using namespace std;
- #include "vector"
-
-
- class AsynTimer
- {
- public:
- template<typename F> // 模板类型,能够接受任意可调用物
- AsynTimer(boost::asio::io_service& ios, int x, F func)
- :f(func), count_max(x), count(0), // 初始化回调函数和计数器
- t(ios, boost::posix_time::millisec(500)) // 启动计时器
- {
- t.async_wait(boost::bind(&AsynTimer::CallBack, // 异步等待计时器
- this, boost::asio::placeholders::error)); // 注册回调函数
- }
-
- void CallBack(const boost::system::error_code& error)
- {
- if (count >= count_max) // 若是计数器达到上限则返回
- {
- return;
- }
- ++count;
- f(); // 调用function对象
-
- // 设置定时器的终止时间为0.5秒以后
- t.expires_at(t.expires_at() + boost::posix_time::microsec(500));
- // 再次启动定时器,异步等待
- t.async_wait(boost::bind(&AsynTimer::CallBack, this, boost::asio::placeholders::error));
- }
-
- private:
- int count;
- int count_max;
- boost::function<void()> f; // function对象,持有无参无返回值的可调用物
- boost::asio::deadline_timer t; // asio定时器对象
- };
-
-
- void client(boost::asio::io_service& ios)
- {
- try
- {
- cout << "client start." << endl;
-
- boost::asio::ip::tcp::socket sock(ios);
- boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string("127.0.0.1"), 6688);
-
- sock.connect(ep);
-
- vector<char> str(100, 0);
- sock.read_some(boost::asio::buffer(str));
-
- cout << "recive from" << sock.remote_endpoint().address();
- cout << &str[0] << endl;
-
- }
- catch (std::exception& e)
- {
- cout << e.what() << endl;
- }
- }
-
-
- int _tmain(int argc, _TCHAR* argv[])
- {
- boost::asio::io_service ios;
- AsynTimer at(ios, 50000, boost::bind(client, boost::ref(ios)));
- ios.run();
-
- return 0;
- }
三、异步socket处理
咱们把刚才的同步socket程序改成异步调用方式。异步程序的处理流程与同步程序基本相同,只须要把原有的同步调用函数都换成前缀是async_的异步调用函数,并增长回调函数,在回调函数中再启动一个异步调用
服务器端:
[cpp] view plain copy
print?

- // server.cpp : 定义控制台应用程序的入口点。
- //
-
- #include "stdafx.h"
- #include "boost/asio.hpp"
- #include "boost/date_time/posix_time/posix_time.hpp"
- #include "boost/bind.hpp"
- #include "boost/function.hpp"
- #include "iostream"
- using namespace std;
-
-
- class Server
- {
- private:
- boost::asio::io_service& ios;
- boost::asio::ip::tcp::acceptor acceptor;
- typedef boost::shared_ptr<boost::asio::ip::tcp::socket> sock_pt;
-
- public:
- Server(boost::asio::io_service& io) : ios(io),
- acceptor(ios, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 6688))
- {
- Start();
- }
- ~Server()
- {
-
- }
-
- void Start()
- {
- sock_pt sock(new boost::asio::ip::tcp::socket(ios)); // 智能指针
-
- // 异步侦听服务
- acceptor.async_accept(*sock, boost::bind(&Server::acceptor_handle,
- this, boost::asio::placeholders::error, sock));
- }
-
- void acceptor_handle(const boost::system::error_code& error, sock_pt sock)
- {
- if (error)
- {
- return;
- }
- cout << "client : ";
-
- // 输出链接的客户端信息
- cout << sock->remote_endpoint().address() << endl;
-
- //
- sock->async_write_some( boost::asio::buffer("hello asio"),
- boost::bind(&Server::write_handle,
- this, boost::asio::placeholders::error));
-
- Start(); // 再次启动异步接受链接
- }
-
- void write_handle(const boost::system::error_code& error)
- {
- cout << "send message is complate" << endl;
- }
-
- };
-
-
- int _tmain(int argc, _TCHAR* argv[])
- {
- try
- {
- cout << "server start." << endl;
-
- boost::asio::io_service ios;
-
- Server serv(ios);
-
- ios.run();
- }
- catch (std::exception& e)
- {
- cout << e.what() << endl;
- }
-
- return 0;
- }
首先检查asio传递的error_code,保证没有错误发生。而后调用socket对象的async_write_some()异步发送数据。一样,咱们必须再为这个异步调用编写回调函数write_handler()。当发送完数据后不要忘记调用Start()再次启动服务器接受连接,不然当完成数据发送后io_service将由于没有时间处理而结束运行。
发送数据的回调函数write_handler()很简单,由于不须要作更多的工做,能够直接实现一个空函数,在这里简单地输出一条信息,表示异步发送数据完成
客户端:
[cpp] view plain copy
print?

- // client.cpp : 定义控制台应用程序的入口点。
- //
-
- #include "stdafx.h"
- #include "boost/asio.hpp"
- #include "boost/date_time/posix_time/posix_time.hpp"
- #include "boost/bind.hpp"
- #include "boost/function.hpp"
- #include "iostream"
- using namespace std;
- #include "vector"
-
-
- class Client
- {
- private:
- boost::asio::io_service& ios;
- boost::asio::ip::tcp::endpoint ep; // tcp端点
- typedef boost::shared_ptr<boost::asio::ip::tcp::socket> sock_pt;
-
- public:
- Client(boost::asio::io_service& io) : ios(io),
- ep(boost::asio::ip::address::from_string("127.0.0.1"), 6688)
- {
- Start(); // 启动异步链接
- }
-
- ~Client()
- {
-
- }
-
- void Start()
- {
- sock_pt sock(new boost::asio::ip::tcp::socket(ios));
- sock->async_connect(ep, boost::bind(&Client::conn_handle, this,
- boost::asio::placeholders::error, sock));
- }
-
- void conn_handle(const boost::system::error_code& error, sock_pt sock)
- {
- if (error)
- {
- return;
- }
- cout << "recive from : " << sock->remote_endpoint().address();
-
- // 创建接收数据的缓冲区
- boost::shared_ptr<vector<char> > str(new vector<char>(100, 0));
-
- // 异步读取数据
- sock->async_read_some(boost::asio::buffer(*str), boost::bind(&Client::read_handle,
- this,
- boost::asio::placeholders::error,
- str));
- Start(); // 再次启动异步链接
- }
-
- void read_handle(const boost::system::error_code& error,
- boost::shared_ptr<vector<char> > str)
- {
- if (error)
- {
- return;
- }
- cout << &(*str)[0] << endl;
- }
- };
-
-
- int _tmain(int argc, _TCHAR* argv[])
- {
- try
- {
- cout << "client start." << endl;
- boost::asio::io_service ios;
-
- Client client(ios);
-
- ios.run();
- }
- catch (std::exception& e)
- {
- cout << e.what() << endl;
- }
-
- return 0;
- }
四、查询网络地址
以前关于tcp通讯的全部论述都是使用直接的ip地址,但在实际生活中大多数时候,都不大可能知道socket连接另外一端的地址,而只有一个域名,这时候咱们就须要使用resolver类来经过域名得到可用的ip,它能够实现与ip版本无关的网址解析
resolver使用内部类query和iterator共同完成查询ip地址的工做:首先使用网址和服务名建立query对象,而后由resolve()函数生成iterator对象,它表明了查询到的ip端点。以后就可使用socket对象尝试链接,知道找到一个可用的为止。
[cpp] view plain copy
print?

- #include "stdafx.h"
- #include "boost/asio.hpp"
- #include "boost/date_time/posix_time/posix_time.hpp"
- #include "boost/bind.hpp"
- #include "boost/function.hpp"
- #include "boost/lexical_cast.hpp"
- #include "boost/asio/error.hpp"
- #include "iostream"
- using namespace std;
-
-
- void resolv_connect(boost::asio::ip::tcp::socket& sock, const char* name, int port)
- {
- boost::asio::ip::tcp::resolver rlv(sock.get_io_service());
- boost::asio::ip::tcp::resolver::query qry(name, boost::lexical_cast<string>(port));
-
- boost::asio::ip::tcp::resolver::iterator iter = rlv.resolve(qry);
- boost::asio::ip::tcp::resolver::iterator end;
-
- boost::system::error_code ec = boost::asio::error::host_not_found;
- for (; ec && iter != end; ++iter)
- {
- sock.close();
- sock.connect(*iter, ec);
- }
-
- if (ec)
- {
- cout << "can't connect." << endl;
- throw boost::system::error_code(ec);
- }
-
- cout << "connet suceessd." << endl;
- }
-
-
- int _tmain(int argc, _TCHAR* argv[])
- {
- try
- {
- boost::asio::io_service ios;
- boost::asio::ip::tcp::socket sock(ios);
-
- resolv_connect(sock, "www.boost.org", 80);
-
- ios.run();
- }
- catch (std::exception& e)
- {
- cout << e.what() << endl;
- }
-
- return 0;
- }
resolv_connect()函数中使用lexical_cast,这是由于query对象只接受字符串参数,因此咱们须要把端口号由整数转换为字符串。
当开始resolver的迭代时,须要使用error_code和逾尾迭代器两个条件来控制循环,由于有可能迭代完全部解析到的端点都没法链接,只有当error_code为0才表示链接成功。
有了resolv_connect()函数,就能够不受具体ip地址值的限制,以更直观更灵活的域名来链接服务器。