c++ boost库异步网络编程 -- ASIO库

时间:2022-09-08 22:12:03

c++ boost库异步网络编程 – ASIO库

标签(空格分隔): c++ boost


概述

Boost C++ 库 Asio,它是异步输入输出的核心。 名字本身就说明了一切:Asio 意即异步输入/输出。该库可以让 C++ 异步地处理数据,且平*立。异步数据处理就是指,任务触发后不需要等待它们完成。 相反,Boost.Asio 会在任务完成时触发一个应用。异步任务的主要优点在于,在等待任务完成时不需要阻塞应用程序,可以去执行其它任务。

异步任务的典型例子是网络应用。 如果数据被发送出去了,比如发送至 Internet,通常需要知道数据是否发送成功。 如果没有一个象 Boost.Asio 这样的库,就必须对函数的返回值进行求值。 但是,这样就要求待至所有数据发送完毕,并得到一个确认或是错误代码。 而使用 Boost.Asio,这个过程被分为两个单独的步骤:第一步是作为一个异步任务开始数据传输。 一旦传输完成,不论成功或是错误,应用程序都会在第二步中得到关于相应的结果通知。 主要的区别在于,应用程序无需阻塞至传输完成,而可以在这段时间里执行其它操作。

基本概念

IP地址

对于IP地址的处理,Boost.Asio提供了ip::address , ip::address_v4和ip::address_v6类。 它们提供了相当多的函数。下面列出了最重要的几个:
ip::address(v4_or_v6_address):这个函数把一个v4或者v6的地址转换成ip::address
ip::address:from_string(str):这个函数根据一个IPv4地址(用.隔开的)或者一个IPv6地址(十六进制表示)创建一个地址。
ip::address::to_string() :这个函数返回这个地址的字符串。
ip::address_v4::broadcast([addr, mask]):这个函数创建了一个广播地址 ip::address_v4::any():这个函数返回一个能表示任意地址的地址。
ip::address_v4::loopback(), ip_address_v6::loopback():这个函数返回环路地址(为v4/v6协议)
ip::host_name():这个函数用string数据类型返回当前的主机名。
大多数情况你会选择用ip::address::from_string:

ip::address addr = ip::address::from_string("127.0.0.1");

如果你想通过一个主机名进行连接,下面的代码片段是无用的:
// 抛出异常

ip::address addr = ip::address::from_string("www.yahoo.com");

端点

端点是使用某个端口连接到的一个地址。不同类型的socket有它自己的endpoint类,比如ip::tcp::endpoint、ip::udp::endpoint和ip::icmp::endpoint
如果想连接到本机的80端口,你可以这样做:
ip::tcp::endpoint ep( ip::address::from_string(“127.0.0.1”), 80);
有三种方式来让你建立一个端点:
endpoint():这是默认构造函数,某些时候可以用来创建UDP/ICMP socket。
endpoint(protocol, port):这个方法通常用来创建可以接受新连接的服务器端socket。
endpoint(addr, port):这个方法创建了一个连接到某个地址和端口的端点。
例子如下:

ip::tcp::endpoint ep1;
ip::tcp::endpoint ep2(ip::tcp::v4(), 80);
ip::tcp::endpoint ep3( ip::address::from_string("127.0.0.1), 80);

解析器

如果你想连接到一个主机(不是IP地址),则需要通过解析器解析给定的ip和端口或者域名,返回解析出来的端点集合,可以通过迭代,拿到所有与域名关联的iP:

// 输出 "87.248.122.122"
io_service service;
ip::tcp::resolver resolver(service);
ip::tcp::resolver::query query("www.yahoo.com", "80");
ip::tcp::resolver::iterator iter = resolver.resolve( query);
ip::tcp::endpoint ep = *iter;
std::cout << ep.address().to_string() << std::endl;

你可以用你需要的socket类型来替换tcp。首先,为你想要查询的名字创建一个查询器,然后用resolve()函数解析它。如果成功,它至少会返回一个入口。你可以利用返回的迭代器,使用第一个入口或者遍历整个列表来拿到全部的入口。
给定一个端点,可以获得他的地址,端口和IP协议(v4或者v6):

std::cout << ep.address().to_string() << ":" << ep.port()
<< "/" << ep.protocol() << std::endl;

套接字

Boost.Asio有三种类型的套接字类:ip::tcp, ip::udp和ip::icmp。当然它也是可扩展的,你可以创建自己的socket类,尽管这相当复杂。如果你选择这样做,参照一下boost/asio/ip/tcp.hpp, boost/asio/ip/udp.hpp和boost/asio/ip/icmp.hpp。它们都是含有内部typedef关键字的超小类。

你可以把ip::tcp, ip::udp, ip::icmp类当作占位符;它们可以让你便捷地访问其他类/函数,如下所示:

ip::tcp::socket, ip::tcp::acceptor, ip::tcp::endpoint,ip::tcp::resolver, ip::tcp::iostream
ip::udp::socket, ip::udp::endpoint, ip::udp::resolver
ip::icmp::socket, ip::icmp::endpoint, ip::icmp::resolver

socket类创建一个相应的socket。而且总是在构造的时候传入io_service实例:

io_service service;
ip::udp::socket sock(service)
sock.set_option(ip::udp::socket::reuse_address(true));

每一个socket的名字都是一个typedef关键字

ip::tcp::socket = basic_stream_socket
ip::udp::socket = basic_datagram_socket
ip::icmp::socket = basic_raw_socket

Demo 服务端

#include <boost\asio.hpp>
#include <boost\make_shared.hpp>
#include <boost\thread.hpp>
#include <boost\bind.hpp>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;
using namespace boost::asio;
using ip::tcp;


class CAsyServer
{
    typedef boost::shared_ptr<ip::tcp::socket> socket_ptr;

public:
    CAsyServer(io_service& server, short port)
        :m_server(server)
        , m_acceptor(m_server, ip::tcp::endpoint(ip::tcp::v4(),port))
    {
    }

    //启动服务器
    void startService()
    {
        cout <<"本地监听地址 IP ["<<m_acceptor.local_endpoint().address() <<"]"<<endl;

        PostAcceptRequest();
    }

private:
    void PostAcceptRequest()
    {
        socket_ptr pClient(new ip::tcp::socket(m_server));

        m_acceptor.async_accept(*pClient, boost::bind(&CAsyServer::accept_complete, 
            this,                   //成员函数的第一参数是this指针
            pClient,
            _1));
    }

    void PostReadRequest(socket_ptr& pClient)
    {
        pClient->async_read_some(buffer(m_szReadBuff),
            boost::bind(&CAsyServer::Read_complete,
            this,
            pClient,
            _1));
    }

private:
    void accept_complete(socket_ptr& pSocket, const boost::system::error_code& err)
    {
        if (err)
        {
            cout << "accept_complete error" << endl;
            return;
        }

        PostAcceptRequest();

        cout << "开始读取数据" << endl;
        PostReadRequest(pSocket);
    }

    void write_complete(socket_ptr& pSocket, const boost::system::error_code& err)
    {
        if (err)
        {
            cout << "error" << endl;
            return;
        }
    }

    void Read_complete(socket_ptr& pSocket, const boost::system::error_code& err)
    {
        if (err)
        {
            cout << "Read_complete error" << endl;
            cout << err.message() << endl;
            cout << err.value() << endl;

            return;
        }

        printf("from[%s]:%s\n", pSocket->remote_endpoint().address().to_string().c_str(),m_szReadBuff);

        PostReadRequest(pSocket);
    }

private:
    io_service&             m_server;
    ip::tcp::acceptor       m_acceptor;
    char                    m_szReadBuff[2048];

};


int _tmain(int argc, _TCHAR* argv[])
{
    try 
    {
        io_service server;
        CAsyServer service(server, 2001);

        service.startService();

        server.run();

    }
    catch (std::exception& _e) 
    {
        cout << "异常处理" << endl;
        std::cout << _e.what() << std::endl;
    }
    std::cout << "server end." << std::endl;

    return 0;
}

Demo 客户端

#include <iostream>
#include <boost\shared_ptr.hpp>
#include <boost\asio.hpp>
#include <boost\bind.hpp>

using namespace std;
using namespace boost::asio;

typedef boost::shared_ptr<ip::tcp::socket> sock_ptr;

void write_handler(sock_ptr&  psock, const boost::system::error_code & ec);

struct myPacket
{
    int mainID;
    int subID;

    char szname[64];
    char szSex[6];
};

void connect_handler(sock_ptr&  psock, const boost::system::error_code & ec) {
    // 如果ec返回成功我们就可以知道连接成功了
    if (ec)
    {
        cout << "connect error" << endl;
        return;
    }


    for (int i = 0; i < 500; i++)
    {
        char szbuf[512] = "这是一个连接\0";
        psock->async_write_some(buffer(szbuf), boost::bind(write_handler, psock, _1));
    }


}

void write_handler(sock_ptr&  psock, const boost::system::error_code & ec) 
{
    try
    {
        // 如果ec返回成功我们就可以知道连接成功了
        if (ec)
        {
            cout << "write_handler error " << ec.message() << endl;
            return;
        }

        char szBuff[128];
        cin >> szBuff;

        psock->async_write_some(buffer(szBuff), boost::bind(write_handler, psock, _1));
    }
    catch (std::exception& e)
    {
        cout << e.what() << endl;
    }
}

int _tmain(int argc, _TCHAR* argv[])
{
    try
    {
        io_service service;

        ip::tcp::endpoint ep(ip::address::from_string("192.168.123.187"), 2001);

        sock_ptr psock(new ip::tcp::socket(service));

        psock->async_connect(ep, boost::bind(connect_handler, psock, _1));

        service.run();

    }
    catch (std::exception &e)
    {
        cout << e.what() << endl;
    }
    catch (...)
    {
        cout << "位置错误" << endl;
    }

    return 0;
}