Lwip—Sequential API编程接口
UDP的使用方法
static void udp_thread(void *arg)
{
OS_CPU_SR cpu_sr;
err_t err;
static struct netconn *udpconn;
static struct netbuf *recvbuf;
static struct netbuf *sentbuf;
struct ip_addr destipaddr;
u32 data_len = 0;
struct pbuf *q;
LWIP_UNUSED_ARG(arg);
udpconn = netconn_new(NETCONN_UDP); //创建一个UDP连接
udpconn->recv_timeout = 10; //接收超时为10ms
if(udpconn != NULL) //如果创建udp连接成功
{
err = netconn_bind(udpconn,IP_ADDR_ANY,UDP_DEMO_PORT); //服务器端绑定到熟知端口
IP4_ADDR(&destipaddr,lwipdev.remoteip[0],lwipdev.remoteip[1],lwipdev.remoteip[2],
lwipdev.remoteip[3]);
netconn_connect(udpconn,&destipaddr,UDP_DEMO_PORT); //连接到远端主机
//作为服务器时,以上两个函数可以不要
if(err == ERR_OK)//绑定成功
{
while(1)
{
if((udp_flag & LWIP_SEND_DATA) == LWIP_SEND_DATA) //有数据要发送
{
sentbuf = netbuf_new();
netbuf_alloc(sentbuf,strlen((char *)udp_demo_sendbuf));
memcpy(sentbuf->p->payload,(void*)udp_demo_sendbuf,
strlen((char*)udp_demo_sendbuf));
err = netconn_send(udpconn,sentbuf); //将netbuf中的数据发送出去
if(err != ERR_OK)
{
printf("发送失败\r\n");
netbuf_delete(sentbuf); //删除buf
}
udp_flag &= ~LWIP_SEND_DATA; //清除数据发送标志
netbuf_delete(sentbuf); //删除buf
}
netconn_recv(udpconn,&recvbuf); //接受数据
if(recvbuf != NULL) //接收到数据
{
OS_ENTER_CRITICAL(); //关中断
memset(udp_demo_recvbuf,0,UDP_DEMO_RX_BUFSIZE); //数据接受缓冲区清零
for(q=recvbuf->p;q!=NULL;q=q->next) //遍历完整个pbuf链表
{
//判断要拷贝的UDP_RX_BUFFSIZE中的数据是否大于UDP_RX_BUFFSIZE的剩余空间,
//如果大于的话就只拷贝UDP_RX_BUFFSIZE剩余的长度,否则的话就拷贝所有的数据
if(q->len > (UDP_DEMO_RX_BUFSIZE-data_len)) memcpy(udp_demo_recvbuf+data_len,q->payload,(UDP_DEMO_RX_BUFSIZE-data_len));
else memcpy(udp_demo_recvbuf+data_len,q->payload,q->len);
data_len += q->len;
if(data_len > UDP_RX_BUFSIZE) break;
}
OS_EXIT_CRITICAL();
data_len=0; //复制完成后data_len要清零
printf("%s\r\n",udp_demo_recvbuf); //打印接收的数据
netbuf_delete(recvbuf); //删除buf
}else OSTimeDlyHMSM(0,0,0,5); //延时5ms
}
}else printf("UDP绑定失败\r\n);
}else printf("UDP连接创建失败\r\n);
}
//创建UDP线程
//返回值:0 成功
// 其他 失败
INT8U udp_demo_init(void)
{
INT8U res;
OS_CPU_SR cpu_sr;
OS_ENTER_CRITICAL();
res = OSTaskCreate(udp_thread,(void*)0,(OS_STK*)&UDP_TASK_STK[UDP_STK_SIZE-1],UDP_PRIO);
OS_EXIT_CRITICAL();
return res;
}
使用UDP的话,首先应该创建一个UDP线程,然后要遵循udp应用的一般模板:
//这是UDP基本的发送模板
struct netconn *conn;
struct netbuf *buf;
struct ip_addr *addr;
char *sendbuf = "123";
conn = netconn_new(NETCONN_UDP); //新建一个UDP类型的连接结构
IP4_ADDR(addr,192.168.1.78); //构造目的IP地址
netconn_connect(conn,&addr,7000); //连接目的端
buf = netbuf_new(); //创建一个新的netbuf结构
netbuf_alloc(buf,strlen((char *)sendbuf)); //分配数据空间
netconn_send(conn,buf); //发送新数据
netconn_delete(conn); //删除连接结构
netconn_delete(buf); //删除netbuf结构
//这是UDP基本的接收模板
struct netconn *conn;
struct netbuf *buf;
struct ip_addr *addr;
unsigned short port;
conn = netconn_new(NETCONN_UDP); //新建UDP连接
conn->recv_timeout = 10; //接收超时为10ms
netconn_bind(conn,NULL,7); //服务器端绑定到熟知端口7
while(1)
{
conn = netconn_recv(conn,&buf); //在连接上接收数据
addr = netbuf_fromaddr(buf); //获得数据的源IP地址
port = netbuf_fromport(buf); //获得数据的源端口
do_something(&buf->p)
netbuf_delete(buf);
}
第一步:创建一个指向netconn的全局指针变量;
第二步:使用netconn_new函数创建一个UDP类型的连接,返回值指向第一步创建的全局指针变量,expl:conn = netconn_new(NETCONN_UDP);
第三步:作为发送方,conn连接接口要连接到远端,netconn_connect(conn,IP地址,端口号); 而作为接收要绑定一个固定端口,netconn_bind(conn,NULL,7);
第四步:发送netconn_send(conn,buf);或者接收netconn_recv(conn,&buf);。
第五步:删除netbuf结构,如果只是发送的话,还要删除netconn结构。
TCP的使用方法
使用TCP的话,首先应该创建一个TCP线程,然后要遵循TCP应用的一般模板:
作为服务器的模板:
static void tcp_server_thread(void *arg)
{
struct netconn *conn,*newconn;
err_t err;
conn = netconn_new(NETCONN_TCP); //新建一个TCP连接
netconn_bind(conn,NULL,7); //绑定服务器熟知端口7
netconn_listen(conn); //进入侦听状态,接收客户端连接
while(1)
{
err = netconn_accept(conn,&newconn);//等待一个新连接
if(err == ERR_OK) //新连接建立成功
{
struct netbuf *buf;
void *data; u16 len;
//在新连接上等待,循环接收数据包
while((err = netconn_recv(newconn,&buf)) == ERR_OK){
do
{
netbuf_data(buf,&data,&len);
err = netconn_write(newconn,data,len,NETCONN_COPY);//回显
}while(netbuf(buf)>=0);
netbuf_delete(buf);//删除数据包
}
//接收数据包失败,或者到空包,则表示对端已经断开连接
netconn_close(newconn); //断开本地连接
netconn_delete(newconn); //删除连接结构
}
}
}
//创建TCP线程
//返回值: 0 成功
// 其他 失败
INT8U tcp_server_init(void)
{
INT8U res;
OS_CPU_SR cpu_sr;
OS_ENTER_CRITICAL();
res = OSTaskCreate(tcp_server_thread,(void*)0,(OS_STK*)&TCPSERVER_TASK_STK[TCPSERVER_STK_SIZE-1],TCPSERVER_PRIO);
OS_EXIT_CRITICAL();
return res;
}
第一步:使用netconn_new函数创建一个TCP类型的连接,返回值指向创建的全局指针变量,expl:conn = netconn_new(NETCONN_TCP);
第二步:作为服务端要绑定一个固定端口,netconn_bind(conn,NULL,7);
第三步:使服务端处于侦听状态netconn_listen(conn);,等待一个新连接netconn_accept(conn,&newconn);
第四步:连接成功后,再新连接上等待循环接收数据包netconn_recv(newconn,&buf);
第五步:处理数据;
第六步:删除netbuf结构,如果只是发送的话,还要删除netconn结构。
作为客户端的模板:
static void tcp_client_thread(void *arg)
{
struct netconn *tcp_clientconn;
err_t err;
while(1)
{
tcp_clientconn = netconn_new(NETCONN_TCP);//创建TCP连接
err = netconn_connect(tcp_clientconn,&server_ipaddr,server_port);//连接服务器
if(err != ERR_OK) netconn_delete(tcp_clientconn);//返回值不等于ERR_OK,删除连接
else if(err == ERR_OK)//处理新连接的数据
{
while(1)
{
if((tcp_client_flag & LWIP_SEND_DATA) == LWIP_SEND_DATA) //有数据要发送
{
err = netconn_write(tcp_clientconn,tcp_client_sendbuf,
strlen((char*)tcp_client_sendbuf),NETCONN_COPY);
tcp_client_flag &= ~LWIP_SEND_DATA;
}
}
}
}
}
//创建TCP客户端线程
//返回值:0 成功
// 其他 失败
INT8U tcp_client_init(void)
{
INT8U res;
OS_CPU_SR cpu_sr;
OS_ENTER_CRITICAL();
res = OSTaskCreate(tcp_client_thread,(void*)0,(OS_STK*)&TCPCLIENT_TASK_STK[TCPCLIENT_STK_SIZE-1],TCPCLIENT_PRIO);
OS_EXIT_CRITICAL();
return res;
}
第一步:使用netconn_new函数创建一个TCP类型的连接,返回值指向创建的全局指针变量,expl:conn = netconn_new(NETCONN_TCP);
第三步:作为客户端,conn连接接口要连接到远端,netconn_connect(conn,IP地址,端口号);
第四步:客户端这时可以用netconn_write写数据了,到时候,有内核发送出去。
第五步:删除netbuf结构,如果只是发送的话,还要删除netconn结构。
协议栈api
协议栈API的实现由两部分组成:一部分作为用户编程接口函数提供给用户,这些函数在用户进程中执行;另一部分驻留在协议栈内核进程中,这两部分通过进程通信机制(IPC)实现通信和同步,共同为应用程序提供服务。被使用到的进程通信机制包括以下三种:
-
邮箱,例如内核邮箱mbox、连接上接收数据的邮箱recvmbox;
-
信号量,例如op_completed,用于两部分API的同步;
-
共享内存,例如内核消息结构tcpip_msg、API消息内容api_msg等;
两部分API之间的关系如图。API设计的核心在于让用户进程负责尽可能多的工作,而协议栈进程值负责简单的通信工作。
内核进程
在操作系统模拟曾的支持下,LwIP内核作为操作系统的一个任务运行,在协议栈初始化函数tcp_init中,内核进程被创建。
static void
tcpip_thread(void *arg)
{
struct tcpip_msg *msg; //消息结构消息
LWIP_UNUSED_ARG(arg);
if (tcpip_init_done != NULL) { //若用户注册了自定义的初始化函数,则调用
tcpip_init_done(tcpip_init_done_arg);
}
while (1) { /* MAIN Loop */
sys_timeouts_mbox_fetch(&mbox, (void **)&msg); //等待一个消息
switch (msg->type) { //根据消息的不同类型做处理
case TCPIP_MSG_API: //API调用
LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: API message %p\n", (void *)msg));
msg->msg.apimsg->function(&(msg->msg.apimsg->msg));//执行API函数
break;
case TCPIP_MSG_INPKT: //底层数据包输入
LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: PACKET %p\n", (void *)msg));
if (msg->msg.inp.netif->flags & (NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET)) {//若支持ARP,则
ethernet_input(msg->msg.inp.p, msg->msg.inp.netif); //交给ARP层处理
} else //若接口不支持ARP(环回),则数据包直接交给IP层处理
{
ip_input(msg->msg.inp.p, msg->msg.inp.netif);
}
memp_free(MEMP_TCPIP_MSG_INPKT, msg);
break;
case TCPIP_MSG_TIMEOUT: //上层注册一个定时时间
LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: TIMEOUT %p\n", (void *)msg));
sys_timeout(msg->msg.tmo.msecs, msg->msg.tmo.h, msg->msg.tmo.arg);
memp_free(MEMP_TCPIP_MSG_API, msg);
break;
case TCPIP_MSG_UNTIMEOUT: //上层删除一个定时事件
LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: UNTIMEOUT %p\n", (void *)msg));
sys_untimeout(msg->msg.tmo.h, msg->msg.tmo.arg);
memp_free(MEMP_TCPIP_MSG_API, msg);
break;
case TCPIP_MSG_CALLBACK: //上层通过回调方式执行一个函数
LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: CALLBACK %p\n", (void *)msg));
msg->msg.cb.function(msg->msg.cb.ctx);
memp_free(MEMP_TCPIP_MSG_API, msg);
break;
case TCPIP_MSG_CALLBACK_STATIC:
LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: CALLBACK_STATIC %p\n", (void *)msg));
msg->msg.cb.function(msg->msg.cb.ctx);
break;
default:
LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: invalid message: %d\n", msg->type));
LWIP_ASSERT("tcpip_thread: invalid message", 0);
break;
}
}
}
void
tcpip_init(tcpip_init_done_fn initfunc, void *arg)
{
lwip_init(); //初始化内核
tcpip_init_done = initfunc; //注册用户自定义函数
tcpip_init_done_arg = arg; //函数参数
if(sys_mbox_new(&mbox, TCPIP_MBOX_SIZE) != ERR_OK) {//创建内核邮箱
LWIP_ASSERT("failed to create tcpip_thread mbox", 0);
}
#if LWIP_TCPIP_CORE_LOCKING
if(sys_mutex_new(&lock_tcpip_core) != ERR_OK) {
LWIP_ASSERT("failed to create lock_tcpip_core", 0);
}
#endif /* LWIP_TCPIP_CORE_LOCKING */
//创建内核进程
sys_thread_new(TCPIP_THREAD_NAME, tcpip_thread, NULL, TCPIP_THREAD_STACKSIZE, TCPIP_THREAD_PRIO);
}
全局邮箱mbox在协议栈初始化时建立,用于内核进程tcpip_thread接收消息。内核进程通过共享内存的方式与协议栈的其他各个模块进行通信,它从邮箱中获得的时一个纸箱消息结构的指针。内核消息封装在tcpip_msg结构中,tcpip_thread使用从邮箱中获得的指针到对应的内存地址处读取消息的内容,并根据消息的内容作出各种处理。上层API函数的实现及底层数据包的处理都是基于这种消息机制的。
内核进程会一直阻塞在sys_timeouts_mbox_fetch(&mbox, (void **)&msg);上 除了等待上层发送的消息,它还处理一些定时任务,比如ARP,TCP定时器。系统消息是通过消息结构tcpip_msg来描述的,内核进程识别消息的类型,并调用不同的函数处理这些消息。
struct tcpip_msg {
enum tcpip_msg_type type; //消息类型字段 TCPIP_MSG_API,TCPIP_MSG_INPKT,TCPIP_MSG_TIMEOUT,
//TCPIP_MSG_UNTIMEOUT,TCPIP_MSG_CALLBACK,TCPIP_MSG_CALLBACK_STATIC
sys_sem_t *sem; //信号量指针,暂未用到
union {
#if LWIP_NETCONN
struct api_msg *apimsg; //指向api_msg结构,记录API消息的具体内容
#endif /* LWIP_NETCONN */
#if LWIP_NETIF_API
struct netifapi_msg *netifapimsg;
#endif /* LWIP_NETIF_API */
struct { //底层数据包消息的具体内容
struct pbuf *p; //指向收到的数据
struct netif *netif; //收到数据包的网络接口
} inp;
struct { //回调执行一个函数消息的具体内容
tcpip_callback_fn function; //函数指针
void *ctx; //函数参数
} cb;
#if LWIP_TCPIP_TIMEOUT
struct { //注册、撤销一个定时时间消息的具体内容
u32_t msecs; //定时时间
sys_timeout_handler h; //定时函数
void *arg; //函数参数
} tmo;
#endif /* LWIP_TCPIP_TIMEOUT */
} msg;
};
比如数据包消息:
err_t
tcpip_input(struct pbuf *p, struct netif *inp)
{
struct tcpip_msg *msg;
if (!sys_mbox_valid(&mbox)) {//系统邮箱有效的情况下
return ERR_VAL;
}
msg = (struct tcpip_msg *)memp_malloc(MEMP_TCPIP_MSG_INPKT);//申请新的消息结构空间
if (msg == NULL) {
return ERR_MEM;
}
msg->type = TCPIP_MSG_INPKT;//数据包消息
msg->msg.inp.p = p; //指向msg中共用体inp的pbuf字段
msg->msg.inp.netif = inp; //指向msg中共用体inp的netif字段
if (sys_mbox_trypost(&mbox, msg) != ERR_OK) { //向系统邮箱投递消息
memp_free(MEMP_TCPIP_MSG_INPKT, msg);
return ERR_MEM;
}
return ERR_OK;
#endif /* LWIP_TCPIP_CORE_LOCKING_INPUT */
}
协议栈API处理数据包消息流程如图:
(1)~(3)在中断处理程序或者单独地数据包查询接收线程中完成,(4)(5)则是由内核进程完成的。
API消息:
err_t
tcpip_apimsg(struct api_msg *apimsg)
{
struct tcpip_msg msg;
if (sys_mbox_valid(&mbox)) {
msg.type = TCPIP_MSG_API;//类型API消息
msg.msg.apimsg = apimsg; //指向API具体消息
sys_mbox_post(&mbox, &msg);//投递邮箱
sys_arch_sem_wait(&apimsg->msg.conn->op_completed, 0);//等待处理程序释放信号
return apimsg->msg.err;
}
return ERR_VAL;
}
API消息重点在apimsg
struct api_msg {
/** function to execute in tcpip_thread context */
void (* function)(struct api_msg_msg *msg);
/** arguments for this function */
struct api_msg_msg msg;
};
struct api_msg_msg {
struct netconn *conn; //与消息相关的当前连接,该字段对任何调用都是必须的
err_t err;
union {
struct netbuf *b; //执行do_send时需要的参数,待发送数据
struct { //执行do_newconn时需要的参数,连接类型
u8_t proto;
} n;
struct { //执行do_bind和do_connect时需要的参数
ip_addr_t *ipaddr;//IP地址
u16_t port; //端口号
} bc;
struct { //执行do_getaddr时需要的参数
ip_addr_t *ipaddr;
u16_t *port;
u8_t local;
} ad;
struct { //执行do_write时需要的参数
const void *dataptr;
size_t len;
u8_t apiflags;
#if LWIP_SO_SNDTIMEO
u32_t time_started;
#endif /* LWIP_SO_SNDTIMEO */
} w;
struct { //执行do_recv时需要的参数
u32_t len;
} r;
struct { /** used for do_close (/shutdown) */
u8_t shut;
} sd;
#if LWIP_IGMP
struct { /** used for do_join_leave_group */
ip_addr_t *multiaddr;
ip_addr_t *netif_addr;
enum netconn_igmp join_or_leave;
} jl;
#endif /* LWIP_IGMP */
#if TCP_LISTEN_BACKLOG
struct {
u8_t backlog;
} lb;
#endif /* TCP_LISTEN_BACKLOG */
} msg;
};
API函数的实现,以netconn_bind为例:
前边说过协议栈API分两部分,一部分在用户进程中,一部分在内核中。用户使用的在在文件api_lib.c中,形式如netconn_xxx;而内核进程中的API函数在tcpip_msg中,形如do_xxx。图中,用户程序调用netconn_bind绑定一个连接,则函数函数实现时,通过向内核进程发送一个TCPIP_MSG-API类型的消息,告诉内核进程执行do_bind函数;消息发送后,函数阻塞在信号量上,等待内核处理该消息;内核在处理消息时,会根据消息内容调用do_bind,而do_bind会根据连接的类型调用内核函数udp_bind、tcp_bind、和raw_bind;当do_bind执行完后,它会释放信号量,这使被阻塞的netconn_bind得以继续执行。
协议栈接口
数据缓冲netbuf
netbuf是API描述数据的一种方式,应用程序可以使用该结构管理发送数据,接收数据的缓冲区。
struct netbuf {
struct pbuf *p, *ptr;
ip_addr_t addr;
u16_t port;
};
字段p指向pbuf链表,字段ptr也指向该netbuf的pbuf链表,但p一直指向pbuf链表中的第一个pbuf结构,而ptr可能指向链表中的其他位置,与该指针调整密切相关的函数是netbuf_next和netbuf_first.还有两个字段addr和port分别记录数据发送方的IP地址和端口号。
操作数据缓冲
无论是UDP连接还是TCP连接,当协议栈接收到数据包后,会将数据封装在一个netbuf中,并递交给应用程序。在数据发送时,不同的类型的连接将导致不同的数据处理方式:对于TCP连接,用户只需要提供待发送数据的起始地址和长度,内核会根据实际情况将数据封装在合适大小的数据包中,并放入发送队列;对于UDP来说,用户需要自行将数据封装在netbuf结构中,当发送函数被调用时,内核直接将该数据包中的数据发送出去。
函数名 | 函数功能 |
---|---|
netbuf_new | 申请一个新的netbuf空间,但不分配任何数据空间,函数返回一个netbuf结构。 |
netbuf_delete | 释放一个netbuf结构空间。 |
netbuf_alloc | 为netbuf结构分配size大小的数据空间,返回数据空间的起始地址(pbuf的payload指向的地址)。 |
netbuf_free | 释放netbuf结构指向的数据pbuf,若pbuf指针为空,则没有任何空间被释放。 |
netbuf_ref | 与netbuf_alloc相似,区别在于它不会分配具体的数据区域,只会分配一个pbuf首部结构,并将pbuf的payload指针指向数据地址dataptr。 |
netbuf_chain | 将tail中的pbuf连接到head中的pbuf之后,调用此函数,tail结构会被删除,用户不能在应用这个netbuf。 |
netbuf_data | 取出ptr记录的pbuf中的数据。将netbuf结构中ptr指针记录的pbuf数据填入dataptr,同时将该pbuf中的数据长度填入到len中。 |
netbuf_next | 将netbuf结构的ptr指针指向pbuf链表中的下一个pbuf结构。 |
netbuf_first | 将netbuf结构的ptr指针指向第一个pbuf。 |
连接结构netconn
netconn为用户提供一个统一的编程接口,不管连接的类型是UDP还是TCP,系统API函数对各种连接类型的操作函数进行了统一的封装。用户程序直接忽略连接类型的差异,使用统一的结构和编程函数。
//枚举类型,用于描述连接类型
enum netconn_type {
NETCONN_INVALID = 0, //无效类型
NETCONN_TCP = 0x10, //TCP
NETCONN_UDP = 0x20, //UDP
NETCONN_UDPLITE = 0x21, //UDPLite
NETCONN_UDPNOCHKSUM= 0x22, //无校验UDP
NETCONN_RAW = 0x40 //原始连接
};
//枚举类型,用于描述连接状态,主要在TCP连接中使用
enum netconn_state {
NETCONN_NONE, //不处于任何状态
NETCONN_WRITE, //正在发送数据
NETCONN_LISTEN, //侦听状态
NETCONN_CONNECT, //连接状态
NETCONN_CLOSE //关闭状态
};
//定义函数指针类型netconn_callback
typedef void (* netconn_callback)(struct netconn *, enum netconn_evt, u16_t len);
//连接结构
struct netconn {
enum netconn_type type; //描述了当前连接的类型 TCP、UDP或者RAW
enum netconn_state state; //当前连接的状态
union { //共用体 记录与连接相关的内核控制块(UDP控制块、TCP控制块)
struct ip_pcb *ip; //IP控制块
struct tcp_pcb *tcp; //tcp控制块
struct udp_pcb *udp; //udp控制块
struct raw_pcb *raw; //RAW控制块
} pcb;
err_t last_err; /** the last error this netconn had */
sys_sem_t op_completed; //信号量,用于两部分API同步
sys_mbox_t recvmbox; //接受数据的邮箱,也可看做时数据缓冲队列
#if LWIP_TCP
sys_mbox_t acceptmbox;//用于TCP服务端,连接请求的缓冲队列
#endif /* LWIP_TCP */
#if LWIP_SOCKET
int socket; //socket描述符,实现socket API时使用到
#endif /* LWIP_SOCKET */
#if LWIP_SO_RCVBUF
int recv_bufsize; //数据邮箱recvmbox上可缓存的最大数据长度
s16_t recv_avail; //数据邮箱recvmbox中已缓存的数据长度
#endif /* LWIP_SO_RCVBUF */
u8_t flags; //netconn的更多状态标识,比如在发送缓冲不够时,发送是够阻塞;是否自动更新内核接收窗口等
#if LWIP_TCP
size_t write_offset; //当调用netconn_write发送数据但缓存不够时,数据会被暂时封装在write_msg中等待下一次发送,write_offset记录下一次发送时的索引
struct api_msg_msg *current_msg;
#endif /* LWIP_TCP */
netconn_callback callback; //连接相关回调函数,实现Socket API时使用到
};
内核回调接口
协议栈API时基于Raw/Callback API来实现的,它与内核交互的方式也只能通过回调。这些回调函数被默认的注册到相关控制块的相关字段。其中UDP有1个函数,TCP有6个。
-
recv_udp UDP内核接收到数据后,将回调时的参数(数据包pbuf)组装成一个上层的数据包格式netbuf,并把这个netbuf投递到连接netconn的邮箱中。
-
accept_function TCP服务器接受一个新的连接请求并完成三次握手后,此函数被回调,使用新连接的TCP控制块建立一个新连接结构netconn,并将该结构投递到服务器连接的acceptmbox邮箱中。
-
sent_tcp 当本地待确认数据被对方TCP报文中的ACK确认后,TCP控制块中的sen_tcpt函数将被回调执行。它将调用do_writemore检查当前连接的netconn是否仍然有数据需要发送,若是则发送数据,并在所有数据发送完成后,释放netconn结构中的信号op_completed。
-
recv_tcp 当TCP内核接收到关于某个TCP控制块的数据后,在控制块上注册的用户函数recv_tcp会被调用。它将数据包pbuf递交到连接结构netconn的recvmbox邮箱上。
-
do_connected 作为TCP客户端,在向服务器发起连接请求,并完成三次握手后,TCP控制块中的do_connected函数将被回调执行。它的功能是释放信号量op_conpleted。
-
poll_tcp TCP慢定时器周期性的调用poll_tcp(4个慢时钟时长,2S),检查连接netconn是否处于数据发送状态,若是,则调用函数do_writemore将连接上的剩余数据发送出去。
-
err_tcp 当某个连接上出现错误时,函数会被回调执行。它会向netconn结构的两个邮箱中投递一条空消息,告诉上层,当前连接发送错误,同时函数还会判断当前是否有API函数阻塞在连接上,如果有,则释放一个信号量op_completed,让API函数解除阻塞。
API函数
函数名 | 函数功能 |
---|---|
netconn_new | 为新连接申请一个连接结构netconn空间,并需要指明新连接的类型,常用值为NETCONN_TCP,NETCONN_UDP。 |
netconn_delete | 删除一个连接结构netconn。 |
netconn_getaddr | 获得一个连接结构netconn的源IP地址和源端口号或者目的IP地址和目的端口号。 |
netconn_bind | 将一二连接结构与本地IP地址addr(IP_ADDR_ANY代表任何一个网络接口的IP地址)和端口号port进行绑定。 |
netconn_connet | 连接服务器,它将连接机构与目的IP地址和目的端口号port进行绑定。 |
netconn_disconnect | 与服务器断开连接(只作用在UDP)。 |
netconn_listen | 将连接结构netconn置为侦听状态。 |
netconn_accept | 从acceptmbox邮箱中获得一个新建立的连接。 |
netconn_recv | 从连接的recvmbox邮箱中接收数据包。 |
netconn_send | 用于已经建立的UDP连接上发送数据。 |
netconn_write | 在稳定的TCP连接上发送数据,参数dataptr和size分别指出了待发送数据的起始地址和长度,该函数并不要求用户将数据封装在netbuf中,对数据长度没有限制。 |
netconn_close | 关闭一个TCP连接。 |
以上函数的执行过程一般和前面介绍的netconn_bind函数过程差不多。