什么是KCP

什么是KCP一 什么是 KCPKCP 是一种网络传输协议 AFastandReli 可以视它为 TCP 的代替品 但是它运行于用户空间 它不管底层的发送与接收 只是个纯算法实现可靠传

大家好,欢迎来到IT知识分享网。

TCP的特点是可靠传输(累积确认、超时重传、选择确认)、流量控制(滑动窗口)、拥塞控制(慢开始、拥塞避免、快重传、快恢复)、面向连接。KCP对这些参数基本都可配,也没用建立/关闭连接的过程。

其实KCP并不神秘,因为TCP的高度自治(很多东西都不可配),满足不了如今各种速度需求。而KCP就是基于UDP协议,再将一些TCP经典的机制移植过来,变成参数可配。

第一步,就是创建一个kcp实例,相当于一个句柄。

ikcpcb* ikcp_create(IUINT32 conv, void *user) 

第二步,设置发送数据的接口,底层用哪种socket都没问题,只要能把数据发送出去,建议使用UDP,比较简单。

int output(const char *buf, int len, ikcpcb *kcp, void *user) 

第三步,更新KCP状态。KCP运行于用户空间,所以需要手动去更新每个实例的状态,其实主要就是检测哪些数据包该重传了。

void ikcp_update(ikcpcb *kcp, IUINT32 current) 

第四步,发送数据。调用ikcp_send之后,KCP最后会使用上面设置的output函数来将发送数据(KCP自己并不关心如何发送数据)。

int ikcp_send(ikcpcb *kcp, const char *buffer, int len) 

第五步,预接收数据。先手动预接收数据,然后再调用ikcp_input将裸数据交给KCP,这些数据有可能是KCP控制报文,并不是我们要的数据。

int ikcp_input(ikcpcb *kcp, const char *data, long size) 

第六步,接收数据。此时收到的数据才是真正的数据,重组操作在调用ikcp_recv之前就完成了。

int ikcp_recv(ikcpcb *kcp, char *buffer, int len) 

总体上还是容易理解的,以前我们是直接使用各种socket和对端通信,各种功能由自己控制。现在是在socket之上使用了一个中间件KCP,帮忙实现快速可靠传输功能。注意一下KCP有模式的区分,不同模式下的速度表现不一样,建议把参数配好之后再使用,否则使用的都是默认的参数。

工作模式

int ikcp_nodelay(ikcpcb *kcp,int nodelay,int interval,int resend,int nc);

最大窗口:int ikcp_wndsize(ikcpcb *kcp,int sndwnd,int rcvwnd);该调用将会设置协议的最大发送窗口和最大接收窗口大小,默认为32。

最大传输单元:纯算法协议并不负责探测 MTU,默认 mtu是1400字节,可以使用 ikcp_setmtu来设置该值。该值将会影响数据包归并及分片时候的最大传输单元。

最小RTO:不管是 TCP还是 KCP计算 RTO时都有最小 RTO的限制,即便计算出来RTO为40ms,由于默认的 RTO是100ms,协议只有在100ms后才能检测到丢包,快速模式下该值为30ms,可以手动更改该值:kcp->rx_minrto =10;

比如下层协议使用最简单的冗余包:单个数据包除了自己外,还会重复存储一次上一个数据包,以及上上一个数据包的内容:

Fn=(Pn,Pn-1,Pn-2) P0 =(0, X, X) P1 =(1,0, X) P2 =(2,1,0) P3 =(3,2,1) 

这样几个包发送出去,接收方对于单个原始包都可能被解出3次来(后面两个包任然会重复该包内容),那么这里需要记录一下,一个下层数据包只会input给kcp一次,避免过多重复ack带来的浪费。

KCP demo

kcpClient.c

#include <sys/types.h> #include <sys/socket.h> #include <pthread.h> #include <netinet/in.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include "ikcp.c" #include <sys/time.h> #include <sys/wait.h> #include <arpa/inet.h> //===================================================================== // // kcp demo // // 说明: // ikcp_input、ikcp_send返回值0 为正常 // 前期通信异常原因: // 1.sendto(send->sockfd, buf, len, 0, (struct sockaddr *)&send->CientAddr,sizeof(struct sockaddr_in)); // 参数buf 写成send->buff // 2.ret = ikcp_send(send->pkcp, send->buff,sizeof(send->buff) );//第三个参数 正确应该是strlen(send->buff)+1 // send->buff实际长度14字节,却传输512字节,后面全是0,kcp对这512字节数据全部进行封包,由UDP发送,导致kcp input处理出错 // send中的 buff[488]最大长度为488,传输正常,(488+24=512)[实际长度488+24kcp头部],感觉kcp_input处理超过512的数据就出错 //===================================================================== static int number = 0; typedef struct { unsigned char *ipstr; int port; ikcpcb *pkcp; int sockfd; struct sockaddr_in addr;//存放服务器的结构体 char buff[488];//存放收发的消息 }kcpObj; /* get system time */ void itimeofday(long *sec, long *usec) { #if defined(__unix) struct timeval time; gettimeofday(&time, NULL); if (sec) *sec = time.tv_sec; if (usec) *usec = time.tv_usec; #else static long mode = 0, addsec = 0; BOOL retval; static IINT64 freq = 1; IINT64 qpc; if (mode == 0) { retval = QueryPerformanceFrequency((LARGE_INTEGER*)&freq); freq = (freq == 0)? 1 : freq; retval = QueryPerformanceCounter((LARGE_INTEGER*)&qpc); addsec = (long)time(NULL); addsec = addsec - (long)((qpc / freq) & 0x7fffffff); mode = 1; } retval = QueryPerformanceCounter((LARGE_INTEGER*)&qpc); retval = retval * 2; if (sec) *sec = (long)(qpc / freq) + addsec; if (usec) *usec = (long)((qpc % freq) *  / freq); #endif } /* get clock in millisecond 64 */ IINT64 iclock64(void) { long s, u; IINT64 value; itimeofday(&s, &u); value = ((IINT64)s) * 1000 + (u / 1000); return value; } IUINT32 iclock() { return (IUINT32)(iclock64() & 0xfffffffful); } /* sleep in millisecond */ void isleep(unsigned long millisecond) { #ifdef __unix /* usleep( time * 1000 ); */ struct timespec ts; ts.tv_sec = (time_t)(millisecond / 1000); ts.tv_nsec = (long)((millisecond % 1000) * ); /*nanosleep(&ts, NULL);*/ usleep((millisecond << 10) - (millisecond << 4) - (millisecond << 3)); #elif defined(_WIN32) Sleep(millisecond); #endif } int udpOutPut(const char *buf, int len, ikcpcb *kcp, void *user){ // printf("使用udpOutPut发送数据\n"); kcpObj *send = (kcpObj *)user; //发送信息 int n = sendto(send->sockfd, buf, len, 0,(struct sockaddr *) &send->addr,sizeof(struct sockaddr_in));//【】 if (n >= 0) { //会重复发送,因此牺牲带宽 //printf("udpOutPut-send: 字节 =%d bytes 内容=[%s]\n", n ,buf+24);//24字节的KCP头部 return n; } else { printf("udpOutPut: %d bytes send, error\n", n); return -1; } } int init(kcpObj *send) { send->sockfd = socket(AF_INET,SOCK_DGRAM,0); if(send->sockfd < 0) { perror("socket error!"); exit(1); } bzero(&send->addr, sizeof(send->addr)); //设置服务器ip、port send->addr.sin_family=AF_INET; send->addr.sin_addr.s_addr = inet_addr((char*)send->ipstr); send->addr.sin_port = htons(send->port); printf("sockfd = %d ip = %s port = %d\n",send->sockfd,send->ipstr,send->port); } void loop(kcpObj *send) { unsigned int len = sizeof(struct sockaddr_in); int n,ret; while(1) { isleep(1); //ikcp_update包含ikcp_flush,ikcp_flush将发送队列中的数据通过下层协议UDP进行发送 ikcp_update(send->pkcp,iclock());//不是调用一次两次就起作用,要loop调用 char buf[512]={0}; //处理收消息 n = recvfrom(send->sockfd,buf,512,MSG_DONTWAIT,(struct sockaddr *) &send->addr,&len); if(n < 0)//检测是否有UDP数据包 continue; printf("UDP接收到数据包 大小= %d buf =%s\n",n,buf+24); //预接收数据:调用ikcp_input将裸数据交给KCP,这些数据有可能是KCP控制报文,并不是我们要的数据。 //kcp接收到下层协议UDP传进来的数据底层数据buffer转换成kcp的数据包格式 ret = ikcp_input(send->pkcp, buf, n); if(ret < 0)//检测ikcp_input是否提取到真正的数据 { //printf("ikcp_input ret = %d\n",ret); continue; } // ikcp_update(send->pkcp,iclock());//不是调用一次两次就起作用,要loop调用 while(1) { //kcp将接收到的kcp数据包还原成之前kcp发送的buffer数据 ret = ikcp_recv(send->pkcp, buf, n); if(ret < 0)//检测ikcp_recv提取到的数据 { //printf("ikcp_recv ret = %d\n",ret); break; } } printf("数据交互 ip = %s port = %d\n",inet_ntoa(send->addr.sin_addr),ntohs(send->addr.sin_port)); if(strcmp(buf,"Conn-OK") == 0) { //kcp收到确认连接包,则进行交互 printf("Data from Server-> %s\n",buf); //把要发送的buffer分片成KCP的数据包格式,插入待发送队列中。 ret = ikcp_send(send->pkcp, send->buff,sizeof(send->buff) );//strlen(send->buff)+1 printf("Client reply -> 内容[%s] 字节[%d] ret = %d\n",send->buff,(int)sizeof(send->buff),ret);//发送成功的 number++; printf("第[%d]次发\n",number); } //发消息 if(strcmp(buf,"Server:Hello!") == 0) { //kcp收到确认连接包,则进行交互 printf("Data from Server-> %s\n",buf); //kcp收到交互包,则回复 //把要发送的buffer分片成KCP的数据包格式,插入待发送队列中。 ikcp_send(send->pkcp, send->buff, sizeof(send->buff)); number++; printf("第[%d]次发\n",number); } } } int main(int argc,char *argv[]) { //printf("this is kcpClient,请输入服务器 ip地址和端口号:\n"); if(argc != 3) { printf("请输入服务器ip地址和端口号\n"); return -1; } printf("this is kcpClient\n"); unsigned char *ipstr = (unsigned char *)argv[1]; unsigned char *port = (unsigned char *)argv[2]; kcpObj send; send.ipstr = ipstr; send.port = atoi(argv[2]); init(&send);//初始化send,主要是设置与服务器通信的套接字对象 bzero(send.buff,sizeof(send.buff)); char Msg[] = "Client:Hello!";//与服务器后续交互 memcpy(send.buff,Msg,sizeof(Msg)); ikcpcb *kcp = ikcp_create(0x1, (void *)&send);//创建kcp对象把send传给kcp的user变量 kcp->output = udpOutPut;//设置kcp对象的回调函数 ikcp_nodelay(kcp,0, 10, 0, 0);//(kcp1, 0, 10, 0, 0); 1, 10, 2, 1 ikcp_wndsize(kcp, 128, 128); send.pkcp = kcp; char temp[] = "Conn";//与服务器初次通信 int ret = ikcp_send(send.pkcp,temp,(int)sizeof(temp)); printf("ikcp_send发送连接请求: [%s] len=%d ret = %d\n",temp,(int)sizeof(temp),ret);//发送成功的 loop(&send);//循环处理 return 0; } 

kcpServer.c

#include <sys/types.h> #include <sys/socket.h> #include <pthread.h> #include <netinet/in.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include "ikcp.c" #include <string.h> #include <sys/time.h> #include <sys/wait.h> #include <arpa/inet.h> //===================================================================== // // kcp demo // // 说明: // ikcp_input、ikcp_send返回值0 为正常 // 前期通信异常原因: // 1.sendto(send->sockfd, buf, len, 0, (struct sockaddr *)&send->CientAddr,sizeof(struct sockaddr_in)); // 参数buf 写成send->buff // 2.ret = ikcp_send(send->pkcp, send->buff,sizeof(send->buff) );//第三个参数 正确应该是strlen(send->buff)+1 // send->buff实际长度14字节,却传输512字节,后面全是0,kcp对这512字节数据全部进行封包,由UDP发送,导致kcp input处理出错 // send中的 buff[488]最大长度为488,传输正常,(488+24=512)[实际长度488+24kcp头部],感觉kcp_input处理超过512的数据就出错 //===================================================================== static int number = 0; typedef struct { unsigned char *ipstr; int port; ikcpcb *pkcp; int sockfd; struct sockaddr_in addr;//存放服务器信息的结构体 struct sockaddr_in CientAddr;//存放客户机信息的结构体 char buff[488];//存放收发的消息 }kcpObj; /* get system time */ void itimeofday(long *sec, long *usec) { #if defined(__unix) struct timeval time; gettimeofday(&time, NULL); if (sec) *sec = time.tv_sec; if (usec) *usec = time.tv_usec; #else static long mode = 0, addsec = 0; BOOL retval; static IINT64 freq = 1; IINT64 qpc; if (mode == 0) { retval = QueryPerformanceFrequency((LARGE_INTEGER*)&freq); freq = (freq == 0)? 1 : freq; retval = QueryPerformanceCounter((LARGE_INTEGER*)&qpc); addsec = (long)time(NULL); addsec = addsec - (long)((qpc / freq) & 0x7fffffff); mode = 1; } retval = QueryPerformanceCounter((LARGE_INTEGER*)&qpc); retval = retval * 2; if (sec) *sec = (long)(qpc / freq) + addsec; if (usec) *usec = (long)((qpc % freq) *  / freq); #endif } /* get clock in millisecond 64 */ IINT64 iclock64(void) { long s, u; IINT64 value; itimeofday(&s, &u); value = ((IINT64)s) * 1000 + (u / 1000); return value; } IUINT32 iclock() { return (IUINT32)(iclock64() & 0xfffffffful); } /* sleep in millisecond */ void isleep(unsigned long millisecond) { #ifdef __unix /* usleep( time * 1000 ); */ struct timespec ts; ts.tv_sec = (time_t)(millisecond / 1000); ts.tv_nsec = (long)((millisecond % 1000) * ); /*nanosleep(&ts, NULL);*/ usleep((millisecond << 10) - (millisecond << 4) - (millisecond << 3)); #elif defined(_WIN32) Sleep(millisecond); #endif } int udpOutPut(const char *buf, int len, ikcpcb *kcp, void *user){ kcpObj *send = (kcpObj *)user; //发送信息 int n = sendto(send->sockfd, buf, len, 0, (struct sockaddr *)&send->CientAddr,sizeof(struct sockaddr_in)); if (n >= 0) { //会重复发送,因此牺牲带宽 printf("udpOutPut-send: 字节 =%d bytes 内容=[%s]\n", n ,buf+24);//24字节的KCP头部 return n; } else { printf("error: %d bytes send, error\n", n); return -1; } } int init(kcpObj *send) { send->sockfd = socket(AF_INET,SOCK_DGRAM,0); if(send->sockfd<0) { perror("socket error!"); exit(1); } bzero(&send->addr, sizeof(send->addr)); send->addr.sin_family = AF_INET; send->addr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY send->addr.sin_port = htons(send->port); printf("服务器socket: %d port:%d\n",send->sockfd,send->port); if(send->sockfd<0){ perror("socket error!"); exit(1); } if(bind(send->sockfd,(struct sockaddr *)&(send->addr),sizeof(struct sockaddr_in))<0) { perror("bind"); exit(1); } } void loop(kcpObj *send) { unsigned int len = sizeof(struct sockaddr_in); int n,ret; //接收到第一个包就开始循环处理 while(1) { isleep(1); ikcp_update(send->pkcp,iclock()); char buf[1024]={0}; //处理收消息 n = recvfrom(send->sockfd,buf,512,MSG_DONTWAIT,(struct sockaddr *)&send->CientAddr,&len); if(n < 0)//检测是否有UDP数据包: kcp头部+data continue; printf("UDP接收到数据包 大小= %d \n",n); //预接收数据:调用ikcp_input将裸数据交给KCP,这些数据有可能是KCP控制报文,并不是我们要的数据。 //kcp接收到下层协议UDP传进来的数据底层数据buffer转换成kcp的数据包格式 ret = ikcp_input(send->pkcp, buf, n); // if(ret < 0)//检测ikcp_input对 buf 是否提取到真正的数据 // { // printf("ikcp_input error ret = %d\n",ret); // continue; // } while(1) { //kcp将接收到的kcp数据包还原成之前kcp发送的buffer数据 ret = ikcp_recv(send->pkcp, buf, n);//从 buf中 提取真正数据,返回提取到的数据大小 if(ret < 0)//检测ikcp_recv提取到的数据 break; } printf("数据交互 ip = %s port = %d\n",inet_ntoa(send->CientAddr.sin_addr),ntohs(send->CientAddr.sin_port)); //发消息 if(strcmp(buf,"Conn") == 0) { //kcp提取到真正的数据 printf("[Conn] Data from Client-> %s\n",buf); //kcp收到连接请求包,则回复确认连接包 char temp[] = "Conn-OK"; //ikcp_send只是把数据存入发送队列,没有对数据加封kcp头部数据 //应该是在kcp_update里面加封kcp头部数据 //ikcp_send把要发送的buffer分片成KCP的数据包格式,插入待发送队列中。 ret = ikcp_send(send->pkcp,temp,(int)sizeof(temp)); printf("Server reply -> 内容[%s] 字节[%d] ret = %d\n",temp,(int)sizeof(temp),ret); number++; printf("第[%d]次发\n",number); } if(strcmp(buf,"Client:Hello!") == 0) { //kcp提取到真正的数据 printf("[Hello] Data from Client-> %s\n",buf); //kcp收到交互包,则回复 ikcp_send(send->pkcp, send->buff,sizeof(send->buff)); number++; printf("第[%d]次发\n",number); } } } int main(int argc,char *argv[]) { printf("this is kcpServer\n"); if(argc <2 ) { printf("请输入服务器端口号\n"); return -1; } kcpObj send; send.port = atoi(argv[1]); send.pkcp = NULL; bzero(send.buff,sizeof(send.buff)); char Msg[] = "Server:Hello!";//与客户机后续交互 memcpy(send.buff,Msg,sizeof(Msg)); ikcpcb *kcp = ikcp_create(0x1, (void *)&send);//创建kcp对象把send传给kcp的user变量 kcp->output = udpOutPut;//设置kcp对象的回调函数 ikcp_nodelay(kcp, 0, 10, 0, 0);//1, 10, 2, 1 ikcp_wndsize(kcp, 128, 128); send.pkcp = kcp; init(&send);//服务器初始化套接字 loop(&send);//循环处理 return 0; } 

在这里插入图片描述

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/125155.html

(0)
上一篇 2025-09-29 21:15
下一篇 2025-09-29 21:20

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信