zoukankan      html  css  js  c++  java
  • boost.asio包装类st_asio_wrapper开发教程(转)

    一:什么是st_asio_wrapper
    它是一个c/s网络编程框架,基于对boost.asio的包装(最低在boost-1.49.0上调试过),目的是快速的构建一个c/s系统;

    二:st_asio_wrapper的特点
    效率高、跨平台、完全异步,当然这是从boost.asio继承而来;
    自动重连,数据透明传输,自动解决分包粘包问题(这一特性表现得与udp一样);
    只支持tcp和udp协议;

    三:st_asio_wrapper的大体结构
    st_asio_wrapper.h:
    编译器版本测试,更新日志等;

    st_asio_wrapper_base.h:
    存放一些全局类、函数、宏以及日志输出等;

    st_asio_wrapper_timer.h(class st_timer):
    定时器类,以下类均需要,除了打包解包器;

    st_asio_wrapper_socket.h(class st_socket):
    st_tcp_socket和st_udp_socket的基类,主要负责消息派发相关功能;

    st_asio_wrapper_tcp_socket.h(class st_tcp_socket):
    tcp套接字类,用于tcp数据的收发,从st_socket继承;

    st_asio_wrapper_udp_socket.h(class st_udp_socket):
    udp套接字类,用于udp数据的收发,从st_socket继承;

    st_asio_wrapper_packer.h(interface i_packer, class packer):
    i_packer是打包器的接口,packer是st_asio_wrapper自带的打包器;

    st_asio_wrapper_unpacker.h(interface i_unpacker, class unpacker):
    i_unpacker是解包器的接口,unpacker是st_asio_wrapper自带的解包器;

    st_asio_wrapper_service_pump.h(interface i_service, class st_service_pump):
    asio的io_servie包装类,用于运行st_asio_wrapper的所有service对象(st_server_base, st_sclient, st_tcp_client_base, st_udp_client_base);

    st_asio_wrapper_object_pool.h(class st_object_pool):
    对象池,从原来的st_server_base抽象出来,供st_server_base和st_client继承,于是乎,服务端和客户端(支持多条连接的)都有了对象池功能;

    st_asio_wrapper_server.h(class st_server_base):
    st_server是服务端的服务对象类,用于服务端的监听、接受客户端请求等,需要实现i_server接口;

    st_asio_wrapper_server_socket.h(interface i_server, class st_server_socket):
    前者用于从st_server获取必要的信息,和调用必要的接口;后者从st_tcp_socket继承,用于服务端的数据收发;

    st_asio_wrapper_connector.h(class st_connector):
    从st_tcp_socket继承,实现连接服务器(包括重连)逻辑;

    st_asio_wrapper_client.h(class st_sclient_base, class st_client_base):
    client相关的公共逻辑,比如遍历等,被st_tcp_client_base和st_udp_client_base继承;

    st_asio_wrapper_tcp_client.h(class st_tcp_sclient, class st_tcp_client_base):
    前者从st_connector继承,只支持一条连接,后者支持任意多条连接,他们实现了一些逻辑,以便被st_service_pump调用;

    st_asio_wrapper_udp_client.h(class st_udp_sclient, class st_udp_client_base):
    基于udp套接字的service对象;

    源代码及例程下载地址:
    命令行:svn checkout http://st-asio-wrapper.googlecode.com/svn/trunk/ st-asio-wrapper-read-only
    如果从svn客户端界面上打开,则只输入http://st-asio-wrapper.googlecode.com/svn/trunk/到地址栏即可
    git:https://github.com/youngwolf-project/st_asio_wrapper/,另外,我的资源里面也有下载,但不是最新的。
    QQ交流群:198941541
    其中include文件夹里面是st_asio_wrapper的所有类、asio_client和asio_server配合用于演示最简单的数据收发、file_server和file_client用于演示文件传送、test_client和asio_server配合用于性能测试、udp_client演示udp通信,他们都基于st_asio_wrapper,可以看成是sample;

            类库(包括demo)在vc下有几个警告,均可安全忽略;
            类库里面大量使用了c++0x特性,主要有:range-based for loop、lambda表达式、nullptr、auto、右值引用、泛型begin()和end()等;因此至少需要vc2010及其以上版本的编译器,或者gcc4.6以上;
            在vc2010和gcc4.6之前的编译器版本中,请使用兼容版本,在compatiable_edition文件夹里面(注意兼容版本的效率并不比普通版本低,甚至略高);
            推荐在能用的情况下,还是用普通版本(或者说标准版本,这是相对于兼容版本而言的),虽然效率没有提高,但你用在一个复杂的工程中时,可能普通版本效率会高,因为相同的代码下,c++0x的效率要普遍的高一些,在st_asio_wrapper里面没有表现出来,是因为我特别的对兼容版本做过优化(而且我的使用也有限,比如有些无法优化的地方,我刚好不需要使用,就躲过去了);
            需要开发者自己编译boost库,大概需要boost.system和boost.thread两个库。

    五:开发教程(客户端)
    客户端直接#include st_asio_wrapper_client.h,就可实现一个简单的客户端了,如下:

    1. //configuration  
    2. #define SERVER_PORT     9527  
    3. #define FORCE_TO_USE_MSG_RECV_BUFFER //force to use the msg recv buffer  
    4. //configuration  
    5.   
    6. #include "st_asio_wrapper_client.h"  
    7. using namespace st_asio_wrapper;  
    8.   
    9. #define QUIT_COMMAND    "quit"  
    10. #define RESTART_COMMAND "restart"  
    11. #define RECONNECT_COMMAND "reconnect"  
    12. #define SUSPEND_COMMAND "suspend"  
    13. #define RESUME_COMMAND  "resume"  
    14.   
    15. int main() {  
    16.     std::string str;  
    17.     st_service_pump service_pump;  
    18.     st_sclient client(service_pump);  
    19.     //there is no corresponding echo client demo as server endpoint  
    20.     //because echo server with echo client made dead loop, and occupy almost all the network resource  
    21.   
    22.     client.set_server_addr(SERVER_PORT + 100, SERVER_IP);  
    23. //  client.set_server_addr(SERVER_PORT, "::1"); //ipv6  
    24. //  client.set_server_addr(SERVER_PORT, "127.0.0.1"); //ipv4  
    25.   
    26.     service_pump.start_service(1);  
    27.     while(service_pump.is_running())  
    28.     {  
    29.         std::cin >> str;  
    30.         if (str == QUIT_COMMAND)  
    31.             service_pump.stop_service();  
    32.         else if (str == RESTART_COMMAND)  
    33.         {  
    34.             service_pump.stop_service();  
    35.             service_pump.start_service(1);  
    36.         }  
    37.         else if (str == RECONNECT_COMMAND)  
    38.             client.graceful_close(true);  
    39.         //the following two commands demonstrate how to suspend msg sending, no matter recv buffer been used or not  
    40.         else if (str == SUSPEND_COMMAND)  
    41.             client.suspend_send_msg(true);  
    42.         else if (str == RESUME_COMMAND)  
    43.             client.suspend_send_msg(false);  
    44.         else  
    45.             client.safe_send_msg(str);  
    46.     }  
    47. }  
    48. //restore configuration  
    49. #undef SERVER_PORT  
    50. #undef FORCE_TO_USE_MSG_RECV_BUFFER //force to use the msg recv buffer  
    51. //restore configuration  

    以上例子中,客户端从控制台接收数据,调用safe_send_msg发送数据;当收到数据时,会输出到控制台(st_tcp_socket或者st_udp_socket实现);

            其中,start_service开启服务,stop_service结束服务(退出时必须明确调用),is_running判断服务的运行状态;如果想修改服务端地址,则在调用start_service之前调用set_server_addr函数;
            stop_service之后,可再次调用start_service开启服务;
            不stop_service而直接想重连接,则以true调用st_connector的force_close或者graceful_close;
            当然,一般来说,客户端不会只把收到的数据显示到控制台这么简单,此时需要从st_tcp_sclient或者st_udp_sclient派生一个类(如果你有多条连接,则从st_connector或者st_udp_socket派生一个类,并用st_tcp_client或者st_udp_client来管理它,这个请参考test_client这个demo,它支持多条连接),然后有两种方法供你选择,一:重写on_msg并在里面直接返回true(或者定义FORCE_TO_USE_MSG_RECV_BUFFER宏,至于为什么要这样,你需要看完所有st_asio_wrapper的一系列教程共四篇),然后再重写on_msg_handle并在里面做消息处理;二:重写on_msg,并在里面做消息处理,然后返回false。这两种方式的区别是:前者不会阻塞消息的接收,后者会,比如你的消息处理需要1秒,那么在这1秒钟之内,前者与接收消息并行,后者则不能接受消息,直到消息处理结束。另一个区别是:前者需要接收缓存,后者不需要。当然,这里说的阻塞,是指对当前套接字的阻塞,与其它套接字无关,比如在服务端,有两个套接字,当你处理套接字1的消息时,无论你用前面的哪一种方法,都不可能阻塞套接字2的消息接收。关于这个话题,请参看教程第四篇
            在消息发送成功之后,你有一次机会得到通知,那就是重写on_msg_send函数,它的参数就是这个消息(注意,是打包后的);
            每调用一次send_msg或者safe_send_msg,对方必定收到一次一模一样的数据,二次开发者不用再考虑分包粘包问题,这一特点你完全可以把它当成udp的行为;
            st_socket里面有发送消息缓存,所以当二次开发者有数据需要发送的时候,可以随时调用send_msg(注意缓存满的问题,safe_send_msg保证数据发送成功,当缓存满时会等待)而不用关心当前连接是否已经建立,它会在条件适当的时候,为你发送数据。

    你可能需要修改的宏有以下几类:
    1.全局宏,服务端客户端均需要:
            UNIFIED_OUT_BUF_NUM:unified_out类使用的缓存空间大小,默认2048;
            MAX_MSG_LEN:消息最大长度(打包后的,拿默认的packer来说,它最大仅支持3998消息长度,因为还有一个2字节的包头),默认4000。对于默认打包器解包器,这个长度范围只能是1至(65535-2),因为包头只用了两个字节的缘故,如果想要超过这个限制,只能重写打包解包器(如果仅仅是扩展包头长度,这个重写将非常简单);
            MAX_MSG_NUM:每个st_socket消息缓存最大消息条数(包括消息接收和发送缓存),默认1024;
            ENHANCED_STABILITY:增强的健壮性,如果开启这个宏,所有service对象都会在最外层(io_service.run)包一层try catch,以增加健壮性,当然,增加try是会有效率损失的,本宏默认不开启;
            FORCE_TO_USE_MSG_RECV_BUFFER:始终使用消息接收缓存,这个是编译期优化,前面说了,on_msg()返回true代表使用消息接收缓存,如果你的on_msg()永远返回true,则可以开启这个宏,那么st_tcp_socket或者st_udp_socket将不再调用on_msg()(根本不存在这个虚函数了)而是直接将消息放入消息接收缓存,这样可以提高一些效率,默认不开启本宏;
            NO_UNIFIED_OUT:让st_asio_wrapper里面的所有输出失效;
            ST_SERVICE_THREAD_NUM:IO线程数量,用于运行io_service.run函数。
            MAX_OBJECT_MAX:对象池最多支持的对象数量,默认4096;
            REUSE_OBJECT:是否开启对象池,如果开启,当创建新对象时,将尝试使用已经被关闭的对象,此时将不会自动定时的释放被关闭的对象链表,请参看SOCKET_FREE_INTERVAL宏,默认不开启本宏;
            SOCKET_FREE_INTERVAL:说这个宏之前,要说一下st_object_pool的内部工作原理:当调用del_object的时候(st_server_socket在on_recv_error里面的默认行为),st_object_pool的作法并不是删除这个对象,而是把这个对象移动到另外一个链表里面,这个链表里面的对象会被每SOCKET_FREE_INTERVAL秒遍历一次,以找到已经关闭超过CLOSED_SOCKET_MAX_DURATION秒钟的对象,然后真正的从内存中释放它,原因是当del_object的时候,可能还有其它的异步操作还没完成,或者完成了,但还在队列中没有被分发,如果此时从内存中释放对象,那么后面的异步回调的时候,可能会出现内存访问越界。这个问题可以通过为每一个异步调用都绑定一个指向本对象this指针的shared_ptr做为参数,但过度使用shared_ptr会影响到效率。单位为秒,默认10秒。如果开启了REUSE_OBJECT,则本宏根本不使用(也就不存在定时遍历了),因为被关闭的对象已经放入对象池,等着被重用,不能释放;
            AUTO_CLEAR_CLOSED_SOCKET:服务端是否定时的循环的调用clear_all_closed_object()以清除已经被关闭的套接字,如果你不方便调用del_object,则对象池里面将会累积越来越多的被关闭了的对象(如果有连接出错,或者退出的话),你可以打开这个宏,让st_object_pool为你定时的做这些清除工作;注意,如果对象链表非常大,遍历链表是会影响效率的;遍历的时间间隔由CLEAR_CLOSED_SOCKET_INTERVAL定义,默认不开启本宏;
            CLEAR_CLOSED_SOCKET_INTERVAL:定时调用clear_all_closed_object()间隔,单位为秒,默认60秒,如果未开启AUTO_CLEAR_CLOSED_SOCKET,则本宏根本不使用(也就不存在定时调用clear_all_closed_object()了);
            CLOSED_SOCKET_MAX_DURATION:关闭连接多少秒钟之后,可以安全释放对象或者重用对象(取决于REUSE_OBJECT是否被定义,定义了就是重用,否则释放),默认为5秒。

    2.tcp客户端专用宏:
            GRACEFUL_CLOSE_MAX_DURATION:优雅关闭地,最长等待时间,单位为秒,默认5。
            SERVER_IP:服务器IP地址,用字符串形式表示,默认"127.0.0.1";
            SERVER_PORT:服务器端口,默认5050;
            RE_CONNECT_INTERVAL:当连接服务器失败时,延时多长时间重连,单位是毫秒,默认500毫秒;
            RE_CONNECT_CONTROL:打开此宏之后,可以控制重连接次数,运行时也可修改。

    3.tcp服务端专用宏:
            SERVER_PORT:服务器端口(服务器IP如果要设置的话,只能调用set_server_addr接口,不能通过宏来实现),默认5050;
            TCP_DEFAULT_IP_VERSION:在不指定服务端IP时,通过这个宏指定IP协议的版本(v4还是v6,取值分别是tcp::v4()和tcp::v6()),如果指定了IP,则版本从ip地址中分析得来;
            ASYNC_ACCEPT_NUM:最多同时投递多少个异步accept调用,默认1;
            NOT_REUSE_ADDRESS:关闭端口重用,udp也用使用此宏

    4.udp客户端专用宏:
            UDP_DEFAULT_IP_VERSION:在不指定IP时,通过这个宏指定IP协议的版本(v4还是v6,取值分别是udp::v4()和udp::v6()),如果指定了IP,则版本从ip地址中分析得来;

    以上宏都可以按工程为单位来修改,你只需要在include相应st_asio_wrapper相关头文件之前,定义这些宏即可,具体例子demo里面都有。

  • 相关阅读:
    Constants and Variables
    随想
    C#基础篇之语言和框架介绍
    Python基础19 实例方法 类方法 静态方法 私有变量 私有方法 属性
    Python基础18 实例变量 类变量 构造方法
    Python基础17 嵌套函数 函数类型和Lambda表达式 三大基础函数 filter() map() reduce()
    Python基础16 函数返回值 作用区域 生成器
    Python基础11 List插入,删除,替换和其他常用方法 insert() remove() pop() reverse() copy() clear() index() count()
    Python基础15 函数的定义 使用关键字参数调用 参数默认值 可变参数
    Python基础14 字典的创建修改访问和遍历 popitem() keys() values() items()
  • 原文地址:https://www.cnblogs.com/lancidie/p/3392511.html
Copyright © 2011-2022 走看看