网络通信

asio库支持TCP、UDP、ICMP通信协议,它在名字空间boost::asio::ip里提供了大量的网络通信方面的函数和类,很好地封装了原始的Berkeley Socket Api,展现给asio用户一个方便易用且健壮的网络通信库。

ip::tcp类是asio网络通信(TCP)部分主要的类,但它本身并没有太多的功能,而是定义了数个用于TCP通信的typedef类型,用来协作完成网络通信。这些typedef包括端点类endpoint、套接字类socket、流类iostream,以及接收器acceptor、解析器resolver等等。从某种程度上来看,ip::tcp类更像是一个名字空间。

1、IP地址和端点

IP地址独立于TCP、UDP等通信协议,asio库使用类ip::address来表示IP地址,可以同时支持ipv4和ipv6两种地址。

#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()获得:

#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;
}

2、同步socket处理

ip::tcp的内部类型socket、acceptor和resolver是asio库TCP通信中最核心的一组类,它们封装了socket的连接、断开和数据收发功能,使用它们可以很容易地编写出socket程序。

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:

// 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:

// 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;
}

3、异步socket处理

我们把刚才的同步socket程序改为异步调用方式。异步程序的处理流程与同步程序基本相同,只需要把原有的同步调用函数都换成前缀是async_的异步调用函数,并增加回调函数,在回调函数中再启动一个异步调用

服务器端:

// 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()很简单,因为不需要做更多的工作,可以直接实现一个空函数,在这里简单地输出一条信息,表示异步发送数据完成

客户端:

// 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;
}

4、查询网络地址

之前关于tcp通信的所有论述都是使用直接的ip地址,但在实际生活中大多数时候,都不大可能知道socket链接另一端的地址,而只有一个域名,这时候我们就需要使用resolver类来通过域名获得可用的ip,它可以实现与ip版本无关的网址解析

resolver使用内部类query和iterator共同完成查询ip地址的工作:首先使用网址和服务名创建query对象,然后由resolve()函数生成iterator对象,它代表了查询到的ip端点。之后就可以使用socket对象尝试连接,知道找到一个可用的为止。

#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地址值的限制,以更直观更灵活的域名来连接服务器。

boost------asio库的使用2(Boost程序库完全开发指南)读书笔记的更多相关文章

  1. boost------asio库的使用1(Boost程序库完全开发指南)读书笔记

    asio库基于操作系统提供的异步机制,采用前摄器设计模式(Proactor)实现了可移植的异步(或者同步)IO操作,而且并不要求多线程和锁定,有效地避免了多线程编程带来的诸多有害副作用. 目前asio ...

  2. [转] boost------ref的使用(Boost程序库完全开发指南)读书笔记

    http://blog.csdn.net/zengraoli/article/details/9663057 STL和Boost中的算法和函数大量使用了函数对象作为判断式或谓词参数,而这些参数都是传值 ...

  3. boost------signals2的使用1(Boost程序库完全开发指南)读书笔记

    signals2基于Boost的另一个库signals,实现了线程安全的观察者模式.在signals2库中,观察者模式被称为信号/插槽(signals and slots),他是一种函数回调机制,一个 ...

  4. boost------ref的使用(Boost程序库完全开发指南)读书笔记

    STL和Boost中的算法和函数大量使用了函数对象作为判断式或谓词参数,而这些参数都是传值语义,算法或函数在内部保修函数对象的拷贝并使用,例如: #include "stdafx.h&quo ...

  5. boost------function的使用(Boost程序库完全开发指南)读书笔记

    function是一个函数对象的“容器”,概念上像是c/c++中函数指针类型的泛化,是一种“智能函数指针”.它以对象的形式封装了原始的函数指针或函数对象,能够容纳任意符合函数签名的可调用对象. 因此, ...

  6. boost------bind的使用(Boost程序库完全开发指南)读书笔记

    bind是c++98标准库中函数适配器bind1st/bind2nd的泛化和增强,可以适配任意的可调用类型,包括函数指针.函数引用.成员函数指针和函数对象. 1.工作原理 bind并不是一个单独的类或 ...

  7. boost------signals2的使用2(Boost程序库完全开发指南)读书笔记

    1.应用于观察者模式 本小节将使用signals2开发一个完整的观察者模式示例程序,用来演示信号/插槽的用法.这个程序将模拟一个日常生活场景:客人按门铃,门铃响,护士开门,婴儿哭闹. Ring.h: ...

  8. Boost程序库完全开发指南——深入C++“准”标准库(第3版)

    内容简介  · · · · · · Boost 是一个功能强大.构造精巧.跨平台.开源并且完全免费的C++程序库,有着“C++‘准’标准库”的美誉. Boost 由C++标准委员会部分成员所设立的Bo ...

  9. Boost.Asio 网络编程([译]Boost.Asio基本原理)

    转自:https://m.w3cschool.cn/nlzbw/nlzbw-3vs825ya.html Boost.Asio基本原理 这一章涵盖了使用Boost.Asio时必须知道的一些事情.我们也将 ...

随机推荐

  1. Android学习4—短信发送器的实现

    界面预览: 由图中可以看出,此APP需要的组件有:两个TextView,一个用于显示手机号码的标题,另一个用于显示短信内容的标题.                                    ...

  2. ng-html 报 不安全 警告解决办法

    app.filter('to_trusted',['$sce',function($sce){ return function(text){ return $sce.trustAsHtml(text) ...

  3. [Kinect]XBox One Kinect连接Windows

    注:本文全原创,作者:Noah Zhang  (http://www.cnblogs.com/noahzn/) XBox One带体感套装去年就买了,昨天京东349元入了个适配器,下午就送到了,开箱. ...

  4. iOS NSDecimalNumber 货币计算 四舍五入

    今天遇到一个问题 服务器返回货币数据 妈的 用string > floatvalue   不准确 去百度查查 妈的国人分享精神真差  真他妈的自私 一个破壁文章没几个字 还是从国外翻译过来的 全 ...

  5. xfire实现webservice客户端之测试关注点

    日前的工作接触到很多系统间的Webservice调用,这里想谈谈基于spring+xfire实现的webservice的客户端踩过的一些坑,需要测试关注的点. xFire的配置项 在spring中实现 ...

  6. www.nygwkt.com

    南京宁阳制冷设备维修有限公司是专业从事厨房空调,http://www.nygwkt.com岗位空调制冷设备设计.制造.安装.改造.维修.保养的专业化公司.在南京享有很高的客户评论. 我们对南京宁阳制冷 ...

  7. scala 安装

    http://www.scala-lang.org/download/install.html http://zh.scala-tour.com/#/hello-wolrd scala指南 To ru ...

  8. 微软HoloLens技术解谜

    HoloLens 是什么? HoloLens 是微软发布的可穿戴式增强现实计算设备,它拥有这么几个关键要素: 它是增强现实产品,即 Augmented Reality(AR),AR 技术将计算机生成的 ...

  9. rootkit的检测工具使用(chkrootkit和rootkit hunter)

      信息安全        这两天突然发现我们的服务器产生大量DNS解析连线.为了查明问题,就下载网上找工具检查问题所在.用了两个工具,一个chkrootkit,另外一个rootkit huntur. ...

  10. -_-#gb2312解码

    百度视频采用gb2312编码,点击出来的链接中的中文转成了gb2312,而gb2312无法解码 如果链接中的中文直接utf-8编码,就没问题,但编辑后台有长度限制 关于URL编码 JS(Unicode ...