目录

KCP简介KCP常用接口KCP测试源码

KCP简介

KCP是国人开发的开源项目,作者:林伟 (skywind3000)(这个是真大牛)。

KCP是快速可靠传输协议,纯算法实现,KCP无任何系统调用,不负责底层协议收发,底层可以使用UDP或其他自定义协议进行收发。

开源地址:https://github.com/skywind3000/kcp

KCP关键技术 KCP通常使用UDP做为底层协议,主要对标TCP协议,github README有详细说明。

1、TCP协议是从大局考虑的,均衡速率和整个网络的拥塞,而KCP是自私的,只顾自己的传输效率,不去考虑整个网络的拥堵情况。 2、KCP使用RTO不翻倍、选择性重传、快速重传、非延迟ACK、非退让流控等技术,实现存在网络延时和丢包时传输效率对TCP的超越,以比 TCP 浪费 10%-20% 的带宽的代价,换取平均延迟降低 30%-40%。 3、KCP流控参数可配,以应对不同场景,TCP策略通常是不可配置的。

KCP使用场景

丢包率和延时高的网络环境才能体现出KCP相对TCP的优势,如果网络环境特别好(比如内网),那么TCP和KCP的传输效率相差不大。

KCP源码

纯C语言开发,只有ikcp.h和ikcp.c两个文件,加在一起1700行代码,很容易集成到现有项目。

git clone https://github.com/skywind3000/kcp.git

KCP常用接口

ikcpcb* ikcp_create(IUINT32 conv, void *user); 创建一个新的kcp对象,“conv”必须在来自同一连接的两个端点中相等。‘user’将被传递到发送数据回调,包括fd和远端地址等信息。

int ikcp_input(ikcpcb *kcp, const char *data, long size); 系统调用接收到数据后,将裸数据交给KCP,这些数据有可能是KCP控制报文。 KCP报文分为ACK报文、数据报文、探测窗口报文、响应窗口报文四种。

int ikcp_recv(ikcpcb *kcp, char *buffer, int len); ikcp_recv需要通过轮询的方式去调用,如果有数据,将返回完整的消息,如果没有就返回错误。buffer和len由调用者预先分配。

int ikcp_send(ikcpcb *kcp, const char *buffer, int len); 把数据加入发送队列,使用设置的发送回调进行发送处理。 流模式情况下,检测每个发送队列里的分片是否达到最大MSS,如果没有达到就会用新的数据填充分片。接收端会把多片发送的数据重组为一个完整的KCP包。 消息模式下,将用户数据分片,为每个分片设置sn和frag,将分片后的数据一个一个地存入发送队列,接收方通过sn和frag解析原来的包,消息方式一个分片的数据量可能不能达到MSS,也会作为一个包发送出去

void ikcp_update(ikcpcb *kcp, IUINT32 current); 调用ikcp_flush,检测发送队列是否有数据要进行发送,探测远端窗口,检测重传等。

KCP测试源码

基本思路: 底层以udp为例,绑定本地端口用于接收数据,指定远端IP和端口用于数据发送。 数据接收:

1、使用系统调用比如 recvfrom 进行接收数据,收到数据后使用 ikcp_input 接口把裸数据交给KCP处理; 2、轮询调用 ikcp_recv 接口,返回值大于0则是有我们需要的应用数据。

数据发送:

1、自定义数据发送接口,把接口函数指针赋值给回调函数ikcpcb->output,同时传递udp句柄; 2、应用层调用 ikcp_send 接口进行数据发送,实际上是把数据加入发送队列; 3、轮询调用 ikcp_update 接口,检测发送队列,有数据要发送时调用 ikcpcb->output 回调函数进行发送。

目录结构 ├── ikcp.c ├── ikcp.h ├── kcp_client.cpp ├── kcp_client.h ├── kcp_inc.cpp └── kcp_inc.h

ikcp.h和ikcp.c在github开源地址下载。 kcp_client.h

#ifndef KCP_CLIENT_H

#define KCP_CLIENT_H

#include "ikcp.h"

#include "kcp_inc.h"

class kcp_client

{

public:

kcp_client(char *serIp, uint16_t serPort, uint16_t localPort);

int sendData(const char *buffer, int len);

bool m_isLoop;

//private:

UdpDef *pUdpDef;

ikcpcb *pkcp;

};

#endif // KCP_CLIENT_H

kcp_client.cpp

#include "kcp_client.h"

/**

* @brief run_udp_thread udp线程处理函数

* @param obj UdpHandle

* @return

*/

void* run_udp_thread(void *obj)

{

kcp_client *client = (kcp_client*) obj;

char buffer[2048] = { 0 };

int32_t len = 0;

socklen_t src_len = sizeof(struct sockaddr_in);

while (client->m_isLoop)

{

///5.核心模块,循环调用,处理数据发送/重发等

ikcp_update(client->pkcp,iclock());

struct sockaddr_in src;

memset(&src, 0, src_len);

///6.udp接收到数据

if ((len = recvfrom(client->pUdpDef->fd, buffer, 2048, 0, (struct sockaddr*) &src, &src_len)) > 0)

{

// printf("rcv=%s,len=%d\n\n",buffer,len);//可能时kcp控制报文

///7.预接收数据:调用ikcp_input将裸数据交给KCP,这些数据有可能是KCP控制报文

int ret = ikcp_input(client->pkcp, buffer, len);

if(ret < 0)//检测ikcp_input是否提取到真正的数据

{

continue;

}

///8.kcp将接收到的kcp数据包还原成应用数据

char rcv_buf[2048] = { 0 };

ret = ikcp_recv(client->pkcp, rcv_buf, len);

if(ret >= 0)//检测ikcp_recv提取到的数据

{

printf("ikcp_recv ret = %d,buf=%s\n",ret,rcv_buf);

//9.测试用,自动回复一条消息

if(strcmp(rcv_buf,"hello") == 0)

{

std::string msg = "hello back.";

client->sendData(msg.c_str(),msg.size());

}

}

}

isleep(1);

}

close(client->pUdpDef->fd);

return NULL;

}

kcp_client::kcp_client(char *serIp, uint16_t serPort, uint16_t localPort)

{

pUdpDef = new UdpDef;

///1.创建udp,指定远端IP和PORT,以及本地绑定PORT

uint32_t remoteIp = inet_addr(serIp);

CreateUdp(pUdpDef,remoteIp,serPort,localPort);

///2.创建kcp实例,两端第一个参数conv要相同

pkcp = ikcp_create(0x1, (void *)pUdpDef);

///3.kcp参数设置

pkcp->output = udp_sendData_loop;//设置udp发送接口

// 配置窗口大小:平均延迟200ms,每20ms发送一个包,

// 而考虑到丢包重发,设置最大收发窗口为128

ikcp_wndsize(pkcp, 128, 128);

// 判断测试用例的模式

int mode = 0;

if (mode == 0) {

// 默认模式

ikcp_nodelay(pkcp, 0, 10, 0, 0);

}

else if (mode == 1) {

// 普通模式,关闭流控等

ikcp_nodelay(pkcp, 0, 10, 0, 1);

} else {

// 启动快速模式

// 第二个参数 nodelay-启用以后若干常规加速将启动

// 第三个参数 interval为内部处理时钟,默认设置为 10ms

// 第四个参数 resend为快速重传指标,设置为2

// 第五个参数 为是否禁用常规流控,这里禁止

ikcp_nodelay(pkcp, 2, 10, 2, 1);

pkcp->rx_minrto = 10;

pkcp->fastresend = 1;

}

///4.启动线程,处理udp收发

m_isLoop = true;

pthread_t tid;

pthread_create(&tid,NULL,run_udp_thread,this);

}

int kcp_client::sendData(const char *buffer, int len)

{

//这里只是把数据加入到发送队列

int ret = ikcp_send(pkcp,buffer,len);

return ret;

}

kcp_inc.h

#ifndef KCP_INC_H

#define KCP_INC_H

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include // for open

#include "ikcp.h"

#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)

#include

#elif !defined(__unix)

#define __unix

#endif

#ifdef __unix

#include

#include

#include

#include

#endif

//udp客户端

typedef struct _UdpDef_{

int32_t fd; //fd

struct sockaddr_in local_addr; //本端地址

struct sockaddr_in remote_addr; //对端地址

}UdpDef;

//创建udp套接字

int32_t CreateUdp(UdpDef *udp, uint32_t remoteIp, int32_t remotePort, int32_t plocalPort);

#define UDP_MTU 1460

int udp_sendData_loop(const char *buffer, int len, ikcpcb *kcp, void *user);

/* get system time */

static inline 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 */

static inline IINT64 iclock64(void)

{

long s, u;

IINT64 value;

itimeofday(&s, &u);

value = ((IINT64)s) * 1000 + (u / 1000);

return value;

}

static inline IUINT32 iclock()

{

return (IUINT32)(iclock64() & 0xfffffffful);

}

/* sleep in millisecond */

static inline 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

}

#include

#include

#endif // KCP_INC_H

kcp_inc.cpp

#include "kcp_inc.h"

#define UDP_MTU 1460

int udp_sendData_loop(const char *buffer, int len, ikcpcb *kcp, void *user)

{

UdpDef *pUdpClientDef = (UdpDef *)user;

int sended = 0;

while(sended < len){

size_t s = (len - sended);

if(s > UDP_MTU) s = UDP_MTU;

ssize_t ret = ::sendto(pUdpClientDef->fd, buffer + sended, s, MSG_DONTWAIT, (struct sockaddr*) &pUdpClientDef->remote_addr,sizeof(struct sockaddr));

if(ret < 0){

return -1;

}

sended += s;

}

return (size_t) sended;

}

int32_t CreateUdp(UdpDef *udp, uint32_t remoteIp, int32_t remotePort, int32_t plocalPort)

{

if (udp == NULL) return -1;

udp->fd = -1;

udp->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

fcntl(udp->fd, F_SETFL, O_NONBLOCK);//设置非阻塞

if(udp->fd < 0)

{

printf("[CreateUdpClient] create udp socket failed,errno=[%d],remoteIp=[%u],remotePort=[%d]",errno,remoteIp,remotePort);

return -1;

}

udp->remote_addr.sin_family = AF_INET;

udp->remote_addr.sin_port = htons(remotePort);

udp->remote_addr.sin_addr.s_addr = remoteIp;

udp->local_addr.sin_family = AF_INET;

udp->local_addr.sin_port = htons(plocalPort);

udp->local_addr.sin_addr.s_addr = INADDR_ANY;

//2.socket参数设置

int opt = 1;

setsockopt(udp->fd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));//chw

fcntl(udp->fd, F_SETFL, O_NONBLOCK);//设置非阻塞

if (bind(udp->fd, (struct sockaddr*) &udp->local_addr,sizeof(struct sockaddr_in)) < 0)

{

close(udp->fd);

printf("[CreateUdpServer] Udp server bind failed,errno=[%d],plocalPort=[%d]",errno,plocalPort);

return -2;

}

}

测试方法 创建两个客户端相互发消息。

kcp_client *pkcp_client = new kcp_client((char*)"127.0.0.1",9001,9002);

std::string msg = "hello";

pkcp_client->sendData(msg.c_str(),msg.size());

相关阅读

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。