美文网首页
采用UDP的echo服务器示例

采用UDP的echo服务器示例

作者: 长不胖的Garfield | 来源:发表于2017-01-21 16:11 被阅读0次

    同步UDP客户端

    UDP是面向无连接的,使用起来比较简单,打开socke之后,指定目标端口,直接进行接收和发送:

    void test_udp_echo_client()
    {
        try
        {
            io_service io;
            udp::endpoint remote_ep(ip::address_v4::from_string("127.0.0.1"), 1024);
    
            udp::socket socket(io);
            socket.open(udp::v4());
    
            char line[1024];
            while (std::cin.getline(line, 1024))
            {
                socket.send_to(boost::asio::buffer(line, std::strlen(line)),remote_ep);
                auto size = socket.receive_from(boost::asio::buffer(line),remote_ep);
                std::cout.write(line,size);
            }
            socket.close();
        }
        catch (std::exception& e)
        {
            std::cerr << e.what() << std::endl;
        }
    }
    

    socket本身提供了一些接口:

    • socket.send_to 同步发送接口
    • socket.receive_from 同步接收接口
      Boost.Asio也有一些接口用来进行发送和接收,可以参见后续的发送/接收函数组;

    需要注意的是,boost.asio.buffer是一种接口适配器,通过接口进行发送和接收,必须有对应的数据缓冲区提供数据或者存储空间。

    同步UDP服务器

    同步接收同步发送的UDP服务器也比较简单,创建一个绑定到本地端口的socket,然后就是接收及发送动作:

    void test_udp_echo_server()
    {
        try
        {
            io_service io;
            ip::udp::socket socket(io, udp::endpoint(udp::v4(), 1024));
            for (;;)
            {
                std::array<char,1024> recv_buf;
                ip::udp::endpoint remote_socket;
                boost::system::error_code error;
    
                //同步接收
                auto size = socket.receive_from(boost::asio::buffer(recv_buf),remote_socket,0,error);
                if (error && error!= boost::asio::error::message_size)
                {
                    throw boost::system::system_error(error);
                }
                std::cout.write(recv_buf.data(),size);
                //发送回去
                socket.send_to(boost::asio::buffer(recv_buf,size),remote_socket);
            }
        }
        catch (std::exception& e)
        {
            std::cerr<<e.what()<<std::endl;
        }
    }
    

    同步操作是不需要运行IO服务的,以最常规的方式来进行发送和接收,注意接收时如果接收到全部消息,即EOF也是通过报错形式,错误码为error::message_size

    异步UDP服务器的实现问题

    实现异步的UDP服务器就略显复杂,需要保证IO服务运行,发起异步操作时要注意数据缓冲区生命周期:

    1. 启动IO服务
      启动IO服务可以直接执行io_service.run,由于IO服务的多线程安全特性,也可以启动线程来执行,譬如:
    boost::asio::io_service io_;
    std::thread task([&](){ io_.run();});
    task.detach();
    
    1. 停止IO服务
      停止IO服务可以直接执行io_service.stop,会立即从运行状态退出,直到reset之后才能重新启动。

    2. 保证IO服务执行
      IO服务的run方法只有在有异步操作未完成的时候才能一直运行,一旦没有异步操作就会退出,因而需要在run之前保证有异步操作发起,在过程中不断发起异步操作就能够保证IO服务一直运行。

    3. 数据缓冲区生命周期
      发起异步操作后,会立即退出,但是异步操作并没有执行,这就要求提供的数据缓冲区生命周期要足够长,存活到异步操作执行完,即在完成回调中再释放数据缓冲区,通常可以采用智能指针或者new出来的对象。

    异步UDP服务器实现

    class async_udp_echo_server
    {
    public:
        async_udp_echo_server()
            :socket_(io_,udp::endpoint(udp::v4(),1024))
        {
            do_recv();
    
            std::thread task([&](){ io_.run();});
            task.detach();
        }
    
        void do_recv()
        {
            //保证发送完成之前一直有效
            char* recv_buf = new char[1024];
    
            socket_.async_receive_from(boost::asio::buffer(recv_buf,1024), remote_ep_,
                [recv_buf, this](const boost::system::error_code& error,std::size_t bytes_transferred){
                if (!error || error == boost::asio::error::message_size)
                {
                    do_send(recv_buf,bytes_transferred, std::move(remote_ep_));
                }
                else
                {
                    std::cout << error.message() << "\n";
                }
                do_recv();
            });
        }
    
        void do_send(char* send_buf,std::size_t size,udp::endpoint ep)
        {
            socket_.async_send_to(boost::asio::buffer(send_buf,size),ep,
                [send_buf](const boost::system::error_code& error, std::size_t bytes_transferred){
                if (!error)
                {
                    std::cout<<"echo finished\n";
                }
                delete[] send_buf;
            });
        }
    
        void stop()
        {
            io_.stop();
        }
        ~async_udp_echo_server()
        {
            stop();
        }
    private:
        boost::asio::io_service io_;
        udp::socket socket_;
        udp::endpoint remote_ep_;
    };
    

    可以看到do_recv方法发起了一个异步接收操作,在操作完成回调中再次发起,构造服务器时率先调用了do_recv,从而保证IO服务一直运行。

    do_recv方法在发起异步操作前申请了一块内存,接收的内容被保存在这块内存之中,当do_send发起异步发送操作时被借用,直到发送完成才将这段内存释放掉。

    在构造函数中启动了一个线程来执行IO服务,并detach掉线程,从而保证服务器不阻塞,在析构函数停止了IO服务。

    需要注意到的是remote_ep_在执行do_send时被move了,由于remote_ep_标识了远程端口,而且被声明为成员变量,在接受操作中会被填充远程端口内容,如果多个远程主机同时发起,单个remote_ep_是无法正常处理的,所以一旦内容被填充后,就会转移出去给发送操作使用[个人理解,没有实际测试和验证]。

    使用方法

    async_udp_echo_server server_;
    
    char line[1024];
    while (std::cin.getline(line, 1024)){
         if(line[0] == 'Q')
              break;
    };
    server_.stop();
    

    相关文章

      网友评论

          本文标题:采用UDP的echo服务器示例

          本文链接:https://www.haomeiwen.com/subject/ekfnbttx.html