• 【KCP】UDP可靠性传输


    1 如何做到可靠性传输

    ◼ ACK机制
    ◼ 重传机制
    ◼ 序号机制 3 2 1 -》2 3 1
    ◼ 重排机制 2 3 1 ->3 2 1
    ◼ 窗口机制
    Tcp不用我们管
    可靠性udp 5种机制都需要用户层处理

    2 UDP与TCP,我们如何选择

    image.png

    3 UDP如何可靠,KCP协议在哪些方面有优势

    以10%-20%带宽浪费的代价换取了比 TCP快30%-40%的传输速度。

    RTO翻倍vs不翻倍
    TCP超时计算是RTOx2,这样连续丢三次包就变成RTOx8了,十分恐怖,而 KCP启动快速模式后不x2,只是x1.5(实验证明1.5这个值相对比较好), 提高了传输速度。 200 300 450 675 – 200 400 800 1600

    选择性重传 vs 全部重传
    TCP丢包时会全部重传从丢的那个包开始以后的数据,KCP是选择性重传, 只重传真正丢失的数据包。
    快速重传(跳过多少个包马上重传)(如果使用了快速重传,可以不考虑 RTO)): 发送端发送了1,2,3,4,5几个包,然后收到远端的ACK: 1, 3, 4, 5,当收到ACK3时,KCP知道2被跳过1次,收到ACK4时,知道2被跳过了2次,此时可以认为2号丢失,不用等超时,直接重传2号包,大大改善了丢包时的传输速度。 fastresend =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都能流畅传输的效果。

    4 KCP精讲

    4.1 kcp名词说明

    kcp官方:https://github.com/skywind3000/kcp

    名词说明

    • 用户数据:应用层发送的数据,如一张图片2Kb的数据
    • MTU:最大传输单元。即每次发送的最大数据
    • RTO:Retransmission TimeOut,重传超时时间。
    • cwnd:congestion window,拥塞窗口,表示发送方可发送多少个KCP数据包。与接收方窗口有关,与网络状况(拥塞控制)有关,与发送窗口大小有关。
    • rwnd:receiver window,接收方窗口大小,表示接收方还可接收多少个KCP数据包
    • snd_queue:待发送KCP数据包队列
    • snd_nxt:下一个即将发送的kcp数据包序列号
    • snd_una:下一个待确认的序列号

    4.2 kcp使用方式

    1. 创建 KCP对象:ikcpcb *kcp = ikcp_create(conv, user);
    2. 设置传输回调函数(如UDP的send函数):kcp->output = udp_output;

    真正发送数据需要调用sendto

    1. 循环调用 update:ikcp_update(kcp, millisec);
    2. 输入一个应用层数据包(如UDP收到的数据包):

    ikcp_input(kcp,received_udp_packet,received_udp_size);
    我们要使用recvfrom接收,然后扔到kcp里面做解析

    1. 发送数据:ikcp_send(kcp1, buffer, 8); 用户层接口
    2. 接收数据:hr = ikcp_recv(kcp2, buffer, 10);

    4.3 kcp源码流程图

    image.png

    4.4 kcp配置模式

    1. 工作模式:int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc)
       nodelay :是否启用 nodelay模式,0不启用;1启用。
       interval :协议内部工作的 interval,单位毫秒,比如 10ms或者 20ms
       resend :快速重传模式,默认0关闭,可以设置2(2次ACK跨越将会直接重传)
       nc :是否关闭流控,默认是0代表不关闭,1代表关闭。
      普通模式: ikcp_nodelay(kcp, 0, 40, 0, 0);
      极速模式: ikcp_nodelay(kcp, 1, 10, 2, 1)
      2. 最大窗口:int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd);
      该调用将会设置协议的最大发送窗口和最大接收窗口大小,默认为32,单位为包。
      3. 最大传输单元:int ikcp_setmtu(ikcpcb *kcp, int mtu);
      kcp协议并不负责探测 MTU,默认 mtu是1400字节
      4. 最小RTO:不管是 TCP还是 KCP计算 RTO 时都有最小 RTO 的限制,即便计算出来RTO为40ms,由于默认的 RTO是100ms,协议只有在100ms后才能检测到丢包,快速模式下为30ms,可以手动更改该值: kcp->rx_minrto = 10;

    4.5 kcp协议头

    image.png
     conv:连接号。UDP是无连接的,conv用于表示来自于哪个客户端。对连接的一种替代
     cmd:命令字。如,
    IKCP_CMD_ACK确认命令,
    IKCP_CMD_WASK接收窗口大小询问命令,
    IKCP_CMD_WINS接收窗口大小告知命令,
     frg:分片,用户数据可能会被分成多个KCP包,发送出去
     wnd:接收窗口大小,发送方的发送窗口不能超过接收方给出的数值
     ts:时间序列
     sn:序列号
     una:下一个可接收的序列号。其实就是确认号,收到 sn=10的包,una为11
     len:数据长度
     data:用户数据
    image.png

    4.6 kcp发送数据过程

    image.png

    4.7 kcp接收数据过程

    image.png

    4.8 kcp确认包处理流程

    image.png

    4.9 kcp快速确认

    image.png

    4.10 流量控制和拥塞控制

    RTO计算(与TCP完全一样)
    RTT:一个报文段发送出去,到收到对应确认包的时间差。
    SRTT(kcp->rx_srtt):RTT的一个加权RTT平均值,平滑值。
    RTTVAR(kcp->rx_rttval):RTT的平均偏差,用来衡量
    RTT的抖动。

    4.11 如何在项目中集成kcp

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include "ikcp.h"
    #define RECV_BUF 1500
    
    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[RECV_BUF]; //存放收发的消息
    
    } kcpObj;
    // 编译:  gcc -o server server.c ikcp.c  
    // 特别需要注意,这里的服务器端也只能一次使用,即是等客户端退出后,服务端也要停止掉再启动
    // 之所以是这样,主要是因为sn的问题,比如客户端第一次启动 sn 0~5, 第二次启动发送的sn还是0 ~5 如果服务器端不停止则自己以为0~5已经收到过了就不会回复。
    
    // 在真正使用的时候,还需要另外的通道让客户端和服务器端之前重新创建ikcpcb,以匹配ikcpcb的conv
    /* 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) * 1000000 / 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);
    }
    
    int64_t first_recv_time = 0;
    /* 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) * 1000000);
    	/*nanosleep(&ts, NULL);*/
    	usleep((millisecond << 10) - (millisecond << 4) - (millisecond << 3));
    #elif defined(_WIN32)
    	Sleep(millisecond);
    #endif
    }
    
    int udp_output(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("send: %d bytes, t:%lld\n", n, iclock64() - first_recv_time); //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;
    	//接收到第一个包就开始循环处理
    	int recv_count = 0;
    
    	isleep(1);
    	ikcp_update(send->pkcp, iclock());
    
    	char buf[RECV_BUF] = {0};
    
    	while (1)
    	{
    		isleep(1);
    		ikcp_update(send->pkcp, iclock());
    		//处理收消息
    		n = recvfrom(send->sockfd, buf, RECV_BUF, MSG_DONTWAIT, (struct sockaddr *)&send->CientAddr, &len);
    		if (n > 0)
    		{
    			printf("UDP recv[%d]  size= %d   \n", recv_count++, n);
    			if (first_recv_time == 0)
    			{
    				first_recv_time = iclock64();
    			}
    			//预接收数据:调用ikcp_input将裸数据交给KCP,这些数据有可能是KCP控制报文,并不是我们要的数据。
    			//kcp接收到下层协议UDP传进来的数据底层数据buffer转换成kcp的数据包格式
    			ret = ikcp_input(send->pkcp, buf, n);
    			if (ret < 0)
    			{
    				continue;
    			}
    			//kcp将接收到的kcp数据包还原成之前kcp发送的buffer数据
    			ret = ikcp_recv(send->pkcp, buf, n); //从 buf中 提取真正数据,返回提取到的数据大小
    			if (ret < 0)
    			{ // 没有检测ikcp_recv提取到的数据
    				isleep(1);
    				continue;
    			}
    			int send_size = ret;
    			//ikcp_send只是把数据存入发送队列,没有对数据加封kcp头部数据
    			//应该是在kcp_update里面加封kcp头部数据
    			//ikcp_send把要发送的buffer分片成KCP的数据包格式,插入待发送队列中。
    			ret = ikcp_send(send->pkcp, buf, send_size);
    			printf("Server reply ->  bytes[%d], ret = %d\n", send_size, ret);
    			ikcp_flush(send->pkcp);	// 快速flush一次 以更快让客户端收到数据
    			number++;
    		}
    		else if (n == 0)
    		{
    			printf("finish loop\n");
    			break;
    		}
    		else
    		{
    			// printf("n:%d\n", n);
    		}
    	}
    }
    
    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变量
    	ikcp_setmtu(kcp, 1400);
    	kcp->output = udp_output;		//设置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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include "ikcp.h"
    
    #include 
    #include 
    #include 
    
    
    #include "delay.h"
    #define DELAY_TEST2_N 5
    #define UDP_RECV_BUF_SIZE 1500
    
    // 编译:  gcc -o client client.c ikcp.c delay.c  -lpthread
    
    
     typedef struct {
    	unsigned char *ipstr;
    	int port;
    	
    	ikcpcb *pkcp;
    	
    	int sockfd;
    	struct sockaddr_in addr;//存放服务器的结构体
    	
    	char buff[UDP_RECV_BUF_SIZE];//存放收发的消息
    }kcpObj;
    
    
    /* 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) * 1000000);
    	/*nanosleep(&ts, NULL);*/
    	usleep((millisecond << 10) - (millisecond << 4) - (millisecond << 3));
    	#elif defined(_WIN32)
    	Sleep(millisecond);
    	#endif
    }
    
    
    
    int udp_output(const char *buf, int len, ikcpcb *kcp, void *user){
       
     //  printf("使用udp_output发送数据\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("send:%d bytes\n", n);//24字节的KCP头部
            return n;
        } 
    	else 
    	{
            printf("udp_output: %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);
    	
    }
    
    // 特别说明,当我们使用kcp测试rtt的时候,如果发现rtt过大,很大一种可能是分片数据没有及时发送出去,需要调用ikcp_flush更快速将分片发送出去。
    void delay_test2(kcpObj *send) {
        // 初始化 100个 delay obj
        char buf[UDP_RECV_BUF_SIZE];
    	unsigned int len = sizeof(struct sockaddr_in);
    
        size_t obj_size = sizeof(t_delay_obj);
        t_delay_obj *objs = malloc(DELAY_TEST2_N * sizeof(t_delay_obj));
    	int ret = 0;
    
    	int recv_objs = 0;
    	//ikcp_update包含ikcp_flush,ikcp_flush将发送队列中的数据通过下层协议UDP进行发送
     	ikcp_update(send->pkcp,iclock());//不是调用一次两次就起作用,要loop调用
        for(int i = 0; i < DELAY_TEST2_N; i++) {
    		//  isleep(1);
    		delay_set_seqno_send_time(&objs[i], i);  
    		ret = ikcp_send(send->pkcp, (char *) &objs[i], obj_size); 
            if(ret < 0) {
                printf("send %d seqno:%u failed, ret:%d, obj_size:%ld\n", i, objs[i].seqno, ret, obj_size);
                return;
            } 
            // ikcp_flush(send->pkcp);		// 调用flush能更快速把分片发送出去
    		//ikcp_update包含ikcp_flush,ikcp_flush将发送队列中的数据通过下层协议UDP进行发送
    		ikcp_update(send->pkcp,iclock());//不是调用一次两次就起作用,要loop调用
    		
    		int n = recvfrom(send->sockfd, buf, UDP_RECV_BUF_SIZE, MSG_DONTWAIT,(struct sockaddr *) &send->addr,&len);
    		// printf("print recv1:%d\n", n);
    		if(n < 0) {//检测是否有UDP数据包 
    			// isleep(1);
    			continue;
    		}
    		ret = ikcp_input(send->pkcp, buf, n);	// 从 linux api recvfrom先扔到kcp引擎
    		if(ret < 0)//检测ikcp_input是否提取到真正的数据
    		{
    			//printf("ikcp_input ret = %d\n",ret);
    			continue;			// 没有读取到数据
    		}	
    		ret = ikcp_recv(send->pkcp, (char *)&objs[i], obj_size);		
    		if(ret < 0)//检测ikcp_recv提取到的数据	
    		{
    			printf("ikcp_recv1 ret = %d\n",ret);
    			continue;
    		}
    		delay_set_recv_time(&objs[recv_objs]);
    		recv_objs++;
    		printf("recv1 %d seqno:%d, ret:%d\n", recv_objs, objs[i].seqno, ret);
            if(ret != obj_size) {
                printf("recv1 %d seqno:%d failed, size:%d\n", i, objs[i].seqno, ret);
                delay_print_rtt_time(objs, i);
                return;
            }
        }
    
    	// 还有没有发送完毕的数据
    	for(int i = recv_objs; i < DELAY_TEST2_N; ) {
    		//  isleep(1);
    		//ikcp_update包含ikcp_flush,ikcp_flush将发送队列中的数据通过下层协议UDP进行发送
    		ikcp_update(send->pkcp,iclock());//不是调用一次两次就起作用,要loop调用
    		//ikcp_flush(send->pkcp);		// 调用flush能更快速把分片发送出去  
    		int n = recvfrom(send->sockfd, buf, UDP_RECV_BUF_SIZE, MSG_DONTWAIT,(struct sockaddr *) &send->addr,&len);
    		// printf("recv2:%d\n", n);
    		if(n < 0) {//检测是否有UDP数据包
    			printf("recv2:%d\n", n);
    			isleep(1);
    			continue;
    		}
    			
    		ret = ikcp_input(send->pkcp, buf, n);	
    		if(ret < 0)//检测ikcp_input是否提取到真正的数据
    		{
    			printf("ikcp_input2 ret = %d\n",ret);
    			continue;			// 没有读取到数据
    		}	
    		ret = ikcp_recv(send->pkcp, (char *)&objs[i], obj_size);		
    		if(ret < 0)//检测ikcp_recv提取到的数据	
    		{
    			printf("ikcp_recv2 ret = %d\n",ret);
    			continue;
    		}
    		printf("recv2 %d seqno:%d, ret:%d\n", recv_objs,  objs[i].seqno, ret);
    		delay_set_recv_time(&objs[recv_objs]);
    		recv_objs++;
    		i++;
            if(ret != obj_size) {
                printf("recv2 %d seqno:%d failed, size:%d\n", i, objs[i].seqno, ret);
                delay_print_rtt_time(objs, i);
                return;
            }
            
    	}
    	ikcp_flush(send->pkcp);
    
        delay_print_rtt_time(objs, DELAY_TEST2_N);
    }
    
    void loop(kcpObj *send)
    {
    	unsigned int len = sizeof(struct sockaddr_in);
    	int n,ret;
    
    	// while(1)
    	{
    		isleep(1);
    		delay_test2(send);
    	}
    	printf("loop finish\n");
    	close(send->sockfd);
    	
    }
    
    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));
    	
    	// 每个连接都是需要对应一个ikcpcb
    	ikcpcb *kcp = ikcp_create(0x1, (void *)&send);//创建kcp对象把send传给kcp的user变量
    	kcp->output = udp_output;//设置kcp对象的回调函数
    	ikcp_nodelay(kcp,0, 10, 0, 0);//(kcp1, 0, 10, 0, 0); 1, 10, 2, 1
    	ikcp_wndsize(kcp, 128, 128);
    	ikcp_setmtu(kcp, 1400);
    	send.pkcp = kcp;	
    	loop(&send);//循环处理
    	ikcp_release(send.pkcp);
    	printf("main finish\n");
    	return 0;	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237

    5.0 QUIC衍生版本XQUIC(作学习参考)

    https://www.yuque.com/docs/share/01d83f75-0fd5-4c9f-976a-0dfcf417e0cc?#
    《阿里XQUIC:标准QUIC实现自研之路》

  • 相关阅读:
    L1-003 个位数统计
    [补题记录] Atcoder Beginner Contest 325(E、F)
    GP db模板、dblink、tablespace、交换分区和数据倾斜
    【项目部署-apache】windows系统下apache部署django+channels
    C++包装器
    【JVM技术专题】 深入分析回顾堆外内存使用和分析「分析篇」
    Python_15 ddt驱动与日志
    Node 入门
    Hadoop3教程(三十四):(生产调优篇)MapReduce生产经验汇总
    中科柏诚与知名上市公司南天信息(000948)签署战略合作协议
  • 原文地址:https://blog.csdn.net/qq_37717687/article/details/128179265