• 解析网络和协程


    resolver

    resolver类对应Socket API的getaddrinfo()系列函数,用于解析网址获得可用的IP地址,解析得到的IP地址可以使用socket对象连接。
    之前关于TCP通信的所有论述我们都是使用直接的IP地址,但在实际生活中大多数时候我们不可能知道socket 连接另一端的地址,而只有一个域名,这时候我们就需要使用resolver类来通过域名获得可用的IP,它可以实现与IP版本无关的网址解析

    template<typename InterProtocol,typename... as>
    
    class basic_resolver
    {
        public:
        typedef InterProtocol    protocol_type;
        typedef typename InterProtocol::endpoint endpoint_type;
        typedef basic_resolver_query<InterProtocol> query;
        typedef basic_resolver_iterator<InterProtocol> iteraror;
    
        explicit basic_resolver(io_service&io_service);
    
        void cancel();
        iteraror resolve(const query&q);
        
        iteraror resolve(const endpoint_type&e);
    
    
    
    
    };
    template<typename InternetProtocol>
    class basic_revolve_query
    {
         typedef InternetProtocol    protocol_type;
         basic_resolver_query(const std::string&service,
         flags resolve_flags=passive|address_configured)
         ...
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    resolver使用内部类query和 iterator共同完成查询IP地址的工作:首先使用网址和服务名(通常是端口号)创建query对象,然后由resolve ()成员函数生成iterator对象,它代表了查询到的IP端点。之后就可以使用socket对象尝试连接,直到找到一个可用的为止。
    在这里我们使用一个函数resolve_connect ()来封装resolver的调用过程,代码如下:

    void resolve_connect(ip::tcp::socket&sock,const char*name,int port)
    {
        ip::tcp::resolver r(sock.get_io_service());
        ip::tcp::resolver::query q(name,lexical_cast<string>(port));
    
        auto iter=r.resolve(q);
        decltype(iter) end;
        error_code ec=error::host_not_found;
        for(;ec&&iter!=end;++iter)
        {
            sock.close();
            sock.connect(*iter,ec);
    
        }
        if(ec)
        {
            cout<<"can't connect"<<endl;
            throw system_error(ec);
        }
        cout<<"connect success"<<endl;
    }
    int main()
    {
        try{
            io_service io;
            ip::tcp::socket sock(io);
            resolve_connect(sock,"www.baidu.com",80);
            cout<<sock.remote_endpoint()<<endl;
        }
        catch(std::exception&e){
            cout<<e.what()<<endl;
            
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    resolve_connect ()函数中使用了lexical_cast,这是因为query对象只接受字符串参数,所以我们需要把端口号由整数转换为字符串。
    当开始resolver的迭代时,我们需要使用error_code和逾尾迭代器两个条件来控制循环,因为有可能迭代完所有解析到的端点都无法连接,只有当error_code为0才表示连接成功。
    有了resolve_connect()函数,我们就可以不受具体IP地址值的限制,以更直观更灵活的域名来连接服务器

    协程

    协程(coroutine)是泛化的例程(routine)。例程只有一个入口和多个出口,C++里的函数就是这样,从最开始的函数入口开始,可以在某个时刻用return返回,例程就结束了。而协程则不同,它可以有多个入口多个出口,从最开始的入口进入之后可以随时用yield调用返回,之后再调用协程就会从刚才返回的地方继续执行。现代的很多编程语言都已经内置了协程支持,其中最著名的就是Lua

    类摘要

    template<typename Handler>
    class basic_yield_context
    {
        public:
        basic_yield_context(
            const detail::weak_ptr<callee_typr>&coro,
            caller_type&ca,Handler& handler
    
        );
        basic_yield_context operator[](
            boost::system::error_code&ec
        )const;
        detail::weak_ptr<callee_type>coro_;
        ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    yield_context的接口很简单,它保存了协程的运行环境,交替执行主协程(caller)和从协程(callee),达到异步的目的。operator[]用于外部获取发生的错误码,如果不使用operator[]则会抛出system_error异常未报告错误。

    通常我们不直接创建yield_context对象,而是使用函数spawn ( )。它产生
    yield_context对象,再传递给使用yield_context的函数。spawn()有多个重载形式,常用的是如下两种:

    void spawn(strand s,Function function);
    void spawn(io_service io,Function function);

    function必须符合如下的函数签名

    void func(boost::asio::yield_context yield);

    用法:
    在 asio 的协程用法里yield_context取代了我们之前使用的回调handler,async_xxx()函数不需要再写回调函数,而是使用yield _context对象。async_xxx ()调用后会自动交出控制权,执行其他的协程,直到异步操作完成时才会返回继续执行之后的动作,异步读写操作的字节数可以从async_xxx( )的返回值获取。整个程序的流程很像是同步模式,但因为有协程的存在实际上是异步而高效的。

    int main()
    {
        typedef ip::tcp::acceptor   acceptor_type;
        typedef ip::tcp::endpoint   endpoint_type;
        typedef ip::tcp::socket     socket_type;
    
        io_service io;
    
        spawn(io,[&](yield_context yield)
        {
            
            acceptor_type acceptor(io,endpoint(ip::tcp::v4(),6688));
            for(;;)
            {
            socket_type sock(io);
            acceptor.async_accept(sock,yield[ec]);
            if(ec)
            {
                return;
            }
            auto len=sock.async_write_some(
                buffer("hello coroutine"),yield
    
            );
            cout<<"send"<<len<<"bytes"<<endl;
        })
        
    
    
    }
    io.run();
    return 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    我们可以对比一下同步服务器代码,两者都是在一个for循环里监听端口,但协程版本是在 spawn ()函数里利用协程实现的,接受连接和发送数据使用的则是异步的async_accept ()和 async_write_some ( ),这样在每个异步调用处并不会阻塞,而是转入其他的协程,只有真正发生连接事件和写完成事件时才会继续执行后续的代码。

  • 相关阅读:
    【数据结构】链表其实并不难 —— 手把手带你实现双向链表
    ubuntu上通过修改grub启动参数,将串口重定向到sol
    8、SpringBoot_多环境开发
    【面试:并发篇38:多线程:线程池】ThreadPoolExecutor类的基本概念
    CESM2代码下载
    js 数组与对象相互转换
    【JavaSE】Map接口--深入源码解读HashMap与HashTable
    JMeter 面试题及答案整理,最新面试题
    【C++】特殊类设计
    使用vue-pdf出现的卡顿,空白,报错,浏览器崩溃解决办法
  • 原文地址:https://blog.csdn.net/qq_62309585/article/details/126948275