zoukankan      html  css  js  c++  java
  • 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地址值的限制,以更直观更灵活的域名来连接服务器。

  • 相关阅读:
    poj 2485 Highways 最小生成树
    hdu 3415 Max Sum of MaxKsubsequence
    poj 3026 Borg Maze
    poj 2823 Sliding Window 单调队列
    poj 1258 AgriNet
    hdu 1045 Fire Net (二分图匹配)
    poj 1789 Truck History MST(最小生成树)
    fafu 1181 割点
    减肥瘦身健康秘方
    人生的问题
  • 原文地址:https://www.cnblogs.com/javawebsoa/p/3230827.html
Copyright © 2011-2022 走看看