基于boost asio实现sync io client通信

一.基于boost实现网络io通信

  关于boost库的介绍及使用可见前述文章:boost库相关介绍
  本文主要介绍基于boost实现sync io通信的通用功能封装,并基于io context实现对write和read功能的等待延时,超时会抛出异常。另外设计了两个接口,分别实现string和byte的发送和接收。通用底层封装程序包括:sync_io.hpp、sync_tcp_client.cpp/hpp、sync_udp_client.cpp/hpp;基于上述封装开发的应用层程序包括:tcp_client.cpp/hpp、udp_client.cpp/hpp、main.cpp。程序的功能介绍及层次关系如下图所示:
在这里插入图片描述

二.源代码

2.1 通用底层封装程序

sync_io.hpp

#ifndef __SYNC_IO_HPP__
#define __SYNC_IO_HPP__

#include <chrono>
#include <string>
#include <iostream>
#include <exception>
#include <functional>

#include <boost/asio.hpp>

namespace asio = boost::asio;

namespace sync_io {
    using duration = std::chrono::steady_clock::duration;

    //! Exception for sync execute timeout
    class ex_timeout : public std::runtime_error
    {
    public:
        ex_timeout()
            : std::runtime_error("sync io timeout")
        {
        }
    };

    class io : public asio::io_context
    {
    public:
        /**
         * @brief       Do sync operation with timeout
         * @throw       @ref io::ex_timeout()
         * @param[in]   timeout The sync operation timeout
         */
        void run(const duration& timeout, std::function<void()> cleaner = nullptr)
        {
            this->restart();
            this->run_for(timeout);
            if (!this->stopped()) {  // io->stopped() means async operation execute success
                if (cleaner) {
                    cleaner();
                    asio::io_context::run();
                }
                throw ex_timeout();
            }
        }
    };
}  // namespace sync_io
#endif

sync_tcp_client.hpp

#ifndef __SYNC_TCP_CLIENT_HPP__
#define __SYNC_TCP_CLIENT_HPP__

#include <exception>
#include <boost/asio.hpp>
#include "sync_io.hpp"

#define MAX_RECV_LEN 1024    //recv byte max length

namespace sync_io {
    namespace asio = boost::asio;
    
    namespace tcp {
        //! Exception for connect failed
        class ex_tcp : public std::runtime_error
        {
        public:
            ex_tcp(std::error_code ec)
                : std::runtime_error("sync tcp client error occured: " + ec.message())
            {
            }
        };

        class client
        {
            using tcp     = asio::ip::tcp;
            using seconds = std::chrono::seconds;

        public:
            using endpoint = tcp::endpoint;

			//设置默认的timeout时间是1ms,如果不传入新的等待时间,则采用默认的1ms
            static constexpr duration TIMELESS = std::chrono::milliseconds(1);

        public:
            //! Default construct, you should call @a connect() by yourself after it
            client();
            /**
             * @brief   Construct and connect to server
             * @throw   @see connect()
             * @param   @see connect()
             */
            client(const endpoint& endpoint, const duration& timeout = TIMELESS);

            /**
             * @brief       Connect to server
             * @throw       @ref client::ex_tcp
             *              @ref client::ex_timeout
             * @param[in]   endpoint    Server's endpoint
             * @param[in]   timeout     Connect timeout
             */
            void connect(const endpoint& endpoint, const duration& timeout = TIMELESS);

            /**
             * @brief       Write message to server
             * @throw       @ref client::ex_tcp
             *              @ref client::ex_timeout
             * @param[in]   endpoint    Server's endpoint
             * @param[in]   timeout     Connect timeout
             */
            void write(const std::string& message, const duration& timeout = TIMELESS);

            /**
             * @brief       Write message to server
             * @throw       @ref client::ex_tcp
             *              @ref client::ex_timeout
             * @param[in]   u_int8_t    write address
             * @param[in]   write_len   write length
             * @param[in]   timeout     ms
             */
            void write_byte(const u_int8_t* in_address, int write_len,const duration& timeout = TIMELESS);

            /**
             * @brief       Read message from server until @a delimeter
             * @throw       @ref client::ex_tcp
             *              @ref client::ex_timeout
             * @param[in]   endpoint    Server's endpoint
             * @param[in]   timeout     Connect timeout
             * @return      Return the received data
             */
            std::string read_until(const std::string& delimeter,
                                   const duration&    timeout = TIMELESS);

            /**
             * @brief       Read some message from server
             * @throw       @ref client::ex_udp
             *              @ref client::ex_timeout
             * @param[out]  u_int8_t     read address
             * @param[out]  read_len     read data len[byte]
             * @param[in]   timeout     ms
             * @return      Return the received data
             */
            void read_some_byte(u_int8_t* out_address, size_t &read_len,const duration& timeout = TIMELESS);
            

        private:
            void run(const duration& timeout)
            {
                io_.run(timeout, [this] { socket_.close(); });
            }

        private:
            io          io_;
            tcp::socket socket_;
        };
    }  // namespace tcp
}  // namespace sync_io

#endif

sync_tcp_client.cpp


#include "sync_tcp_client.hpp"

using namespace std;

#define ASYNC_CALLBACK(ec)                                                                         \
    ({                                                                                             \
        if (ec)                                                                                    \
            throw sync_io::tcp::ex_tcp(ec);                                                        \
    })

namespace sync_io {
    namespace tcp {
        client::client()
            : socket_(io_)
        {
        }
        //构造函数
        client::client(const endpoint& endpoint, const duration& timeout)
            : client()
        {
            connect(endpoint, timeout);
        }

        void client::connect(const endpoint& endpoint, const duration& timeout)
        {
            socket_.async_connect(endpoint, [](error_code ec) { ASYNC_CALLBACK(ec); });
            run(timeout);
        }

        void client::write(const string& message, const duration& timeout)
        {
            asio::async_write(  //
                socket_, asio::buffer(message),
                [](error_code ec, size_t size) { ASYNC_CALLBACK(ec); }  //
            );
            run(timeout);
        }

        //写入一些字节
        void client::write_byte(const u_int8_t* in_address, int write_len,const duration& timeout)
        {
            socket_.async_send(asio::buffer(in_address,write_len),[](error_code ec, size_t size) { ASYNC_CALLBACK(ec); });
            run(timeout);
        }

		//读到delimeter分隔符结束
        string client::read_until(const string& delimeter, const duration& timeout)
        {
            string result;
            asio::async_read_until(                                    //
                socket_, asio::dynamic_buffer(result), delimeter,
                [](error_code ec, size_t len) { ASYNC_CALLBACK(ec); }  //
            );
            run(timeout);
            return result;
        }

        //读取一些字节
        void client::read_some_byte(u_int8_t* out_address, size_t &read_len,const duration& timeout)
        {
            socket_.async_receive(asio::buffer(out_address,MAX_RECV_LEN),[&](error_code ec, size_t len) { ASYNC_CALLBACK(ec);read_len=len;});
            run(timeout);
        }
    }  // namespace tcp
}  // namespace sync_io

sync_udp_client.hpp

#ifndef __SYNC_UDP_CLIENT_HPP__
#define __SYNC_UDP_CLIENT_HPP__

#include <exception>
#include <boost/asio.hpp>
#include "sync_io.hpp"

#define MAX_RECV_LEN 1024      //recv byte max length

namespace sync_io {
    namespace asio = boost::asio;

    namespace udp {
        //! Exception for connect failed
        class ex_udp : public std::runtime_error
        {
        public:
            ex_udp(std::error_code ec)
                : std::runtime_error("sync udp client error occured: " + ec.message())
            {
            }
        };

        class client
        {
            using udp     = asio::ip::udp;
            using seconds = std::chrono::seconds;

        public:
            using endpoint = udp::endpoint;

			//设置默认的timeout时间是1ms,如果不传入新的等待时间,则采用默认的1ms
            static constexpr duration TIMELESS = std::chrono::milliseconds(1);

        public:
            //! Default construct, you should call @a connect() after it
            client();
            /**
             * @brief   Construct and connect to server
             * @throw   @see connect()
             * @param   @see connect()
             */
            client(const endpoint& endpoint, const duration& timeout = TIMELESS);

            /**
             * @brief       Connect to server
             * @throw       @ref client::ex_udp
             *              @ref client::ex_timeout
             * @param[in]   endpoint    Server's endpoint
             * @param[in]   timeout     Connect timeout
             */
            void connect(const endpoint& endpoint, const duration& timeout = TIMELESS);

            /**
             * @brief       Write message to server
             * @throw       @ref client::ex_udp
             *              @ref client::ex_timeout
             * @param[in]   endpoint    Server's endpoint
             * @param[in]   timeout     Connect timeout
             */
            void write(const std::string& message, const duration& timeout = TIMELESS);

            /**
             * @brief       Write message to server
             * @throw       @ref client::ex_udp
             *              @ref client::ex_timeout
             * @param[in]   u_int8_t    write address
             * @param[in]   write_len   write length
             * @param[in]   timeout     ms
             */
            void write_byte(const u_int8_t* in_address, int write_len,const duration& timeout = TIMELESS);

            /**
             * @brief       Read some message from server
             * @throw       @ref client::ex_udp
             *              @ref client::ex_timeout
             * @param[in]   timeout     Connect timeout
             * @return      void
             */
            std::string read_some(const duration& timeout = TIMELESS);

            /**
             * @brief       Read some message from server
             * @throw       @ref client::ex_udp
             *              @ref client::ex_timeout
             * @param[out]  u_int8_t     read address
             * @param[out]  read_len     read data len[byte]
             * @param[in]   timeout     ms
             * @return      Return the received data
             */
            void read_some_byte(u_int8_t* out_address, size_t &read_len,const duration& timeout = TIMELESS);
            
        private:
            void run(const duration& timeout)
            {
                io_.run(timeout, [this] { socket_.close(); });
            }

        private:
            io          io_;
            udp::socket socket_;
        };
    }  // namespace udp
}  // namespace sync_io

#endif

sync_udp_client.cpp


#include "sync_udp_client.hpp"
#include <iostream>

using namespace std;

#define ASYNC_CALLBACK(ec)                                                                         \
    ({                                                                                             \
        if (ec)                                                                                    \
            throw sync_io::udp::ex_udp(ec);                                                        \
    })

namespace sync_io {
    namespace udp {
        client::client()
            : socket_(io_)
        {
        }
        //构造函数
        client::client(const endpoint& endpoint, const duration& timeout)
            : client()
        {
            connect(endpoint, timeout);
        }

        void client::connect(const endpoint& endpoint, const duration& timeout)
        {
            socket_.async_connect(endpoint, [](error_code ec) { ASYNC_CALLBACK(ec); });
            run(timeout);
        }

        void client::write(const string& message, const duration& timeout)
        {
            socket_.async_send(asio::buffer(message),[](error_code ec, size_t size) { ASYNC_CALLBACK(ec); });
            run(timeout);
        }

        void client::write_byte(const u_int8_t* in_address, int write_len,const duration& timeout)
        {
            socket_.async_send(asio::buffer(in_address,write_len),[](error_code ec, size_t size) { ASYNC_CALLBACK(ec); });
            run(timeout);
        }

        string client::read_some(const duration& timeout)
        {
            char buf[MAX_RECV_LEN] = {0};  //申请接受buffer,并清零
            socket_.async_receive(asio::buffer(buf,MAX_RECV_LEN),[](error_code ec, size_t len) { ASYNC_CALLBACK(ec); });
            run(timeout);
            return string(buf);
        }

        void client::read_some_byte(u_int8_t* out_address, size_t &read_len,const duration& timeout)
        {
            socket_.async_receive(asio::buffer(out_address,MAX_RECV_LEN),[&](error_code ec, size_t len) { ASYNC_CALLBACK(ec);read_len=len;});
            run(timeout);
        }
    }  // namespace udp
}  // namespace sync_io

2.2 应用层程序

  基于上述提供的tcp/udp通信类接口,可以通过创建sync_io::tcp::client或sync_io::udp::client对象实现上述通讯接口的调用。下面给出应用层面的tcp和udp client程序。
tcp_client.hpp

#ifndef __TCP_CLIENT_HPP__
#define __TCP_CLIENT_HPP__
#include <boost/asio.hpp>
#include "sync_tcp_client.hpp"

#define TCP_IP "192.168.2.128"    //定义应用层的tcp server ip,在target_endpoint()函数中使用
#define TCP_PORT 8080             //定义应用层的tcp server port,在target_endpoint()函数中使用

namespace tcpclient {
    namespace asio = boost::asio;
    /**
     * @brief   Client to vision server
     * @throw   runtime_error() in construct on connect failed
     *          runtime_error() in @ref request() on timeout or read/write failed
     */
    class client
    {
        static constexpr const char* DELIMETER = "}";   //read until delimeter

    public:
        using duration = std::chrono::steady_clock::duration;
        using endpoint = asio::ip::tcp::endpoint;

        client();

        void send_string(const std::string& message);        //string类型发送
        std::string recv_string(const duration& timeout);    //string类型接收
        void send_byte(const u_int8_t* in_address, int write_len); //byte[hex]类型发送
        void recv_byte(u_int8_t* out_address, size_t &read_len,const duration& timeout); //byte[hex]类型接收

    private:
        endpoint target_endpoint();     //get tcp server endpoint function
        
    private:
        sync_io::tcp::client client_;   //创建底层封装对象client_,以实现对底层tcp接口的调用
    };
}  // namespace tcpclient 
#endif

tcp_client.cpp

#include <exception>
#include "tcp_client.hpp"
#include <iostream>

using namespace std;

namespace tcpclient {
    client::client()
        : client_(target_endpoint(), 2s)
    {
    }

    /**
     * @brief       send string to tcp server
     * @throw       @see @ref async_read()
     * @param[in]   message   The request messag to be send
     * @return      void
     */
    void client::send_string(const std::string& message)
    {
        client_.write(message);   //调用write函数发送数据
    }

    /**
    * @brief       read string from tcp server
    * @throw       @see @ref async_read()
    * @param[in]   timeout   read timeout
    * @return      read result
    */
    std::string client::recv_string(const duration& timeout)
    {
        return client_.read_until(DELIMETER, timeout);
    }

    /**
     * @brief       send byte[hex] to tcp server
     * @throw       @see @ref async_read()
     * @param[in]   in_address send byte addreass
     * @param[in]   write_len send byte length
     * @return      void
     */
    void client::send_byte(const u_int8_t* in_address, int write_len)
    {
        client_.write_byte(in_address,write_len);
    }

    /**
     * @brief       read byte[hex] from tcp server
     * @throw       @see @ref async_read()
     * @param[in]   in_address send byte addreass
     * @param[in]   write_len send byte length
     * @return      void
     */
    void client::recv_byte(u_int8_t* out_address, size_t &read_len,const duration& timeout)
    {
        client_.read_some_byte(out_address,read_len,timeout);
    }

    //获取目标server的endpoint
    client::endpoint client::target_endpoint()
    {
        return {asio::ip::address::from_string(TCP_IP), TCP_PORT};
    }
}  // namespace tcpclient 

udp_client.hpp

#ifndef __UDP_CLIENT_HPP__
#define __UDP_CLIENT_HPP__
#include <boost/asio.hpp>
#include "sync_udp_client.hpp"

#define UDP_IP "192.168.2.128"		//定义应用层的udp server ip,在target_endpoint()函数中使用
#define UDP_PORT 8088				//定义应用层的udp server port,在target_endpoint()函数中使用

namespace udpclient {
    namespace asio = boost::asio;
    /**
     * @brief   Client to vision server
     * @throw   runtime_error() in construct on connect failed
     *          runtime_error() in @ref request() on timeout or read/write failed
     */
    class client
    {
        static constexpr const char* DELIMETER = "}";   //read until delimeter

    public:
        using duration = std::chrono::steady_clock::duration;
        using endpoint = asio::ip::udp::endpoint;

        client();

        void send_string(const std::string& message);         //string类型发送
        std::string recv_string(const duration& timeout);     //string类型接收
        void send_byte(const u_int8_t* in_address, int write_len);//byte[hex]类型发送
        void recv_byte(u_int8_t* out_address, size_t &read_len,const duration& timeout);//byte[hex]类型接收

    private:
        endpoint target_endpoint();    //get udp server endpoint function

    private:
        sync_io::udp::client client_;  //创建底层封装对象client_,以实现对底层udp接口的调用
    };
}  // namespace udpclient 
#endif

udp_client.cpp

#include <exception>
#include "udp_client.hpp"
#include <iostream>

using namespace std;

namespace udpclient {
    client::client()
        : client_(target_endpoint(), 2s)
    {
    }
    /**
     * @brief       send string to udp server
     * @throw       @see @ref async_read()
     * @param[in]   message   The request messag to be send
     * @return      void
     */
    void client::send_string(const std::string& message)
    {
        client_.write(message);   //调用write函数发送数据
    }

    /**
    * @brief       read string from udp server
    * @throw       @see @ref async_read()
    * @param[in]   timeout   read timeout
    * @return      read result
    */
    std::string client::recv_string(const duration& timeout)
    {
        return client_.read_some(timeout);
    }

    /**
     * @brief       send byte[hex] to udp server
     * @throw       @see @ref async_read()
     * @param[in]   in_address send byte addreass
     * @param[in]   write_len send byte length
     * @return      void
     */
    void client::send_byte(const u_int8_t* in_address, int write_len)
    {
        client_.write_byte(in_address,write_len);
    }

    /**
     * @brief       read byte[hex] from udp server
     * @throw       @see @ref async_read()
     * @param[in]   in_address send byte addreass
     * @param[in]   write_len send byte length
     * @return      void
     */
    void client::recv_byte(u_int8_t* out_address, size_t &read_len,const duration& timeout)
    {
        client_.read_some_byte(out_address,read_len,timeout);
    }

    //获取目标server的endpoint
    client::endpoint client::target_endpoint()
    {
        return {asio::ip::address::from_string(UDP_IP), UDP_PORT};
    }
}  // namespace vision

2.3 主函数

main.cpp

#include "tcp_client.hpp"
#include "udp_client.hpp"

int main(int argc,char** argv)
{
    try{
        /* tcp client test */
        tcpclient::client c;
        c.send_string("hello tcp server!");
        std::cout<<"tcp send string data success!"<<std::endl;
        std::string ret = c.recv_string(std::chrono::milliseconds(5000));
        std::cout<<"tcp recv string:"<<ret<<std::endl;

        unsigned char send_buf[] = {0x01,0x02,0x03,0x04};
        unsigned char recv_buf[100] = {0x0};
        size_t read_len;
        c.send_byte(send_buf,sizeof(send_buf));
        std::cout<<"tcp send byte data success!"<<std::endl;
        c.recv_byte(recv_buf,read_len,std::chrono::milliseconds(5000));
        std::cout<<"tcp recv byte len = "<<read_len<<",tcp recv data: ";
        for(int i = 0;i<read_len;i++)printf("%x ",recv_buf[i]);
        std::cout<<std::endl;

        /* udp client test */
        udpclient::client c1;
        c1.send_string("hello udp server!");
        std::cout<<"udp send string data success!"<<std::endl;
        std::string ret1 = c1.recv_string(std::chrono::milliseconds(5000));
        std::cout<<"udp recv string:"<<ret1<<std::endl;

        unsigned char send_buf1[] = {0x01,0x02,0x03,0x04};
        unsigned char recv_buf1[100] = {0x0};
        size_t read_len1;
        c1.send_byte(send_buf1,sizeof(send_buf1));
        std::cout<<"udp send byte data success!"<<std::endl;
        c1.recv_byte(recv_buf1,read_len1,std::chrono::milliseconds(5000));
        std::cout<<"udp recv byte len = "<<read_len1<<",udp recv data: ";
        for(int i = 0;i<read_len1;i++)printf("%x ",recv_buf1[i]);
        std::cout<<std::endl;
    }
    catch(std::exception& ex)
    {
        std::cout<<"execption:"<<ex.what()<<std::endl;
    }
    return -1;
}

三.程序编译

g++ main.cpp tcp_client.cpp udp_client.cpp sync_tcp_client.cpp sync_udp_client.cpp -o client -lpthread -lboost_system -std=c++17

四.程序执行

4.1 正常执行

  ./client执行程序,通过main.cpp程序中设置的timeout时间可知:std::chrono::milliseconds(5000),也就是最多等待5000ms来自server的响应,当超过5000ms仍未收到来自server的响应,则直接报错,报错类型为ex_tcp(ec)或ex_udp(ec)。
  下面是server在5000ms内正常响应的情况。
在这里插入图片描述

4.2 异常执行

  下面是tcp server在5000ms内未作出响应的情况。
在这里插入图片描述
  下面是udp server在5000ms内未作出响应的情况。
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tutu-hu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值