Python KCP协议实例
KCP协议介绍
- 官网介绍KCP是一个快速可靠协议,能以比 TCP浪费10%-20%的带宽的代价,换取平均延迟降低 30%-40%,且最大延迟降低三倍的传输效果。纯算法实现,并不负责底层协议(如UDP)的收发,需要使用者自己定义下层数据包的发送方式,以 callback的方式提供给 KCP。 连时钟都需要外部传递进来,内部不会有任何一次系统调用。
- kcp协议是传输层的一个具有可靠性的传输层ARQ协议。它的设计是为了解决在网络拥堵情况下tcp协议的网络速度慢的问题。kcp力求在保证可靠性的情况下提高传输速度。kcp协议的关注点主要在控制数据的可靠性和提高传输速度上面,因此kcp没有规定下层传输协议,一般用udp作为下层传输协议,kcp层协议的数据包在udp数据报文的基础上增加控制头。
KCP协议使用流程
1、创建 KCP对象:
123// 初始化 kcp对象,conv为一个表示会话编号的整数,和tcp的 conv一样,通信双// 方需保证 conv相同,相互的数据包才能够被认可,user是一个给回调函数的指针ikcpcb *kcp = ikcp_create(conv, user);2、设置回调函数:
123456789// KCP的下层协议输出函数,KCP需要发送数据时会调用它// buf/len 表示缓存和长度// user指针为 kcp对象创建时传入的值,用于区别多个 KCP对象int udp_output(const char *buf, int len, ikcpcb *kcp, void *user){....}// 设置回调函数kcp->output = udp_output;3、循环调用 update:
123// 以一定频率调用 ikcp_update来更新 kcp状态,并且传入当前时钟(毫秒单位)// 如 10ms调用一次,或用 ikcp_check确定下次调用 update的时间不必每次调用ikcp_update(kcp, millisec);4、输入一个下层数据包:
123// 收到一个下层数据包(比如UDP包)时需要调用:ikcp_input(kcp, received_udp_packet, received_udp_size);处理了下层协议的输出/输入后 KCP协议就可以正常工作了,使用 ikcp_send 来向 远端发送数据。而另一端使用 ikcp_recv(kcp, ptr, size)来接收数据。kcp源码流程图:
- 接收数据包流程:接收数据包时,UDP自带的recv函数收到的包,不断通过kcp_input喂给KCP,KCP会对这部分数据(KCP协议数据)进行解包,重新封装成应用层用户数据,应用层通过kcp_recv获取。
- 发送数据包流程:发送数据包时,应用层通过kcp_send发送数据,KCP会把用户数据拆分kcp数据包,通过kcp_output,以UDP(send)的方式发送。
- KCP会不停的进行update更新最新情况,数据的实际发送和接收数据都是在update时进行的。
Python KCP使用实例
- 想用Python调用KCP协议,发现并没有现成的实现,在github上找到一个用Cython包装的pyhton-kcp,却不支持Python3,于是做了简单的修改,使其支持Python3,地址如下:https://github.com/HatBoy/Python-KCP
- 下层使用UDP协议,KCP的实例代码如下:
Server端:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556#coding=UTF-8import socketimport timefrom lkcp import KcpObj# send回调函数,真正发送数据是是用该函数发送def kcp_callback(uid, data):s.sendto(data, uid_addr[uid])# 用该函数接收UDP数据def recv_udp(sock):try:data, udp_addr = sock.recvfrom(65535)uid_addr[1] = udp_addrreturn dataexcept Exception as e:passreturn None# socket连接s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)# 很重要一定要设置为非阻塞,不然程序会卡死在接受数据的地方s.setblocking(0)addr = ("127.0.0.1", 9999)s.bind(addr)# 存放连接客户端字典uid_addr = {}kcp = KcpObj(123, 1, kcp_callback)# 设置工作模式和窗口大小kcp.nodelay(1, 10, 2, 1)kcp.wndsize(128, 128)kcp.setmtu(1000)# 循环接收并发送数据while True:kcp.update(time.time())# 接收数据并吐给input函数while True:data = recv_udp(s)if data is None:breakkcp.input(data)# 接收数据将数据send给客户端kcp.update(time.time())while True:lens, data = kcp.recv()if lens < 0:breakprint("Recv: ", data)kcp.send(data)time.sleep(0.05)client端:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263import timeimport socketimport randomfrom lkcp import KcpObjstart_time = time.time()def getms():return int((time.time()-start_time)*1000)def kcp_callback(uid, data):s.sendto(data, addr)def recv_udp(sock):try:data, udp_addr = sock.recvfrom(65535)return dataexcept Exception as e:passreturn Nones = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)s.setblocking(0)addr = ("127.0.0.1", 9999)kcp = KcpObj(123, 1, kcp_callback)kcp.nodelay(1, 10, 2, 1)kcp.wndsize(128, 128)kcp.setmtu(1000)start_ts = getms() # 获取代码运行一直到此时的时间差slap = start_tswhile True:current = getms()kcp.update(time.time())while current >= slap: # 主动发送sleep()/100次data = str(random.randint(0, 10000))print("Send: ", data)kcp.send(data)slap += 20kcp.update(time.time())while True:data = recv_udp(s)if data is None:breakkcp.input(data)kcp.update(time.time())while True:lens, data = kcp.recv()if lens < 0:breakprint("Recv: ", data)kcp.send(data)time.sleep(0.05)运行Server端和Client端即可看到效果。