KCP 协议基本数据结构和算法介绍

(本文Github地址为: learning-kcp-protocol)

在特定的应用场合,单纯的使用 TCP 不能满足需要。直接使用 UDP 数据报不能保证数据的可靠性,常需要在应用层基于 UDP 实现一套可靠的传输协议。

直接使用 KCP 协议是一种选择,它实现了健全的自动重传协议,并在此之上提供了自由的参数调整。通过配置参数和合适的调用方式来适应不同场景的需求。

KCP 简介:

KCP是一个快速可靠协议,能以比 TCP 浪费 10%-20% 的带宽的代价,换取平均延迟降低 30%-40%,且最大延迟降低三倍的传输效果。纯算法实现,并不负责底层协议(如UDP)的收发,需要使用者自己定义下层数据包的发送方式,以 callback的方式提供给 KCP。 连时钟都需要外部传递进来,内部不会有任何一次系统调用。
整个协议只有 ikcp.h, ikcp.c两个源文件,可以方便的集成到用户自己的协议栈中。也许你实现了一个P2P,或者某个基于 UDP的协议,而缺乏一套完善的ARQ可靠协议实现,那么简单的拷贝这两个文件到现有项目中,稍微编写两行代码,即可使用。

本文对 KCP 协议的基础收发流程、拥塞窗口、超时算法作简单介绍,并同时提供了参考的示例代码。

参阅的 KCP 的版本为撰写文章时的最新版本。本文不会完全贴上所有KCP的源代码,会在关键处添加指向源代码相应位置的链接。

阅读更多

CentOS 中的 iptables 与 firewalld 防火墙基本策略

在 CentOS 中的 firewalld 是基于 iptables 和一些其它程序构建的,firewalld 使用了一些更加友好的配置方式来实现了对 iptables 操作。同时还扩展了一些 iptables 本身不支持的功能,例如定时防火墙规则。完整的程序以及库依赖见 firewalld 官网 https://firewalld.org/

iptables 通过操作 Linux 内核 netfilter 模块来针对网络包处理。

在操作命令之前请临时关闭机器的 firewalld 防火墙,同时需要确保有物理机的访问权限(或者虚拟机中的物理终端操作权限),有些规则会屏蔽 SSH 22 端口的访问导致无法后续操作。

# 停用 firewalld
systemctl stop firewalld

iptables 基础概念和操作

策略链 (Policy Chain)

iptables 的过滤规则是通过匹配策略链上的规则来决定不同情况下对包的处理,包含了三种策略链:

INPUT: 通过此链来控制传入的连接和包,例如允许来自某些 IP 的 SSH 传入连接。

FORWARD: 转发控制链接,如果你需要在机器上配置路由功能时会用到此链。

OUTPUT: 传出链,当前主机发送请求到外部时匹配此链,通常没有特殊需求时也不会给它配置特殊规则。

可以通过下列命令来查看当前设置的 iptables 规则:

~]# iptables -S
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT

策略链默认行为

对应的策略链会有默认的行为。意味着定义了未匹配任何规则的时,链对数据包的处理。

使用如下命令查看当前策略链的行为(同时输出了未匹配此链的数据包数量和流量)。

~]# iptables -L -v | grep policy
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
Chain OUTPUT (policy ACCEPT 821 packets, 293K bytes)

如果当前的策略链的行为不是如上所示,可以通过下列命令来设置接受所有数据的行为:

iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT
iptables -P FORWARD ACCEPT

可以尝试如下命令丢弃所有情况下的包:

iptables -P INPUT DROP
iptables -P OUTPUT DROP
iptables -P FORWARD DROP

注意:此时外部主机将无法访问主机,同时主机内部也无法访问外部。

阅读更多

lua table 源码阅读

近日配置了下终端下的 vim 插件,把 c 相关的开发插件都弄的勉强可以用了,为了熟悉下 GDB 的一些基本操作,就拿读 lua 部分源代码来练手了。

这里所有的内容均基于 lua 5.3 版本。

table 的内部操作定义于文件 ltable.h / ltable.c 中,有以下相关的函数构成:

  • luaH_new : 构造一个新的 table

1.初始化

Table *luaH_new (lua_State *L) {
  GCObject *o = luaC_newobj(L, LUA_TTABLE, sizeof(Table));
  Table *t = gco2t(o);
  t->metatable = NULL;
  t->flags = cast_byte(~0);
  t->array = NULL;
  t->sizearray = 0;
  setnodevector(L, t, 0);
  return t;
}

首先由 lua gc 模块分配一个被托管的垃圾回收对象 GCObject *o, 然后转为一个指向实际的 table 结构的 Table *t 进行初始化操作。

一个 lua table 被分为两个部分:数组部分和哈希表部分。

数组部分的初始化比较粗暴,直接置零。

t->array = NULL;
t->sizearray = 0;

则哈希表的部分由 setnodevector 函数来初始化,由于传入的特殊表大小 0,哈希表部分被指向一个 所有哈希部分为空的 table 所共享的一个静态变量 dummynode (此处为一个宏)

static void setnodevector (lua_State *L, Table *t, unsigned int size) {
  int lsize;
  if (size == 0) {  /* no elements to hash part? */
    t->node = cast(Node *, dummynode);  /* use hashnode 'dummynode' */
    lsize = 0;
  }
  else {
  ....

阅读更多