函数netconn_new用来创建一个新的连接结构。连接结构的类型可以选择为TCP或UDP等。函数结构原型如下所示,参数type描述了连接的类型,可以为NETCONN_TCP或NETCONN_UDP等,这里都以TCP作为讨论的对象。
struct netconn* netconn_new(enum
netconn_type type)
该函数首先调用netconn_alloc函数分配并初始化一个netconn结构。初始化的过程包括设置netconn结构类型字段,同时为该结构的op_completed创建一个信号量、recvmbox字段创建一个接收邮箱。奇怪的是netconn_alloc函数并不是在文件api_lib.c文件中,而是在api_msg.c中,凌乱!接下来函数netconn_new会构建一个api_msg消息,该消息要求内核执行函数do_newconn,最后函数tcpip_apimsg用来将消息包装成tcpip_msg结构并发送出去。tcpip_thread函数解析该消息并调用函数do_newconn,do_newconn根据参数的类型最终调用函数tcp_new创建一个TCP控制块。tcpip_apimsg会阻塞在一个信号量上,直至do_newconn释放该信号量。
函数netconn_delete用来删除一个连接结构netconn。与前面的流程相同,它通过消息告诉内核执行do_delconn,调用tcp_close函数关闭TCP连接。而后netconn_delete调用netconn_free函数释放netconn结构的空间。注意这里的netconn_free函数netconn_alloc函数一样,也不是在文件api_lib.c文件中,而是在api_msg.c中,尽管他们都是netconn_xxx结构。
netconn_bind用于将一个IP地址及端口号与结构netconn绑定。事实上,内核是通过函数do_bind调用tcp_bind完成相应TCP控制块得绑定工作的。
netconn_connect函数一般在客户端调用,用以将服务器端的IP地址和端口号与本地的netconn结构绑定。该函数与内核tcp_connect函数对应。
netconn_listen函数一般在服务器端调用,用于将某个TCP控制块置为LISTEN状态。类似的函数do_listen会被调用,该函数有两个重要的工作:为结构netconn字段acceptmbox创建邮箱,该邮箱用于接受外部连接;向相应TCP的PCB控制块中accept字段注册一个回调函数accept_function,当该PCB上有新连接出现时,回调函数会被调用,以向上面的acceptmbox邮箱中发送消息,告诉应用程序有新的连接到来,新连接的信息以netconn结构形式被保存在了邮箱中。
netconn_accept函数在服务器上使用,用于接收远端的连接,该函数主要在阻塞在上面所述的acceptmbox邮箱上,当接收到新的连接后,在该邮箱上取下连接的netconn结构并返回。
netconn_recv函数用于接收数据,接收到得数据被封装为netbuf结构。这里内核函数tcp_recved会被协议栈调用,以通知内核数据被正常接收,内核因此调整发送窗结构,返回ACK确认等。
函数netconn_write用于向相应的TCP连接上发送数据,主要这个函数只用于发送TCP数据,用于发送UDP数据的函数叫netconn_send,这里先不讨论。netconn_write函数原型如下,它用于将dataptr指向的size个数据放到连接conn的发送队列上,apiflags用于描述
err_t netconn_write(struct netconn
*conn, const void *dataptr, int size, u8_t apiflags)
对该数据的操作,包括是否拷贝,是否立即发送两种选择。最后netconn_close函数用于主动关闭连接。
API函数就说这么一点点了。下面我们用这些API函数构造一个服务器程序。这个服务器程序很简单,它能响应一个客户端的连接和数据请求,并向客户端发送一个固定字符串。任务代码如下:
const uint8 data[]= "hello
world!"; // 待发送字符串
void mytcp_thread(void *arg)
{
struct netconn * conn,
*newconn; // API描述的连接结构
struct netbuf *
buf; // API数据缓冲
conn = netconn_new(NETCONN_TCP); //
创建新的TCP连接结构
netconn_bind(conn, NULL,
7); //
该连接与端口7绑定
netconn_listen(conn); //
将结构置为侦听状态
newconn =
netconn_accept(conn); // 接收到一个新的连接
while(1)
{
buf =
netconn_recv(newconn); // 在新连接上接收到一个数据
netbuf_delete(buf); // 删除接收到的数据
netconn_write(newconn,data,sizeof(data), NETCONN_COPY); //
将字符串发送的客户端
}
}
服务器程序之所以要这样设计是为了测试的方便,因为手上恰好有个小程序可以用来测试这个服务器程序以及我们的LwIP协议栈运转是否正常。这个小程序是当年参加中兴编程大赛的时候写的,名字叫报文监视器。它能接收某个TCP连接上的数据并能按照用户要求对这些数据进行过滤,去除用户不关心的数据。大嘴东哥和寝室的鹏鹏。。O(∩_∩)O~看到这个程序就想到了你们,大功臣啊。。。测试结果如下:
过滤表达式编辑框内的内容是用户输入的过滤条件,当接收的数据串满足过滤条件时,该字符串不会在接收结果中显示出来。过滤条件是一系列的引号括起来的字符串,它们可以用or,and,not,括号等连接起来,组成很复杂的过滤条件。。不讲了。
首先,将过滤条件置为空,此时显示了从服务器接收到得所有数据“hello
word!”,如图下方所示。然后将过滤条件设置为“he”or
“ww”..即字符串中含有“he”或者“ww”字样的数据串将被滤除掉不以显示。。这正如接收结果中的前两行所示。OK…测试结果一切正常,我们的LwIP稳定的跑起来了!不过,这里还可以用其他的测试方法,更常用的方法是构建一个http服务器,然后用我们的浏览器来连接服务器,这些在LwIP移植手册中有了很多例程以及详细的说明,不罗嗦了。
可见,使用LwIP
API已经可以轻松完成所有TCP通信的相关任务了。除此之外,LwIP还用自身的API函数实现了BSD Socket
API函数。因为很多的软件编写是基于BSD套接字的,BSD套接字更简单易懂,使用广泛,可见实现Socket
API还是有必要的。但是LwIP说明文档中这样写道:
这一节提供使用LwIP API对BSD Socket
API的一个简单实现。这个实现只能作为一个参考,不能用于实际编程中,因为它并不完善,比如它没有容错机制等。同时,这个简单实现也不支持BSD
Socket API中的select()与poll()函数,因为LwIP
API没有任何函数可以用于这两个函数的实现。要实现这两个函数,BSD
socket实现需要直接与LwIP协议栈通讯,而不是使用其API。
所以这里不对BSD Socket API做详细讨论了,使用LwIP
API完全可以完成相关的工作,且编程工作也很简单。
到这里,我们已经从头到尾的将LwIP协议走完了一遍,从网络接口层到ARP层,再到IP层,然后到TCP层,最后到API层。通常实际应用中,TCP数据包也是按照这个次序依次被处理的。LwIP还有很多其他内容还没有讨论到,首先是UDP,接下来是PPP,SILP,DNS,IGMP,DHCP,SNMP,IPV6等等。这些都是在某些特殊的场合才会使用到的,不具有什么共性,所以这里先不涉及这些了。
全剧终。。。