可基于udp的可靠传输协议kcp介绍,C++测试kcp示例

目录

    • 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());

你可能感兴趣的:(C/C++,udp,c++,网络协议,算法,tcp)