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


    网络通信

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

  • 相关阅读:
    Spring BeanFactory与FactoryBean的区别及其各自的详细介绍于用法
    解决 vim 报错:the imp module is deprecated in favour of importlib
    SIFT了解,哪些方法可以在现在的AI算法中借鉴?
    CLAHE
    实际算法项目工程上手日志C/C++
    OS X 切换gcc版本
    opencv3.4.2 cmake错误:in-source builds are not allowed
    C++ opencv 计算两张图像的PSNR相似度
    如何在OS X 中使用markdown + latex混合记笔记?
    给anaconda 换源
  • 原文地址:https://www.cnblogs.com/javawebsoa/p/3230827.html
Copyright © 2020-2023  润新知