immigrant from my original project http://code.google.com/p/ikcp

This commit is contained in:
skywind3000 2012-05-07 00:48:58 +08:00
parent e510747b5e
commit 09e1a3495d
4 changed files with 1979 additions and 0 deletions

1182
ikcp.c Normal file

File diff suppressed because it is too large Load Diff

379
ikcp.h Normal file
View File

@ -0,0 +1,379 @@
//=====================================================================
//
// KCP - A Better ARQ Protocol Implementation
// skywind3000 (at) gmail.com, 2010-2011
//
// Features:
// + Average RTT reduce 30% - 40% vs traditional ARQ like tcp.
// + Maximum RTT reduce three times vs tcp.
// + Lightweight, distributed as a single source file.
//
//=====================================================================
#ifndef __IKCP_H__
#define __IKCP_H__
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//=====================================================================
// 32BIT INTEGER DEFINITION
//=====================================================================
#ifndef __INTEGER_32_BITS__
#define __INTEGER_32_BITS__
#if defined(_WIN64) || defined(WIN64) || defined(__amd64__) || \
defined(__x86_64) || defined(__x86_64__) || defined(_M_IA64) || \
defined(_M_AMD64)
typedef unsigned int ISTDUINT32;
typedef int ISTDINT32;
#elif defined(_WIN32) || defined(WIN32) || defined(__i386__) || \
defined(__i386) || defined(_M_X86)
typedef unsigned long ISTDUINT32;
typedef long ISTDINT32;
#elif defined(__MACOS__)
typedef UInt32 ISTDUINT32;
typedef SInt32 ISTDINT32;
#elif defined(__APPLE__) && defined(__MACH__)
#include <sys/types.h>
typedef u_int32_t ISTDUINT32;
typedef int32_t ISTDINT32;
#elif defined(__BEOS__)
#include <sys/inttypes.h>
typedef u_int32_t ISTDUINT32;
typedef int32_t ISTDINT32;
#elif (defined(_MSC_VER) || defined(__BORLANDC__)) && (!defined(__MSDOS__))
typedef unsigned __int32 ISTDUINT32;
typedef __int32 ISTDINT32;
#elif defined(__GNUC__)
#include <stdint.h>
typedef uint32_t ISTDUINT32;
typedef int32_t ISTDINT32;
#else
typedef unsigned long ISTDUINT32;
typedef long ISTDINT32;
#endif
#endif
//=====================================================================
// Integer Definition
//=====================================================================
#ifndef __IINT8_DEFINED
#define __IINT8_DEFINED
typedef char IINT8;
#endif
#ifndef __IUINT8_DEFINED
#define __IUINT8_DEFINED
typedef unsigned char IUINT8;
#endif
#ifndef __IUINT16_DEFINED
#define __IUINT16_DEFINED
typedef unsigned short IUINT16;
#endif
#ifndef __IINT16_DEFINED
#define __IINT16_DEFINED
typedef short IINT16;
#endif
#ifndef __IINT32_DEFINED
#define __IINT32_DEFINED
typedef ISTDINT32 IINT32;
#endif
#ifndef __IUINT32_DEFINED
#define __IUINT32_DEFINED
typedef ISTDUINT32 IUINT32;
#endif
#ifndef __IINT64_DEFINED
#define __IINT64_DEFINED
#if defined(_MSC_VER) || defined(__BORLANDC__)
typedef __int64 IINT64;
#else
typedef long long IINT64;
#endif
#endif
#ifndef __IUINT64_DEFINED
#define __IUINT64_DEFINED
#if defined(_MSC_VER) || defined(__BORLANDC__)
typedef unsigned __int64 IUINT64;
#else
typedef unsigned long long IUINT64;
#endif
#endif
#ifndef INLINE
#if defined(__GNUC__)
#if (__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1))
#define INLINE __inline__ __attribute__((always_inline))
#else
#define INLINE __inline__
#endif
#elif (defined(_MSC_VER) || defined(__BORLANDC__) || defined(__WATCOMC__))
#define INLINE __inline
#else
#define INLINE
#endif
#endif
#ifndef inline
#define inline INLINE
#endif
//=====================================================================
// QUEUE DEFINITION
//=====================================================================
#ifndef __IQUEUE_DEF__
#define __IQUEUE_DEF__
struct IQUEUEHEAD {
struct IQUEUEHEAD *next, *prev;
};
typedef struct IQUEUEHEAD iqueue_head;
//---------------------------------------------------------------------
// queue init
//---------------------------------------------------------------------
#define IQUEUE_HEAD_INIT(name) { &(name), &(name) }
#define IQUEUE_HEAD(name) \
struct IQUEUEHEAD name = IQUEUE_HEAD_INIT(name)
#define IQUEUE_INIT(ptr) ( \
(ptr)->next = (ptr), (ptr)->prev = (ptr))
#define IOFFSETOF(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define ICONTAINEROF(ptr, type, member) ( \
(type*)( ((char*)((type*)ptr)) - IOFFSETOF(type, member)) )
#define IQUEUE_ENTRY(ptr, type, member) ICONTAINEROF(ptr, type, member)
//---------------------------------------------------------------------
// queue operation
//---------------------------------------------------------------------
#define IQUEUE_ADD(node, head) ( \
(node)->prev = (head), (node)->next = (head)->next, \
(head)->next->prev = (node), (head)->next = (node))
#define IQUEUE_ADD_TAIL(node, head) ( \
(node)->prev = (head)->prev, (node)->next = (head), \
(head)->prev->next = (node), (head)->prev = (node))
#define IQUEUE_DEL_BETWEEN(p, n) ((n)->prev = (p), (p)->next = (n))
#define IQUEUE_DEL(entry) (\
(entry)->next->prev = (entry)->prev, \
(entry)->prev->next = (entry)->next, \
(entry)->next = 0, (entry)->prev = 0)
#define IQUEUE_DEL_INIT(entry) do { \
IQUEUE_DEL(entry); IQUEUE_INIT(entry); } while (0)
#define IQUEUE_IS_EMPTY(entry) ((entry) == (entry)->next)
#define iqueue_init IQUEUE_INIT
#define iqueue_entry IQUEUE_ENTRY
#define iqueue_add IQUEUE_ADD
#define iqueue_add_tail IQUEUE_ADD_TAIL
#define iqueue_del IQUEUE_DEL
#define iqueue_del_init IQUEUE_DEL_INIT
#define iqueue_is_empty IQUEUE_IS_EMPTY
#define IQUEUE_FOREACH(iterator, head, TYPE, MEMBER) \
for ((iterator) = iqueue_entry((head)->next, TYPE, MEMBER); \
&((iterator)->MEMBER) != (head); \
(iterator) = iqueue_entry((iterator)->MEMBER.next, TYPE, MEMBER))
#define iqueue_foreach(iterator, head, TYPE, MEMBER) \
IQUEUE_FOREACH(iterator, head, TYPE, MEMBER)
#define iqueue_foreach_entry(pos, head) \
for( (pos) = (head)->next; (pos) != (head) ; (pos) = (pos)->next )
#define __iqueue_splice(list, head) do { \
iqueue_head *first = (list)->next, *last = (list)->prev; \
iqueue_head *at = (head)->next; \
(first)->prev = (head), (head)->next = (first); \
(last)->next = (at), (at)->prev = (last); } while (0)
#define iqueue_splice(list, head) do { \
if (!iqueue_is_empty(list)) __iqueue_splice(list, head); } while (0)
#define iqueue_splice_init(list, head) do { \
iqueue_splice(list, head); iqueue_init(list); } while (0)
#ifdef _MSC_VER
#pragma warning(disable:4311)
#pragma warning(disable:4312)
#pragma warning(disable:4996)
#endif
#endif
//---------------------------------------------------------------------
// WORD ORDER
//---------------------------------------------------------------------
#ifndef IWORDS_BIG_ENDIAN
#ifdef _BIG_ENDIAN_
#if _BIG_ENDIAN_
#define IWORDS_BIG_ENDIAN 1
#endif
#endif
#ifndef IWORDS_BIG_ENDIAN
#if defined(__hppa__) || \
defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \
(defined(__MIPS__) && defined(__MISPEB__)) || \
defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \
defined(__sparc__)
#define IWORDS_BIG_ENDIAN 1
#endif
#endif
#ifndef IWORDS_BIG_ENDIAN
#define IWORDS_BIG_ENDIAN 0
#endif
#endif
//=====================================================================
// SEGMENT
//=====================================================================
struct IKCPSEG
{
struct IQUEUEHEAD node;
IUINT32 conv;
IUINT32 cmd;
IUINT32 frg;
IUINT32 wnd;
IUINT32 ts;
IUINT32 sn;
IUINT32 una;
IUINT32 len;
IUINT32 resendts;
IUINT32 rto;
IUINT32 fastack;
IUINT32 xmit;
char data[1];
};
//---------------------------------------------------------------------
// IKCPCB
//---------------------------------------------------------------------
struct IKCPCB
{
IUINT32 conv, mtu, mss, state;
IUINT32 snd_una, snd_nxt, rcv_nxt;
IUINT32 ts_recent, ts_lastack, ssthresh;
IINT32 rx_rttval, rx_srtt, rx_rto, rx_minrto;
IUINT32 snd_wnd, rcv_wnd, rmt_wnd, cwnd, probe;
IUINT32 current, interval, ts_flush, xmit;
IUINT32 nrcv_buf, nsnd_buf;
IUINT32 nrcv_que, nsnd_que;
IUINT32 nodelay, updated;
IUINT32 ts_probe, probe_wait;
IUINT32 dead_link, incr;
struct IQUEUEHEAD snd_queue;
struct IQUEUEHEAD rcv_queue;
struct IQUEUEHEAD snd_buf;
struct IQUEUEHEAD rcv_buf;
IUINT32 *acklist;
IUINT32 ackcount;
IUINT32 ackblock;
void *user;
char *buffer;
int fastresend;
int nocwnd;
int logmask;
int (*output)(const char *buf, int len, struct IKCPCB *kcp, void *user);
void (*writelog)(const char *log, struct IKCPCB *kcp, void *user);
};
typedef struct IKCPCB ikcpcb;
#define IKCP_LOG_OUTPUT 1
#define IKCP_LOG_INPUT 2
#define IKCP_LOG_SEND 4
#define IKCP_LOG_RECV 8
#define IKCP_LOG_IN_DATA 16
#define IKCP_LOG_IN_ACK 32
#define IKCP_LOG_IN_PROBE 64
#define IKCP_LOG_IN_WINS 128
#define IKCP_LOG_OUT_DATA 256
#define IKCP_LOG_OUT_ACK 512
#define IKCP_LOG_OUT_PROBE 1024
#define IKCP_LOG_OUT_WINS 2048
#ifdef __cplusplus
extern "C" {
#endif
//---------------------------------------------------------------------
// interface
//---------------------------------------------------------------------
// create a new kcp control object, 'conv' must equal in two endpoint
// from the same connection. 'user' will be passed to the output callback
// output callback can be setup like this: 'kcp->output = my_udp_output'
ikcpcb* ikcp_create(IUINT32 conv, void *user);
// release kcp control object
void ikcp_release(ikcpcb *kcp);
int ikcp_recv(ikcpcb *kcp, char *buffer, int len);
int ikcp_send(ikcpcb *kcp, const char *buffer, int len);
// update state (call it repeatedly, every 10ms-100ms)
// 'current' - current timestamp in millisec
void ikcp_update(ikcpcb *kcp, IUINT32 current);
IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current);
// when you received a low level packet (eg. UDP packet), call it
int ikcp_input(ikcpcb *kcp, const char *data, long size);
void ikcp_flush(ikcpcb *kcp);
int ikcp_peeksize(const ikcpcb *kcp);
// change MTU size, default is 14000
int ikcp_setmtu(ikcpcb *kcp, int mtu);
// set maximum window size: sndwnd=32, rcvwnd=32 by default
int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd);
// get how many packet is waiting to be sent
int ikcp_waitsnd(const ikcpcb *kcp);
// fastest: ikcp_nodelay(kcp, 1, 20, 2, 1)
// nodelay: 0:disable(default), 1:enable
// interval: internal update timer interval in millisec, default is 100ms
// resend: 0:disable fast resend(default), 1:enable fast resend
// nc: 0:normal congestion control(default), 1:disable congestion control
int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc);
int ikcp_rcvbuf_count(const ikcpcb *kcp);
int ikcp_sndbuf_count(const ikcpcb *kcp);
void ikcp_log(ikcpcb *kcp, int mask, const char *fmt, ...);
#ifdef __cplusplus
}
#endif
#endif

177
test.cpp Normal file
View File

@ -0,0 +1,177 @@
//=====================================================================
//
// test.cpp - kcp 测试用例
//
// 说明:
// gcc test.cpp -o test -lstdc++
//
//=====================================================================
#include <stdio.h>
#include <stdlib.h>
#include "test.h"
#include "ikcp.c"
// 模拟网络
LatencySimulator *vnet;
// 模拟网络:模拟发送一个 udp包
int udp_output(const char *buf, int len, ikcpcb *kcp, void *user)
{
int id = (int)user;
vnet->send(id, buf, len);
return 0;
}
// 测试用例
void test(int mode)
{
// 创建模拟网络丢包率10%Rtt 60ms~125ms
vnet = new LatencySimulator(10, 60, 125);
// 创建两个端点的 kcp对象第一个参数 conv是会话编号同一个会话需要相同
// 最后一个是 user参数用来传递标识
ikcpcb *kcp1 = ikcp_create(0x11223344, (void*)0);
ikcpcb *kcp2 = ikcp_create(0x11223344, (void*)1);
// 设置kcp的下层输出这里为 udp_output模拟udp网络输出函数
kcp1->output = udp_output;
kcp2->output = udp_output;
IUINT32 current = iclock();
IUINT32 slap = current + 20;
IUINT32 index = 0;
IUINT32 next = 0;
IINT64 sumrtt = 0;
int count = 0;
int maxrtt = 0;
// 配置窗口大小平均延迟200ms每20ms发送一个包
// 而考虑到丢包重发设置最大收发窗口为128
ikcp_wndsize(kcp1, 128, 128);
ikcp_wndsize(kcp2, 128, 128);
// 判断测试用例的模式
if (mode == 0) {
// 默认模式
ikcp_nodelay(kcp1, 0, 10, 0, 0);
ikcp_nodelay(kcp2, 0, 10, 0, 0);
}
else if (mode == 1) {
// 普通模式,关闭流控等
ikcp_nodelay(kcp1, 0, 10, 0, 1);
ikcp_nodelay(kcp2, 0, 10, 0, 1);
} else {
// 启动快速模式
// 第二个参数 nodelay-启用以后若干常规加速将启动
// 第三个参数 interval为内部处理时钟默认设置为 10ms
// 第四个参数 resend为快速重传指标设置为2
// 第五个参数 为是否禁用常规流控,这里禁止
ikcp_nodelay(kcp1, 1, 10, 2, 1);
ikcp_nodelay(kcp2, 1, 10, 2, 1);
}
char buffer[2000];
int hr;
IUINT32 ts1 = iclock();
while (1) {
isleep(1);
current = iclock();
ikcp_update(kcp1, iclock());
ikcp_update(kcp2, iclock());
// 每隔 20mskcp1发送数据
for (; current >= slap; slap += 20) {
*(IUINT32*)(buffer + 0) = index++;
*(IUINT32*)(buffer + 4) = current;
// 发送上层协议包
ikcp_send(kcp1, buffer, 8);
}
// 处理虚拟网络检测是否有udp包从p1->p2
while (1) {
hr = vnet->recv(1, buffer, 2000);
if (hr < 0) break;
// 如果 p2收到udp则作为下层协议输入到kcp2
ikcp_input(kcp2, buffer, hr);
}
// 处理虚拟网络检测是否有udp包从p2->p1
while (1) {
hr = vnet->recv(0, buffer, 2000);
if (hr < 0) break;
// 如果 p1收到udp则作为下层协议输入到kcp1
ikcp_input(kcp1, buffer, hr);
}
// kcp2接收到任何包都返回回去
while (1) {
hr = ikcp_recv(kcp2, buffer, 10);
// 没有收到包就退出
if (hr < 0) break;
// 如果收到包就回射
ikcp_send(kcp2, buffer, hr);
}
// kcp1收到kcp2的回射数据
while (1) {
hr = ikcp_recv(kcp1, buffer, 10);
// 没有收到包就退出
if (hr < 0) break;
IUINT32 sn = *(IUINT32*)(buffer + 0);
IUINT32 ts = *(IUINT32*)(buffer + 4);
IUINT32 rtt = current - ts;
if (sn != next) {
// 如果收到的包不连续
printf("ERROR sn %d<->%d\n", (int)count, (int)next);
return;
}
next++;
sumrtt += rtt;
count++;
if (rtt > maxrtt) maxrtt = rtt;
printf("[RECV] mode=%d sn=%d rtt=%d\n", mode, (int)sn, (int)rtt);
}
if (next > 1000) break;
}
ts1 = iclock() - ts1;
ikcp_release(kcp1);
ikcp_release(kcp2);
const char *names[3] = { "default", "normal", "fast" };
printf("%s mode result (%dms):\n", names[mode], ts1);
printf("avgrtt=%d maxrtt=%d\n", (int)(sumrtt / count), maxrtt);
printf("press enter to next ...\n");
char ch; scanf("%c", &ch);
}
int main()
{
test(0); // 默认模式,类似 TCP正常模式无快速重传常规流控
test(1); // 普通模式,关闭流控等
test(2); // 快速模式,所有开关都打开,且关闭流控
return 0;
}
/*
default mode result (20917ms):
avgrtt=740 maxrtt=1507
normal mode result (20131ms):
avgrtt=156 maxrtt=571
fast mode result (20207ms):
avgrtt=138 maxrtt=392
*/

241
test.h Normal file
View File

@ -0,0 +1,241 @@
#ifndef __TEST_H__
#define __TEST_H__
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <ctype.h>
#include <string.h>
#include "ikcp.h"
#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)
#include <windows.h>
#elif !defined(__unix)
#define __unix
#endif
#ifdef __unix
#include <unistd.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/types.h>
#endif
/* 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
}
#ifdef __cplusplus
#include <list>
#include <vector>
// 带延迟的数据包
class DelayPacket
{
public:
virtual ~DelayPacket() {
if (_ptr) delete _ptr;
_ptr = NULL;
}
DelayPacket(int size, const void *src = NULL) {
_ptr = new unsigned char[size];
_size = size;
if (src) {
memcpy(_ptr, src, size);
}
}
unsigned char* ptr() { return _ptr; }
const unsigned char* ptr() const { return _ptr; }
int size() const { return _size; }
IUINT32 ts() const { return _ts; }
void setts(IUINT32 ts) { _ts = ts; }
protected:
unsigned char *_ptr;
int _size;
IUINT32 _ts;
};
// 均匀分布的随机数
class Random
{
public:
Random(int size) {
this->size = 0;
seeds.resize(size);
}
int random() {
int x, i;
if (seeds.size() == 0) return 0;
if (size == 0) {
for (i = 0; i < (int)seeds.size(); i++) {
seeds[i] = i;
}
size = (int)seeds.size();
}
i = rand() % size;
x = seeds[i];
seeds[i] = seeds[--size];
return x;
}
protected:
int size;
std::vector<int> seeds;
};
// 网络延迟模拟器
class LatencySimulator
{
public:
virtual ~LatencySimulator() {
clear();
}
// lostrate: 往返一周丢包率的百分比,默认 10%
// rttminrtt最小值默认 60
// rttmaxrtt最大值默认 125
LatencySimulator(int lostrate = 10, int rttmin = 60, int rttmax = 125, int nmax = 1000):
r12(100), r21(100) {
current = iclock();
this->lostrate = lostrate / 2; // 上面数据是往返丢包率单程除以2
this->rttmin = rttmin / 2;
this->rttmax = rttmax / 2;
this->nmax = nmax;
}
// 清除数据
void clear() {
DelayTunnel::iterator it;
for (it = p12.begin(); it != p12.end(); it++) {
delete *it;
}
for (it = p21.begin(); it != p21.end(); it++) {
delete *it;
}
p12.clear();
p21.clear();
}
// 发送数据
// peer - 端点0/1从0发送从1接收从1发送从0接收
void send(int peer, const void *data, int size) {
if (peer == 0) {
if (r12.random() < lostrate) return;
if ((int)p12.size() >= nmax) return;
} else {
if (r21.random() < lostrate) return;
if ((int)p21.size() >= nmax) return;
}
DelayPacket *pkt = new DelayPacket(size, data);
current = iclock();
IUINT32 delay = rttmin;
if (rttmax > rttmin) delay += rand() % (rttmax - rttmin);
pkt->setts(current + delay);
if (peer == 0) {
p12.push_back(pkt);
} else {
p21.push_back(pkt);
}
}
// 接收数据
int recv(int peer, void *data, int maxsize) {
DelayTunnel::iterator it;
if (peer == 0) {
it = p21.begin();
if (p21.size() == 0) return -1;
} else {
it = p12.begin();
if (p12.size() == 0) return -1;
}
DelayPacket *pkt = *it;
current = iclock();
if (current < pkt->ts()) return -2;
if (maxsize < pkt->size()) return -3;
if (peer == 0) {
p21.erase(it);
} else {
p12.erase(it);
}
maxsize = pkt->size();
memcpy(data, pkt->ptr(), maxsize);
return maxsize;
}
protected:
IUINT32 current;
int lostrate;
int rttmin;
int rttmax;
int nmax;
typedef std::list<DelayPacket*> DelayTunnel;
DelayTunnel p12;
DelayTunnel p21;
Random r12;
Random r21;
};
#endif
#endif