打印
[应用相关]

没事开个源,只准看不准用,随便玩玩的TCPIP协议栈

[复制链接]
3319|19
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 Simon21ic 于 2015-5-1 13:47 编辑

开发目的:没有目的,闲的蛋疼+钱多了没地方用
开源目的:为了以后找TCPIP相关的外包和兼职人员更加方便

不建议非兼职/外包人员放着成熟稳定的uip和lwip不用,而用我的协议栈。

一些基本的说明:
1. 针对cortexM或者类似等级的处理器
2. 基本的系统构架是基于vsfsm的协作式多任务内核,vsfip的API接口,都是PT函数
3. 协议栈规模介于uip和lwip之间
4. UDP应用层可以只用PT线程的方式,也可以使用回调,TCP只支持PT线程
5. 面向对象的代码
6. buffer结构不使用链表,直接一个连续的内存,简单粗暴
7. socket层的API接口,类似windows和linux的socket接口
8. 巨多的BUG,以及各种明坑暗坑
9. 总之,最大的特点就是没特点,非兼职外包人员绕道

开源地址,在vsf的stack里:github.com/versaloon/vsf
vsf/stack/vsfip目录下,不定期更新

目录结构上的一些说明:

netif是底层的网络接口,目前底层只实现了eth以太网,其他的什么PPP等,都没有做。
proto下面是一些支持的协议,目前只有DHCP,以后会陆续增加DNS,HTTP等
vsfip.c是核心的协议栈代码,vsfip_buffer.c是用户根据应用,使用不同的内存分配方法

代码导读(以太网为例):
1. 首先看vsfip_buffer.c中的内存处理
2. 数据接收过程
void vsfip_eth_input(struct vsfip_buffer_t *buf)
{
        struct vsfip_ethhead_t *head = (struct vsfip_ethhead_t *)buf->buf.buffer;
        
        buf->buf.buffer += sizeof(struct vsfip_ethhead_t);
        buf->buf.size -= sizeof(struct vsfip_ethhead_t);
        switch (BE_TO_SYS_U16(head->type))
        {
        case VSFIP_ETH_TYPE_IP:
                vsfip_netif_ip4_input(buf);
                break;
        case VSFIP_ETH_TYPE_IP6:
                vsfip_netif_ip6_input(buf);
                break;
        case VSFIP_ETH_TYPE_ARP:
                vsfip_netif_arp_input(buf);
                break;
        default:
        case VSFIP_ETH_TYPE_RARP:
                // not supported
                vsfip_buffer_release(buf);
        }
}
底层驱动收到以太网数据包后,调用vsfip_eth_input,如果是ARP,调用vsfip_netif_arp_input交由netif通用层里的ARP处理,如果是IPv4或者IPv6,调用vsfip_netif_ip4_input或者vsfip_netif_ip6_input。
        switch (head->op)
        {
        case ARP_REQUEST:
                // for ARP request, send sem to arps.sm
                bufptr = (uint8_t *)head + sizeof(struct vsfip_arphead_t);
                if ((head->hwlen != netif->macaddr.size) ||
                        (head->protolen != netif->ipaddr.size) ||
                        (memcmp(bufptr + 2 * head->hwlen + head->protolen,
                                        netif->ipaddr.addr.s_addr_buf, head->protolen)))
                {
                        break;
                }
               
                buf->next = NULL;
                vsfip_bufferlist_queue(&netif->arps.requestlist, buf);
                vsfsm_sem_post(&netif->arps.sem);
                return;
                break;
netif中的ARP,自己看代码,实现了2个线程,分别用于ARP server以及ARP client。vsfip_netif_arp_input会把ARP_REQUEST的报文放到arps.requestlist队列里,并且发送信号,等待ARP server处理。ARP server收到信号后,解析ARP请求,并且发送应答(TODO:之后会优化掉ARP server进程,直接解析并发送应答)。
vsfip_netif_ip4_input只是一个netif层中的过渡接口,直接调用vsfip_ip4_input提交给TCPIP协议栈。
/*        if (is multicast)
        {
        }
        else
*/        {
                switch (iphead->proto)
                {
                case IPPROTO_UDP:
                        vsfip_udp_input(buf);
                        break;
                case IPPROTO_TCP:
                        vsfip_tcp_input(buf);
                        break;
                case IPPROTO_ICMP:
                        vsfip_icmp_input(buf);
                        break;
                default:
                        vsfip_buffer_release(buf);
                        break;
                }
        }
vsfip_ip4_input解析报文后,会根据IP头的设置,处理IP组包(特么设想很好啊,现在根本没有支持)。如果组包得到一个完整的IP报文,则根据类型,调用vsfip_udp_input/vsfip_tcp_incpu/vsfip_icmp_input。就用简单的UDP举例吧。
static void vsfip_udp_input(struct vsfip_buffer_t *buf)
{
        struct vsfip_udphead_t *udphead = (struct vsfip_udphead_t *)buf->buf.buffer;
        
        // endian fix
        udphead->port.src = BE_TO_SYS_U16(udphead->port.src);
        udphead->port.dst = BE_TO_SYS_U16(udphead->port.dst);
        udphead->len = BE_TO_SYS_U16(udphead->len);
        udphead->checksum = BE_TO_SYS_U16(udphead->checksum);
        
        buf->app.buffer = buf->buf.buffer + sizeof(struct vsfip_udphead_t);
        buf->app.size = buf->buf.size - sizeof(struct vsfip_udphead_t);
        vsfip_proto_input(vsfip.udp_listeners, buf, &udphead->port);
}
vsfip_udp_input处理好UDP报文头后,会调用vsfip_proto_inpput(通用的数据处理)。
        if (socket->callback.input != NULL)
        {
                socket->callback.input(socket->callback.param, buf);
        }
        else
        {
                buf->ttl = VSFIP_CFG_TTL_INPUT;
                vsfip_bufferlist_queue(&socket->input_bufferlist, buf);
                vsfsm_sem_post(&socket->input_sem);
        }
vsfip_proto_input也都是一些无聊的代码,匹配socket,并且如果socket设置了callback,就直接调用callback,如果没有的话,就把收到的报文加进input_bufferlist队列,并且发送事件给socket的input_sem。
        if (socket->callback.input != NULL)
        {
                socket->callback.input(socket->callback.param, buf);
        }
        else
        {
                buf->ttl = VSFIP_CFG_TTL_INPUT;
                vsfip_bufferlist_queue(&socket->input_bufferlist, buf);
                vsfsm_sem_post(&socket->input_sem);
        }
UDP可以通过设置callback来实现callback方式,顺便说一下,TCP不支持callback,是因为TCP本来就是callback,所以API接口上,就不能使用callback。
vsf_err_t vsfip_udp_recv(struct vsfsm_pt_t *pt, vsfsm_evt_t evt,
                        struct vsfip_socket_t *socket, struct vsfip_sockaddr_t *sockaddr,
                        struct vsfip_buffer_t **buf)
{
        vsfsm_pt_begin(pt);
        
        if ((NULL == socket) || (NULL == sockaddr) || (NULL == buf) ||
                !socket->local_sockaddr.sin_port)
        {
                return VSFERR_INVALID_PARAMETER;
        }
        
        socket->remote_sockaddr = *sockaddr;
        
        *buf = vsfip_udp_match(socket, sockaddr);
        if (*buf != NULL)
        {
                return VSFERR_NONE;
        }
        
        // set pending with timeout
        if (socket->timeout_ms)
        {
                socket->rx_timer.interval = socket->timeout_ms;
                socket->rx_timer.sm = pt->sm;
                socket->rx_timer.evt = VSFIP_EVT_SOCKET_TIMEOUT;
                vsftimer_register(&socket->rx_timer);
        }
        
        while (1)
        {
                if (vsfsm_sem_pend(&socket->input_sem, pt->sm))
                {
                        evt = VSFSM_EVT_NONE;
                        vsfsm_pt_entry(pt);
                        if ((evt != VSFIP_EVT_SOCKET_RECV) &&
                                (evt != VSFIP_EVT_SOCKET_TIMEOUT))
                        {
                                return VSFERR_NOT_READY;
                        }
                        if (VSFIP_EVT_SOCKET_RECV == evt)
                        {
                                *buf = vsfip_udp_match(socket, sockaddr);
                                if (*buf != NULL)
                                {
                                        vsftimer_unregister(&socket->rx_timer);
                                        return VSFERR_NONE;
                                }
                        }
                        else if (VSFIP_EVT_SOCKET_TIMEOUT == evt)
                        {
                                vsftimer_unregister(&socket->rx_timer);
                                vsfsm_sync_cancel(&socket->input_sem, pt->sm);
                                return VSFERR_FAIL;
                        }
                }
                else
                {
                        *buf = vsfip_udp_match(socket, sockaddr);
                        if (*buf != NULL)
                        {
                                vsftimer_unregister(&socket->rx_timer);
                                return VSFERR_NONE;
                        }
                }
        }
        
        vsfsm_pt_end(pt);
        return VSFERR_NONE;
}
应用层调用了vsfip_udp_recv后,会先在input_bufferlist里看看有没有要的数据,没有的话,如果需要就注册个超时定时器,之后就等待input_sem信号。vsfip_proto_input发送了input_sem信号后,就会唤醒这个线程,继续运行下去。之后当然就是判断唤醒的时间,可以是接收到到数据的时间,也可以是定时器发来的超时事件。注意,这2个事件是互斥的,收到一个后,要取消掉另一个。
注意,数据报文的内存,是底层驱动,调用vsfip_buffer_get分配的,并且在处理过程中,任何层里,一旦处理完成,必须调用vsfip_buffer_release释放。

3. 发送过程,还是用UDP,这个就太简单了,UDP发送虽然接口上是使用PT,但是,代码完全是非阻塞的,直接add_head后,调用vsfip_ip_output发送IP报文。vsfip_proto_input会根据版本,调用IPv4或者IPv6的output接口。
static vsf_err_t vsfip_ip4_output(struct vsfip_socket_t *socket)
{
        struct vsfip_ippcb_t *pcb = &socket->pcb.ippcb;
        vsf_err_t err = VSFERR_NONE;
       
        if (pcb->buf->buf.size >
                        (pcb->buf->netif->mtu - sizeof(struct vsfip_ip4head_t)))
        {
                err = VSFERR_NOT_ENOUGH_RESOURCES;
                goto cleanup;
        }
       
        pcb->id = vsfip.ip_id++;
        err = vsfip_add_ip4head(socket);
        if (err) goto cleanup;
       
        return vsfip_netif_ip_output(pcb->buf);
cleanup:
        vsfip_buffer_release(pcb->buf);
        return err;
}
这里就只说vsfip_ip4_output,也就是一样操作add_head,调用vsfip_netif_ip_output交由netif层去处理。
vsf_err_t vsfip_netif_ip_output(struct vsfip_buffer_t *buf)
{
        struct vsfip_netif_t *netif = buf->netif;
        struct vsfip_macaddr_t *mac;
        struct vsfip_ipaddr_t dest;
        vsf_err_t err = VSFERR_NONE;
       
        vsfip_netif_get_ipaddr(buf, &dest);
        mac = vsfip_netif_get_mac(netif, &dest);
        if (NULL == mac)
        {
                vsfip_bufferlist_queue(&netif->arpc.requestlist, buf);
                err = vsfsm_sem_post(&netif->arpc.sem);
                if (err) goto cleanup; else goto end;
        }
        else if (buf->buf.size)
        {
                return vsfip_netif_ip_output_do(buf, VSFIP_NETIF_PROTO_IP, mac);
        }
       
cleanup:
        vsfip_buffer_release(buf);
end:
        return err;
}
vsfip_netif_ip_output里,会先从ARP缓冲里匹配MAC地址,如果没有匹配到的话,那就加到arpc(ARP client)的requestlist里去,并发送事件。之后ARPC会执行ARP,得到MAC后,把这个报文发给驱动层处理。
注意,无论成功还是出错,传进来的buf都需要被release。

其他N多细节,自己看吧。
TCP的状态机比较复杂,目前也在完善中。
外包兼职人员,有问题可以直接问我,希望参与的人,玩的开心。
沙发
wank2887| | 2015-5-1 08:44 | 只看该作者
先收藏 :lol

使用特权

评论回复
板凳
lkl0305| | 2015-5-1 09:53 | 只看该作者
学习下哈

使用特权

评论回复
地板
那就地方iv| | 2015-5-1 10:07 | 只看该作者
谢谢楼主分享

使用特权

评论回复
5
mochou| | 2015-5-1 16:26 | 只看该作者

使用特权

评论回复
6
charrijon| | 2015-5-2 16:52 | 只看该作者
谢谢,正好在玩这个。用stm32发送UDP包给PC,在PC中截数据包看到的数据都正确,可是用网络调试助手,老是读不到,求教啊

使用特权

评论回复
7
Serge_Ding| | 2015-5-2 20:14 | 只看该作者
学习了,楼主好强大

使用特权

评论回复
8
Simon21ic|  楼主 | 2015-5-3 05:06 | 只看该作者
charrijon 发表于 2015-5-2 16:52
谢谢,正好在玩这个。用stm32发送UDP包给PC,在PC中截数据包看到的数据都正确,可是用网络调试助手,老是读 ...

我敢打赌,你正好玩的不是我的协议栈。

使用特权

评论回复
9
Simon21ic|  楼主 | 2015-5-3 05:07 | 只看该作者
Serge_Ding 发表于 2015-5-2 20:14
学习了,楼主好强大

在TCPIP方面确实不强,没什么经验,这个是第一次做

使用特权

评论回复
10
小浣熊| | 2015-5-3 22:18 | 只看该作者
全面细致,深入剖析。。。

使用特权

评论回复
11
Simon21ic|  楼主 | 2015-5-4 00:45 | 只看该作者
小浣熊 发表于 2015-5-3 22:18
全面细致,深入剖析。。。

这个只是最粗略的,详细的要自己看了

使用特权

评论回复
12
charrijon| | 2015-5-5 18:58 | 只看该作者
本帖最后由 charrijon 于 2015-5-5 21:36 编辑
Simon21ic 发表于 2015-5-3 05:06
我敢打赌,你正好玩的不是我的协议栈。

的确不是你的协议栈,我是自己按照以前51的例程改的,目前与网络调试工具已经调通。但是有一件奇怪的事情,我如果按照全双工的模式工作,即pc端与stm端各自发送数据,那么pc发送的数据就会出错,用抓包软件看的。stm发送至pc端的数据照样正确,不知道是什么原因,还要查

使用特权

评论回复
13
xuan309170083| | 2015-5-5 21:28 | 只看该作者
不错

使用特权

评论回复
14
81190865| | 2015-5-6 12:20 | 只看该作者
强!

使用特权

评论回复
15
阳光豆苗| | 2015-5-6 13:41 | 只看该作者
感觉挺牛B。顶一个。

使用特权

评论回复
16
yinhaix| | 2015-5-7 14:12 | 只看该作者
谢谢楼主分享

使用特权

评论回复
17
andydisplay| | 2015-5-7 16:07 | 只看该作者
强!

使用特权

评论回复
18
yhy123456| | 2015-5-7 20:53 | 只看该作者
路过 ,看看

使用特权

评论回复
19
Simon21ic|  楼主 | 2015-5-10 01:29 | 只看该作者
charrijon 发表于 2015-5-5 18:58
的确不是你的协议栈,我是自己按照以前51的例程改的,目前与网络调试工具已经调通。但是有一件奇怪的事情 ...

TCPIP协议栈以及底层驱动,甚至还包括高层的应用,调试稳定并不是很容易的
有时候,花了几天调试的问题,结果就只是一个小地方而已,不过乐趣不就在这里吗?

使用特权

评论回复
20
搞IT的| | 2015-5-10 10:19 | 只看该作者
我也正想研究一下,具体项目中还没用到过。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:www.versaloon.com --- under construction

266

主题

2597

帖子

104

粉丝