asio-kcp源码分析

asio kcp代码走读

(1)kcp_client_wrap类
a 提供方法接口如下:
    send_msg
        kcp_client_.send_msg(msg);
    stop //等待工作线程退出
    set_event_callback
    
    connect //main函数中调用connect进行kcp client的初始化
        kcp_client_.connect_async
        do_asio_kcp_connect_loop    
        
    connect_async
    connect_result
b 包含的私有方法:
start_workthread
client_event_callback_func
handle_client_event_callback

do_asio_kcp_connect_loop 
    while(){kcp_client_.update();} //主线程中kcp的tick

workthread_loop //工作线程入口函数

do_workthread_loop 
    while(){kcp_client_.update();} //工作线程中kcp的tick,kcp update时间间隔是KCP_UPDATE_INTERVAL 5ms
    
c 封装的成员变量有:
kcp_client kcp_client_;
相关状态变量
d kcp client创建实例:
asio_kcp::kcp_client_wrap net;
Client client;
net.set_event_callback(Client::client_event_callback, (void*)(&client));

int ret = net.connect(0, "127.0.0.1", 32323);
asio_kcp::millisecond_sleep(10);

net.send_msg(std::string("1234567890"));
asio_kcp::millisecond_sleep(510);
(2)kcp_client类
a 提供方法接口如下:
connect_async
    init_udp_connect 
    //设置udp连接的状态,in_connect_stage_置为true,获得当前时间作为connect_start_time_
    
send_msg //主线程调用此函数将待发送消息push到发送队列中,供工作线程使用
    send_msg_queue_.push(msg);

update
    do_asio_kcp_connect //向udp server发送“标志连接”的消息,并请求udp server端分配kcp conv
    
    //若udp连接成功 (connect_succeed_字段为true,当client的kcp初始化完成)
    do_send_msg_in_queue //一次全部取出发送队列中的消息,并调用ikcp_send,交给kcp模块进行处理
        while(){ikcp_send();}
    
    do_recv_udp_packet_in_loop
        recv 
        handle_udp_packet 
            is_disconnect_packet //是否断连消息
            ikcp_input //交给kcp将收到的udp消息由kcp消息格式转化为应用层消息格式
            while(1){recv_udp_package_from_kcp();}//
                //recv_udp_package_from_kcp()的内容:
                ikcp_recv //利用ikcp_recv获取经kcp解析(去掉头)后的应用层消息
            
    ikcp_update
        //距上次ts_flush超过一定阈值,则调用ikcp_flush,并更新kcp->ts_flush
        ikcp_flush //利用ikcp_output调用注册的回调函数将各类kcp消息(ack/数据/请求窗口/告知窗口)发送给udp server
    
b 包含的私有方法:
init_udp_connect
    //servaddr_内容填充,udp服务端ip、监听端口号、
    socket  //SOCK_DGRAM,建立udp socket,设置为非阻塞模式
    bind    //填充struct sockaddr_in bind_addr,利用bind绑定本地端口号
    connect(udp_socket_, &servaddr_,...) //

connect_timeout //kcp重连时间间隔 500ms
need_send_connect_packet
do_asio_kcp_connect
    do_send_connect_packet
    try_recv_connect_back_packet //
    

udp_output //静态方法,在kcp模块中被调用,下面方法所属的应用层类实例指针为kcp_client*
    ((kcp_client*)user)->send_udp_package(buf, len); //
        send//利用应用层创建的udp网络连接将kcp封装后的udp消息发送出去

send_udp_package
    send//利用应用层创建的udp网络连接将kcp封装后的udp消息发送出去

do_send_connect_packet
    making_connect_packet //udp client向udp server发送特定“标志连接”的消息,用于udp服务端记录udp client信息
        send
do_recv_udp_packet_in_loop

do_send_msg_in_queue

handle_udp_packet

try_recv_connect_back_packet
    recv //等待接收“send_back_conv_packet”,消息体内容:"asio_kcp_connect_back_package get_conv:"
    grab_conv_from_send_back_conv_packet //从udp服务端接收到上面消息后,解析出消息中包含的conv字段值(udp server分配)
    init_kcp(conv);//利用服务端发来的conv来初始化client端的kcp
    //状态置位,in_connect_stage_置为false,connect_succeed_置为true
    
init_kcp
    p_kcp_ = ikcp_create(conv, (void*)this); //创建kcp对象,并将应用层的上下文信息指针传递给kcp模块
    //注册应用层的udp发送消息的回调kcp_client::udp_output
    ikcp_nodelay(p_kcp_, 1, 2, 1, 1);//为kcp设置特定模式
c 封装的成员变量有:
in_connect_stage_; //udp连接状态

threadsafe_queue_mutex<std::string> send_msg_queue_;//线程安全的发送队列,主线程生产,工作线程消费
int udp_port_bind_;//本地udp端口号
std::string server_ip_;//udp服务器ip
int server_port_;//udp服务器监听端口号
int udp_socket_;//fd
char udp_data_[1024 * 4];//缓存区

ikcpcb* p_kcp_;//本client对应的kcp信息
(3)client_with_asio类,继承自: private boost::noncopyable基类 =========使用asio库的示例
a 提供方法接口如下:
b 包含的私有方法:
c 封装的成员变量有:
asio_kcp::kcp_client kcp_client_;
//几个定时器
boost::asio::deadline_timer client_timer_;
boost::asio::deadline_timer client_timer_send_msg_;

//几个整数vector (包含接收包的间隔的vector)

总结:udp客户端的逻辑

整体,udp连接的建立过程,和,kcp的初始化和创建过程:

1 udp client的主线程
connect //main函数中调用connect进行kcp client的初始化

    kcp_client_.connect_async
    
        init_udp_connect 
            //servaddr_内容填充,udp服务端ip、监听端口号、
            socket  //SOCK_DGRAM,建立udp socket,设置为非阻塞模式
            bind    //填充struct sockaddr_in bind_addr,利用bind绑定本地端口号
            connect(udp_socket_, &servaddr_,...) //
            
        //设置udp连接的状态,in_connect_stage_置为true,获得当前时间作为connect_start_time_
        
    do_asio_kcp_connect_loop    
        while(){kcp_client_.update();} //主线程中kcp的tick
            //kcp_client_.update()执行的操作
            
            do_asio_kcp_connect //向udp server发送“标志连接”的消息,并请求udp server端分配kcp conv
            
            //若udp连接成功 (connect_succeed_字段为true,当client的kcp初始化完成)
            do_send_msg_in_queue //一次全部取出发送队列中的消息,并调用ikcp_send,交给kcp模块进行处理
                while(){ikcp_send();}
            
            do_recv_udp_packet_in_loop
                recv 
                handle_udp_packet  
                    is_disconnect_packet //是否断连消息
                    ikcp_input //交给kcp将收到的udp消息由kcp消息格式转化为应用层消息格式
                    while(1){recv_udp_package_from_kcp();}//
                        //recv_udp_package_from_kcp()的内容:
                        ikcp_recv //利用ikcp_recv获取经kcp解析(去掉头)后的应用层消息
                        
                        //while中调用recv_udp_package_from_kcp后,又调用下面函数
                        (*pevent_func_)(p_kcp_->conv, eRcvMsg, msg, event_callback_var_); //pevent_func_在set_event_callback()中被注册,
                        //这里的示例是Client::client_event_callback(上层业务处理函数,传入的参数列表是:{kcp conv,udp消息类型eEventType类型,消息内容,event_func_var_(上层注册时传递的指针)})
                        //eEventType在udp client定义在kcp_client.hpp中
                        
                        
                    
            ikcp_update
                //距上次ts_flush超过一定阈值,则调用ikcp_flush,并更新kcp->ts_flush
                ikcp_flush //利用ikcp_output调用注册的回调函数将各类kcp消息(ack/数据/请求窗口/告知窗口)发送给udp server
    
    start_workthread //创建工作线程
2 udp client的主线程
workthread_loop
    do_workthread_loop
        while(){kcp_client_.update();} //工作线程中kcp的tick,kcp update时间间隔是KCP_UPDATE_INTERVAL 5ms
        
3 udp_output //静态方法,在kcp模块中被调用,下面方法所属的应用层类实例指针为kcp_client
((kcp_client
)user)->send_udp_package(buf, len); //
        send//利用应用层创建的udp网络连接将kcp封装后的udp消息发送出去
        
        
        

udp服务端

(1)connection_manager类,udp server管理多个udp client的管理类,继承自private boost::noncopyable, public std::enable_shared_from_this这两个基类,

a 提供方法接口如下:
connection_manager //构造函数,在udp server的main函数中系统初始化时进行调用

    hook_udp_async_receive //为udp server对应的监听udp socket创建回调connection_manager::handle_udp_receive_from(利用boost::bind进行绑定),和udp接收缓存区,以及udp网元信息udp_remote_endpoint_
    //udp_socket_用boost::asio::io_service io_service, udp::endpoint(boost::asio::ip::address::from_string(address), udp_port)实参进行初始化
    hook_kcp_timer //创建kcp定时器kcp_timer_

stop_all //断开所有udp client的连接
force_disconnect
    call_event_callback_func //调用该函数,将特定消息:"server force disconnect"传递给上层注册的回掉函数event_callback_
    connections_.remove_connection //断开
    

set_callback //将来自应用层上层的业务回调函数保存在该类的event_callback_成员变量中

send_msg //从connections_中根据kcp conv找到对应udp client的连接指针的智能指针管理类connection_ptr,并调用send_kcp_msg
    connections_.find_by_conv
    connection::send_kcp_msg
        ikcp_send //调用kcp接口将应用层消息传递给kcp模块,交给kcp模块进行kcp头的封装和其他处理

call_event_callback_func

send_udp_packet

get_cur_clock
    
b 包含的私有方法:
handle_udp_receive_from
    is_connect_packet //调用公共库中函数判断是否连接包,消息内容是:"asio_kcp_connect_package get_conv"
    handle_connect_packet //若是连接包,则调用该函数,
        udp_socket_.send_to //向udp client发送连接包的响应消息:"asio_kcp_connect_back_package get_conv: %kcp conv" 
        
        connections_.add_new_connection //并在此时,将udp client的网元信息保存在udp server中
        
    handle_kcp_packet //其他非连接包的udp client发来的包的处理
        ikcp_get_conv //从应用层收到的udp消息中解析出kcp conv
        connections_.find_by_conv //利用conv找到udp client的网元连接信息
        
        connection::input//获取当前时间戳,更新last_packet_recv_time_,并更新udp client的网元信息udp_remote_endpoint_;并调用kcp接口将应用层接收到的udp消息,交给kcp模块处理,
        
            ikcp_input  //交给kcp将收到的udp消息由kcp消息格式转化为应用层消息格式
            
            //调用ikcp_recv从kcp模块获取解析后的应用层消息,并进行相关处理
            ikcp_recv

hook_udp_async_receive

handle_kcp_time //该函数是kcp定时器kcp_timer_的超时回调,每隔5ms调用一次
    hook_kcp_timer
    connections_.update_all_kcp(cur_clock_); //更新所有udp client的kcp tick
    
hook_kcp_timer //设置kcp定时器的超时时间5ms和异步回调函数connection_manager::handle_kcp_time

handle_connect_packet

handle_kcp_packet
c 封装的成员变量有:
stopped_ //udp server正常工作的标志,bool类型
std::function<event_callback_t> event_callback_; //应用层回调

udp::endpoint udp_remote_endpoint_;  //udp server测的udp连接的网元信息
udp::socket udp_socket_; //udp server测的udp socket信息

char udp_data_[1024 * 32];//udp server测的udp应用层接收缓存区,udp_packet_max_length = 1080((576-8-20 - 8) * 2)

connection_container connections_; //包含了各个udp client的kcp、udp连接网元等信息
(2)connection_container类,继承自private boost::noncopyable
a 提供方法接口如下:
get_new_conv //udp server为多个udp client分配全局唯一kcp conv,从1000递增一个static全局变量
    
update_all_kcp//遍历多个udp client的map,对每个调用下面函数
    connection_manager::update_kcp
        ikcp_update
            //距上次ts_flush超过一定阈值,则调用ikcp_flush,并更新kcp->ts_flush
            ikcp_flush //利用ikcp_output调用注册的回调函数将各类kcp消息(ack/数据/请求窗口/告知窗口)发送给udp server
            
    connection_manager::do_timeout //超时后将该udp client从map中删掉
    
add_new_connection //利用传入的kcp conv和udp client信息udp::endpoint udp_sender_endpoint来本地维护多个udp client的信息
    connection::create //传入connection_manager指针manager_ptr、kcp conv、udp_sender_endpoint,将manager_ptr维护在智能指针中
        connection::init_kcp
        connection::set_udp_remote_endpoint
b 包含的私有方法:
c 封装的成员变量有:
std::unordered_map<kcp_conv_t, connection::shared_ptr> connections_; //每个kcp标志(conv)和每个udp client连接之间的哈希map
(3)connection类,udp server用于管理每一个udp client的信息,继承自private boost::noncopyable
a 提供方法接口如下:
is_timeout
do_timeout

udp_output //kcp直接调用的应用层注册的回调
    ((connection*)user)->send_udp_package(buf, len);
    
send_kcp_msg
    ikcp_send //调用kcp接口将应用层消息传递给kcp模块,交给kcp模块进行kcp头的封装和其他处理
    
input //获取当前时间戳,更新last_packet_recv_time_,并更新udp client的网元信息udp_remote_endpoint_;并调用kcp接口将应用层接收到的udp消息,交给kcp模块处理,
    ikcp_input 
    
    //调用ikcp_recv从kcp模块获取解析后的应用层消息,并进行相关处理
    ikcp_recv
    
update_kcp
    ikcp_update
b 包含的私有方法:
init_kcp //同udp client的init_kcp的实现
    ikcp_create
    //注册的回调函数是connection::udp_output
    ikcp_nodelay(p_kcp_, 1, 5, 1, 1);

udp_output
send_udp_package
    connection_manager.send_udp_packet

get_cur_clock
get_timeout_time
c 封装的成员变量有:
std::weak_ptr<connection_manager> connection_manager_weak_ptr_; 
kcp_conv_t conv_; //本udp连接的标志:kcp conv
ikcpcb* p_kcp_;
udp::endpoint udp_remote_endpoint_; //udp client的网元信息
uint32_t last_packet_recv_time_; //上次从udp client收到包的时间戳
(4)server类
a 提供方法接口如下:
server //构造函数,
//添加各个信号SIGINT、SIGTERM、SIGQUIT到signals_,并注册信号处理的回调函数server::handle_stop
//注册应用层的正常事件回调server::event_callback
    connection_manager::set_callback //将来自应用层上层的业务回调函数server::event_callback保存在该类的event_callback_成员变量中
    
run
    io_service_.run();//阻塞,直到所有的异步操作完成,
b 包含的私有方法:
handle_stop
event_callback //被调用的地方:connection_manager::call_event_callback_func
    //若消息类型是kcp_svr::eRcvMsg,则调用下面函数
    kcp_server_.send_msg(conv, msg);
        connection_manager::send_msg //从connections_中根据kcp conv找到对应udp client的连接指针的智能指针管理类connection_ptr,并调用send_kcp_msg
            connections_.find_by_conv
            connection::send_kcp_msg
                ikcp_send //调用kcp接口将应用层消息传递给kcp模块,交给kcp模块进行kcp头的封装和其他处理
hook_test_timer
handle_test_timer
test_force_disconnect
c 封装的成员变量有:

boost::asio::io_service io_service_; //用作执行异步操作

boost::asio::signal_set signals_;//用于注册各种进程终止通知函数

bool stopped_;

kcp_svr::server kcp_server_; //管理了各个udp client连接信息的udp+kcp管理类

boost::asio::deadline_timer test_timer_;

udp server端的示例main函数

main

 server s(argv[1], argv[2]); //初始化udp server,构造函数,
    //添加各个信号SIGINT、SIGTERM、SIGQUIT到signals_,并注册信号处理的回调函数server::handle_stop
    //注册应用层的正常事件回调server::event_callback

    new connection_manager //调用构造函数connection_manager,在udp server的main函数中系统初始化时进行调用

        hook_udp_async_receive //为udp server对应的监听udp socket创建回调connection_manager::handle_udp_receive_from(利用boost::bind进行绑定),和udp接收缓存区,以及udp网元信息udp_remote_endpoint_
        //udp_socket_用boost::asio::io_service io_service, udp::endpoint(boost::asio::ip::address::from_string(address), udp_port)实参进行初始化
        hook_kcp_timer //创建kcp定时器kcp_timer_
        
    connection_manager::set_callback //将来自应用层上层的业务回调函数server::event_callback保存在该类的event_callback_成员变量中
        event_callback_=//保存在本地
        
        
 s.run();
 
    handle_udp_receive_from //当udp server socket收到消息时触发调用,从接收包中解析出kcp conv,进而判断是属于哪个udp client的消息
    
        is_connect_packet //调用公共库中函数判断是否连接包,消息内容是:"asio_kcp_connect_package get_conv"
        handle_connect_packet //若是连接包,则调用该函数,
            udp_socket_.send_to //向udp client发送连接包的响应消息:"asio_kcp_connect_back_package get_conv: %kcp conv" 
            
            connections_.add_new_connection //并在此时,将udp client的网元信息保存在udp server中
            
        handle_kcp_packet //其他非连接包的udp client发来的包的处理
            ikcp_get_conv //从应用层收到的udp消息中解析出kcp conv
            connections_.find_by_conv //利用conv找到udp client的网元连接信息
            
            connection::input//获取当前时间戳,更新last_packet_recv_time_,并更新udp client的网元信息udp_remote_endpoint_;并调用kcp接口将应用层接收到的udp消息,交给kcp模块处理,
            
                ikcp_input  //交给kcp将收到的udp消息由kcp消息格式转化为应用层消息格式
                
                //调用ikcp_recv从kcp模块获取解析后的应用层消息,并进行相关处理
                ikcp_recv
                
                connection_manager::call_event_callback_func //调用上层业务注册的回调,这里的示例是,
                    event_callback_ //被调用的地方:connection_manager::call_event_callback_func
                    
                        //若消息类型是kcp_svr::eRcvMsg,则调用下面函数
                        kcp_server_.send_msg(conv, msg);
                            connection_manager::send_msg //从connections_中根据kcp conv找到对应udp client的连接指针的智能指针管理类connection_ptr,并调用send_kcp_msg
                                connections_.find_by_conv
                                connection::send_kcp_msg
                                    ikcp_send //调用kcp接口将应用层消息传递给kcp模块,交给kcp模块进行kcp头的封装和其他处理
                    
                
在udp client和udp server之间的几种不同udp消息类型:
服务端定义在kcp_typedef.hpp中 /server_lib/
    enum eEventType
    {
        eConnect,
        eDisconnect,
        eRcvMsg,
        eLagNotify,

        eCountOfEventType
    };

转载于:https://www.cnblogs.com/studyofadeerlet/p/11469551.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
KCP 是一个快速可靠协议,能以比 TCP浪费10%-20%的带宽的代价,换取平均延迟降低 30%-40%,且最大延迟降低三倍的传输效果。纯算法实现,并不负责底层协议(如UDP) 的收发,需要使用者自己定义下层数据包的发送方式,以 callback的方式提供给 KCP。 连时钟都需要外部传递进来,内部不会有任何一次系统调用。 整个协议只有 ikcp.h, ikcp.c两个源文件,可以方便的集成到用户自己的协议栈中。 也许你实现了一个P2P,或者某个基于 UDP的协议,而缺乏一套完善的ARQ可靠协议实现, 那么简单的拷贝这两个文件到现有项目中,稍微编写两行代码,即可使用。 技术特性 TCP是为流量设计的(每秒内可以传输多少KB的数据),讲究的是充分利用带宽。而KCP 是为流速设计的(单个数据包从一端发送到一端需要多少时间),以10%-20%带宽浪费 的代价换取了比 TCP快30%-40%的传输速度。TCP信道是一条流速很慢,但每秒流量很大 的大运河,而KCP是水流湍急的小激流。KCP有正常模式和快速模式两种,通过以下策略 达到提高流速的结果: RTO翻倍vs不翻倍: TCP超时计算是RTOx2,这样连续丢三次包就变成RTOx8了,十分恐怖,而KCP启动快速   模式后不x2,只是x1.5(实验证明1.5这个值相对比较好),提高了传输速度。 选择性重传 vs 全部重传: TCP丢包时会全部重传从丢的那个包开始以后的数据,KCP是选择性重传,只重传真正   丢失的数据包。 快速重传: 发送端发送了1,2,3,4,5几个包,然后收到远端的ACK: 1, 3, 4, 5,当收到ACK3时,   KCP知道2被跳过1次,收到ACK4时,知道2被跳过了2次,此时可以认为2号丢失,不用   等超时,直接重传2号包,大大改善了丢包时的传输速度。 延迟ACK vs 非延迟ACK: TCP为了充分利用带宽,延迟发送ACK(NODELAY都没用),这样超时计算会算出较大   RTT时间,延长了丢包时的判断过程。KCP的ACK是否延迟发送可以调节。 UNA vs ACK UNA: ARQ模型响应有两种,UNA(此编号前所有包已收到,如TCP)和ACK(该编号包已收到   ),光用UNA将导致全部重传,光用ACK则丢失成本太高,以往协议都是二选其一,而   KCP协议中,除去单独的 ACK包外,所有包都有UNA信息。 非退让流控: KCP正常模式同TCP一样使用公平退让法则,即发送窗口大小由:发送缓存大小、接收   端剩余接收缓存大小、丢包退让及慢启动这四要素决定。但传送及时性要求很高的小   数据时,可选择通过配置跳过后两步,仅用前两项来控制发送频率。以牺牲部分公平   性及带宽利用率之代价,换取了开着BT都能流畅传输的效果。 基本使用 创建 KCP对象: // 初始化 kcp对象,conv为一个表示会话编号的整数,和tcp的 conv一样,通信双 // 方需保证 conv相同,相互的数据包才能够被认可,user是一个给回调函数的指针 ikcpcb *kcp = ikcp_create(conv, user); 设置回调函数: // KCP的下层协议输出函数,KCP需要发送数据时会调用它 // buf/len 表示缓存和长度 // user指针为 kcp对象创建时传入的值,用于区别多个 KCP对象 int udp_output(const char *buf, int len, ikcpcb *kcp, void *user) {  .... } // 设置回调函数 kcp->output = udp_output; 循环调用 update: // 以一定频率调用 ikcp_update来更新 kcp状态,并且传入当前时钟(毫秒单位) // 如 10ms调用一次,或用 ikcp_check确定下次调用 update的时间不必每次调用 ikcp_update(kcp, millisec); 输入一个下层数据包: // 收到一个下层数据包(比如UDP包)时需要调用: ikcp_input(kcp, received_udp_packet, received_udp_size); 处理了下层协议的输出/输入后 KCP协议就可以正常工作了,使用 ikcp_send 来向 远端发送数据。而另一端使用 ikcp_recv(kcp, ptr, size)来接收数据。 协议配置 协议默认模式是一个标准的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值