首页
社区
课程
招聘
[原创]超轻量级tcp实现
发表于: 2009-9-19 23:42 11007

[原创]超轻量级tcp实现

2009-9-19 23:42
11007

轻量级tcp框架
By boywhp@126.com

        一.系统数据层次
        1、Sock接口层
     实现Sock接口以及网络数据包的组装、投递。对应用层接口如下:
struct tcp_link_info{
pvoid on_send_done;//tcp发送确认完成

}

struct sock_buf{
sock_buf* next;                                                //下一个数据包
pvoid head_protos[MAXS_PROTS];        //协议层信息,指向自身buf位置
char *head;                                                                //封装协议头时候,head向下递减
Int status;
int size;
char buf[MAX_SOCK_BUF];
}
head_protos安装具体协议层次:
ARP,IP,[ICMP,TCP,UDP]
收、发送预先设置好默认指针!

Int sock_create(int style, ulong bind_ip);
创建一个sock并绑定于指定ip地址
Int tcp_connect(int sock, ulong ip, word port);
尝试建立tcp连接
Int tcp_get_info(struct *tcp_link_info);
Int tcp_put_info(struct *tcp_link_info);
设置以及获取tcp传输信息
Int tcp_send(int sock, pvoid buf, int size);
发送tcp数据,不确保发送成功(写入发送队列)。
Int tcp_recv(int sock, pvoid buf, int size,int wait);
接收tcp数据<-buf,wait指明等待模式
Int sock_close(int sock);
关闭sock

Socket{
pvoid proto;                         //最上层的协议 tcp,udp,arp,raw
int status;                         //closed,ready,XXX
sock_buf* send_skb;        //在该socket上的发送队列
Sock_buf* recv_skb;        //在该socket上的接收队列
}
sock是一个句柄,内部通过一个struct socket[MAX_SOCK_NUM]的表管理socket, socket[sock]即可访问socket!

Int insert_sock_send(socket, skb)
插入一个skb数据包到socket发送队列
Int insert_sock_recv(socket, skb)
插入一个skb数据包到socket接收队列

        2.协议传输层
Struct proto_lay{
Proto_lay * prev;//协议双链表
Proto_lay * next;
Proto_lay * next_hash;//处理hash值重复问题
sock_buf* send_skb;                        //待发送到skb链
Socket* sock;
Int send_skb(context, skb);
Int on_send_skb_done(context, skb);
Int get_mcu();
Int on_recv_skb(context, skb);
int type;
pvoid alloc_context();//申请上下文
pvoid free_context(pvoid conext);//释放上下文
Pvoid conext;        //根据type决定conext的具体类型
}

Alloc_proto_lay(int type);
Free_proto_lay();

Struct proto_tcp
{
Struct proto_lay;        //继承协议层接口
Int tcp_status;
Int tcp_windows;
Int tcp_seq;
Int tcp_ack;
...
};

proto_tcp* New_proto_tcp();
proto_tcp* Release_proto_tcp();
Void Init_proto_tcp(proto_tcp* tcp);
Int send_tcp_skb(proto_tcp* tcp, skb);

Struct proto_ip{
Struct proto_lay;
Ulong dst_IP;
Ulong src_IP;
....
}

        3.网卡接口
struct net_filter{
Proto_lay* proto;
Int check_pack();
}
//??如何快速的识别数据包,并分发到对应的proto_lay?
通过hash表:
       Eth层   IP层           TCP/UDP层
Hash = style1 + [style2 + IP]  +[style3 + PORT]
计算出Hash值后:
proto_lays[PROTO_MAX_HASH_NUM]; //动态分配
直接递交给proto_lays[hash]->on_recv_skb
如何处理Hash重复的问题!!!添加一个proto_lay->next_hash,形成单链表

struct net_adapter{
Int net_send(pvoid head, pvoid buf, int size);
Int on_net_recv(pvoid frame, int size);
Filter* filter_list;       
}
Reg_net_adapter(net_adapter* adapter);

        4.lookaside内存管理
lookaside内存管理(管理大量相同数据大小的内存申请和释放)
Struct look_aside_pack{
Look_aside_pack* next;
Int pack_max;
Int pack_size;
PBIT mask;                                 //数据区masks[pack_max]
Pvoid pack;                         //实际数据区packs[pack_max]
}
Struct look_aside_pools{

Look_aside_pack* pack[HASH_SIZE];        //256 hashs - 长度 & FF
}

Alloc_look_aside_pack();

        5、socket输入输出环形缓冲区
Struct ring_buf{
Int size;
Pvoid out_buf;
Pvoid in_buf;
Pvoid buf;
}

ring_buf* alloc_ring_buf(size); //申请环形缓存
Void release_ring_buf(size);//释放环形缓冲
Int write_ring_buf(ring_buf, buf, size);//写入环形缓存
Int read_ring_buf(ring_buf, buf, size);
读取环形缓存,buf=NULL,不拷贝,直接移动!!以便实现0拷贝

???全局proto_lays[MAX_PROTO_LAYS_NUM];通过proto_id即可获取协议层
具体的传输细节处理?怎么实现?如tcp控制窗口以及重传等机制如何实现?

        6、sock异步接口
Struct sock_tcp_events{
        On_connect(int sock);                 //连接成功
        On_request(int sock);                 //连接请求 - 可以暂不实现,自动建立连接
        On_send_completed(int sock, int size); //发送成功-ACK确认
        On_recv(int sock, buf, size);//接受到数据包
}
如何实现一个端口上listen<-接受多个tcp连接?在listen状态下的80端口,并没有目的端口,如何接受数据?即并没有hash值!!
每个最上层的协议提供一个自己的hash计算函数:
Int make_tcp_recv_hash(struct skb);
XXXX
listen时候 hash = tcp协议 + listen端口
其他时候:hash = tcp协议 + src_port + dst_port + src_ip + dst_ip

        二.数据基本流程
        1.数据发送
1、        tcp_send(int sock, buf, size)
2、        通过sock号获取Socket数据结构
3、        写入socket的环形输入缓冲区
4、        主线程循环读取环形缓冲区,测试是否有数据发送。
5、        申请lookaside skb数据结构,初始化并插入Socket发送队列
4、获取socket->proto,[proto_lay]数据结构
5、调用proto_lay->send_skb发送数据包
6、调用proto_tcp->tcp_send_skb封装tcp头
7、调用proto_tcp->next->send_skb,发送到下层协议
8、调用proto_ip->send_skb封装ip头

        2.数据接收
1、adapter->on_net_recv(pvoid frame, int size);
2、从下到上分析proto层次,计算hash值
3、通过hash表获取接收协议的proto_lay层次
4、组skb包
5、遍历next_hash链表,依次调用proto_lay->on_recv_skb(skb);
6、按协议双链表向上依次执行proto_lay->on_recv_skb(skb);解协议包
7、一旦proto_lay->sock!=NULL,将skb挂入socket->recv_skb队列
8、用户通过tcp_recv(sock);进行数据接收

        3.TCP连接的建立
1、tcp_connect(sock,ip,port)
2、(tcp_proto*)(socket->proto)->connect(tcp_proto*, ip,port)
3、根据tcp协议状态图执行,记录dest_addr,dst_port,src_port
4、new_skb(),insert_sock_send(sock, skb) //sock写入发送队列
5、tcp_proto->send_skb(skb),next->dst_Ip = dest_addr.ip;
测试skb->status,写入tcp syn连接数据.
6、next->send_skb(skb)
主动连接:
1        tcp_proto->connect = tcp_proto_connect(主动连接)
2        初始化skb填写tcp连接头
3        tcp_send_skb发送该skb连接包,将skb挂入发送队列
4        等待on_tcp_recv接受服务段syn+ack 或者超时重发syn连接包(超时重发:直接将包->ip层)
5        on_tcp_syn_send_recv接受到syn+ack,申请skb应答ack并发送
6        完成3次握手->进入数据状态

        4.数据包的拆分以及重组
合并:大量的待发送的小数据包,将在发送队列进行重组。首先检查发送队列是否为空,不空的话就尝试和下一个数据包合并,即tcp粘包现象。可以强制立即发送,不等待发送线程调度。
拆分:如果大数据包进入队列,进行数据拆分,依次进入发送队列。或者发送当前队列数据包时候超过发送协议MCU,自动拆分数据包
队列操作涉及到线程锁问题!!!

Socket输入输出缓存不使用skb队列,直接读写环形缓冲区。!!!

        5.数据包0拷贝的实现
数据包在未确认发送成功时,不移动环形缓冲区的读位置,其数据并不拷贝到skb中,只是记录在skb->head_protos[]中,skb->buf中只记录数据协议头封包。最终发送给网卡net_send(head, buf, head_size, buf_size)实现发送0拷贝。
        6.数据发送线程
        7.tcp超时重传问题
注意数据包的重传机制位于协议层,每个协议层维护一个skb链表。以便实现超时重传。由于tcp滑动窗口协议,不会导致skb重组,因此不必变动skb包,只需超时重发即可。
一旦接受到确认ack,tcp计算确认的长度,然后遍历skb发送链路。删除已确认的skb!不允许出现确认skb包的一部分!

        8.tcp一个端口监听,多个tcp连接
tcp在指定端口监听后,如何应对多个tcp连接请求,并建立tcp通信链路:提供accept调用。返回建立连接的socket链路!tcp在src_port上监听, accept调用在线程阻塞等待tcp连接建立,一旦连接建立,创建一个tcp_socket,初始tcp连接参数,设置接受hash = src_port+dst_port,返回创建的socket,accept同时提供异步接口,由用户查询返回结果,或者通过注册tcp_events回调函数实现。流程如下:
1.on_tcp_listen_recv 远程主机SYN连接请求,应答SYN+ACK
2.等待ACK,建立三次握手,sock_tcp_events->on_request
3.on_tcp_request(sock.c),测试socket.accept_max参数
4.创建一个tcp_socket,设置socket参数
5.初始化tcp连接,注册接受函数
6.将socket挂入tcp->accept_header队列
7.用户程序调用sock_accept
8.从socket->tcp.accept列表里面获取等待accept的socket,并从链表中删除

        9.tcp分组数据接收以及重排
一旦接收到的tcp_hdr->seq > tcp.ack时候,说明需要接收tcp数据,此时tcp处理流程如下:
1.申请一个skb, skb的数据指针->对应socket的环形缓冲区recv_buf
2.将接收的数据拷贝到recv_buf中,位置通过seq计算【相对in_buf指针位置】。
3.检查当前接收数据包序号是否可以立即接收,不需要重排。
4.将需要重排的数据包按照seq次序插入接收队列,同时发送ack-以通知发送可立即接收的数据包。
5.如果是可以立即接收,则检查接收队列,序号连续,并in_ring_buf,发送最后一个确认ack!
        10、tcp接收0 windows问题
在同步接收数据模式下,由于发送方数据填充windows,接收数据部分处理不及时,出现应答ack0窗口现象,导致tcp发送方停止5s左右再发送数据。为避免该现象,应该延迟发送ack,只有在接收方有足够空闲时候发送ack,且不能阻塞tcp主接收线程。方案如下:
1.insert_skb_recv_buf 将接收到的skb,复制到缓冲区并按照seq大小插入recv_skb_header链表。
2.sock_read从recv_buf中同步out数据
3.main_thread[定时器]中,检查sock接收缓冲,如果空闲空间足够大,调用sock->recv_buf_free。
4.tcp_free_recv_skb释放skb接收队列->tcp.ack并回应ack

        三.实现步骤
        1.完成通用内存管理、hash-obj表
hash-obj表:主要用来动态申请obj,以及通过hash的快速查找obj,结合内存管理实现obj管理。
Struct hash_obj{
int hash;
int obj_size;
pvoid object;
}

Struct hash_obj_pool{

int max_obj;
hash_obj* hash_table;
}

Int init_hash_obj_pool(hash_obj_pool*, max_objs, size);
viod free_hash_obj_pool(hash_obj_pool*);
hash_obj* alloc_hash_pool_obj(hash_obj_pool*,obj_size);
viod release_hash_pool_obj(hash_obj_pool*, pviod obj);
pvoid* get_hash_pool_obj(hash_obj_pool*, int hash);
从hash表中快速获取obj, hash_table[hash]->object

编写内存位操作支持函数,以便内存管理mask
void set_bit(void *addr, int pos);
void clr_bit(void *addr, int pos);
int get_first_bit(void *addr, int size);

Init_hash_table(hash_max);
Insert_hash_table(int hash, pvoid obj);
Pvoid Get_hash_table(int hash, pvoid obj);

        2.sock接口层sock_create
socket定义,socket-hash表,完成hash表测试,
Sock_create流程:
1、通过hash_obj管理申请一个socket
2、初始化socket,输入输出环形缓冲区、状态以及最上层proto_lay;
专门一个tcp_lay_pool,[tcp_lay];tcp_lay.proto.context = tcp_lay;
alloc_tcp_lay();
init_tcp_lay();   //设置函数指针以及协议链
alloc_ip_lay();
init_ip_lay();
Socket->proto = tcp_lay;
        3.环形缓冲区
测试OK!struct sock_ring_buf

        4.网络数据收发接口(测试环境)
Ok~struct sock_net_nic
        5.网络数据发送线程
1、发送线程扫描每个socket的发送环形缓冲队列
2、获取协议层的当前最大包长度MSS,以及允许发送长度Windows
3、发送线程申请skb数据包,填充Windows,包大小为MSS,如果缓冲区回环,则切割为两个skb包,以便0拷贝技术【不读取实际数据和移动环形缓冲区读写指针!/只有在确认发送成功后移动读指针】。


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

上传的附件:
收藏
免费 7
支持
分享
最新回复 (7)
雪    币: 215
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
在写毕业论文吧。。
2009-9-20 00:04
0
雪    币: 1233
活跃值: (907)
能力值: ( LV12,RANK:750 )
在线值:
发帖
回帖
粉丝
3
啊哈,难道现在流行在网上搞毕业论文审核?呵呵,不是毕业论文,是项目需要,征得BOSS同意,就开源了其中的TCP实现
我靠。难道都是下载不留名的?
2009-9-20 08:29
0
雪    币: 234
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
LZ是在做嵌入式开发么?
去年我们电子设计竞赛的时候,我写过类似的东西。。很头大。。。
2009-9-21 09:16
0
雪    币: 1233
活跃值: (907)
能力值: ( LV12,RANK:750 )
在线值:
发帖
回帖
粉丝
5
准备学arm
2009-9-21 11:20
0
雪    币: 204
活跃值: (14)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
学习了,向高手致敬!
2009-9-23 18:36
0
雪    币: 33
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
嗯.我下载留名来了,

为什么代码的注释是乱码? 这是什么语?
  /* 濡傛灉鏄摼琛ㄥご */

还有一个问题是, google 上面的这个是怎么用的?一直都没弄明白
2009-9-23 18:56
0
雪    币: 1233
活跃值: (907)
能力值: ( LV12,RANK:750 )
在线值:
发帖
回帖
粉丝
8
用svn客户端,就是传说中的小海龟?不应该是乱码啊?
http://code.google.com/p/tinyshell/source/browse/#svn/trunk/src/tcp_sock
可以在线浏览代码
2009-9-23 19:24
0
游客
登录 | 注册 方可回帖
返回
//