- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
终于到接口层了.
原文:李柱明博客: https://www.cnblogs.com/lizhuming/p/17442931.html 。
。
前面我们已经学完了,都知道raw接口了,其实也可以直接用,就是麻烦点.
这里NETCONN就是封装了raw接口,让用户使用更加简单.
socket接口是封装NETconn接口的,让用户使用更加标准,方便应用程序移植.
。
NETCONN的接口框架:
解耦:编写回调函数xxx_tcp()、xxx_udp(),注册到协议栈里面。协议栈通过回调函数告知接口层,当前PCB的状态信息。接口层根据当前PCB的状态信息做相应处理即可.
。
文件:
api_msg.c
:构建api msg,被netconn调用,发送到内核锁或tcpip内核线程执行指定的回调函数。 api_lib.c
:netconn API。 sockets.c
:socket套接字接口层,封装netconn接口。供用户使用。 。
在raw/callback API编程时,用户编程的方法就是向内核注册各种自定义的回调函数, 回调函数是与内核实现交互的唯一方式 .
协议栈API NETCONN是基于raw/callback API实现的.
。
内核通过调用注册到TCP/UDP内核的回调函数,把接收到的数据或可发送数据的事件发送回netconn对应的邮箱中,上层检查这些邮箱即可和内核协议栈交互.
netbuf是应用层描述待发送数据和已接收数据的基本结构。当然,最基本的粒度数据结构还是pbuf.
应用层接收数据:
netbuf
中,并递交给应用层。 应用层发送数据:
netbuf
结构中。 。
netbuf数据结构:
/** "Network buffer" - contains data and addressing info */
struct netbuf {
struct pbuf *p, *ptr; /* 包缓冲区。p:pbuf链。ptr:pbuf链中当前pbuf游标。 */
ip_addr_t addr; /* 发送方IP */
u16_t port; /* 发送方端口 */
#if LWIP_NETBUF_RECVINFO || LWIP_CHECKSUM_ON_COPY
u8_t flags; /* 标志位 */
u16_t toport_chksum; /* 目的端口号。用于checksum */
#if LWIP_NETBUF_RECVINFO
ip_addr_t toaddr; /* 目的地址 */
#endif /* LWIP_NETBUF_RECVINFO */
#endif /* LWIP_NETBUF_RECVINFO || LWIP_CHECKSUM_ON_COPY */
};
。
代表一个连接,TCP或UDP等.
相关文件:api.h 。
分析完UDP和TCP协议实现后,会分析他们的原生接口udp_xxx()和tcp_xxx()都是互相独立的.
而连接结构netconn就是为了统一这些接口.
。
netconn控制块:
/** A netconn descriptor */
struct netconn {
/** type of the netconn (TCP, UDP or RAW) */
enum netconn_type type; /* 连接类型 */
enum netconn_state state; /* netconn当前状态。即是当前netconn被哪些netconn API占用 */
union {
struct ip_pcb *ip; /* IP控制块 */
struct tcp_pcb *tcp; /* TCP控制块 */
struct udp_pcb *udp; /* UDP控制块 */
struct raw_pcb *raw; /* TCP控制块 */
} pcb; /* 内核中与连接相关的控制块指针 */
/* 此netconn的最新未报告的异步错误 */
err_t pending_err;
#if !LWIP_NETCONN_SEM_PER_THREAD /* 只能每个netconn数据结构占用一个信号量 */
/* 信号量。是对一个API完成两部分线程的同步。如用户调用API,API调用内核API,并等待内核API完成后通过该信号量通知当前API。 */
sys_sem_t op_completed;
#endif
/* 接收数据的邮箱。数据缓冲队列。 */
sys_mbox_t recvmbox;
#if LWIP_TCP
/* 用于TCP服务器端。连接请求的缓冲队列。 */
sys_mbox_t acceptmbox;
#endif /* LWIP_TCP */
#if LWIP_NETCONN_FULLDUPLEX /* 全功率 */
/* mbox的读阻塞线程数。当线程在waiting时closing,需要解除所有线程的阻塞。 */
int mbox_threads_waiting;
#endif
union {
int socket; /* socket */
void *ptr; /* 指针 */
} callback_arg; /* 回调参数 */
#if LWIP_SO_SNDTIMEO /* 发送超时 */
/* 等待发送数据超时值,单位ms。 */
s32_t send_timeout;
#endif /* LWIP_SO_RCVTIMEO */
#if LWIP_SO_RCVTIMEO /* 接收超时 */
/* 等待接收新数据的超时时间,单位ms。 */
u32_t recv_timeout;
#endif /* LWIP_SO_RCVTIMEO */
#if LWIP_SO_RCVBUF /* 接收缓冲区 */
/* 应用层的接收缓冲区size。限制recvmbox上所有数据的size。 */
int recv_bufsize;
/* recvmbox 当前接收到的数据size,用于FIONREAD。 */
int recv_avail;
#endif /* LWIP_SO_RCVBUF */
#if LWIP_SO_LINGER /* SO_LINGER选项 */
/* < 0: 关闭该功能。
= 0: 立即关闭。发送缓冲区残留有数据时,RST给对端。
> 0: 超时值。单位:秒。超时前尽量把发送缓冲区中的数据发送出去。 */
s16_t linger;
#endif /* LWIP_SO_LINGER */
/* 包含更多的netconn-internal状态。参考NETCONN_FLAG_x宏 */
u8_t flags;
#if LWIP_TCP
/* 当调用netconn_write()函数发送的数据不适合发送到缓冲区时,
数据会暂时存储在current_msg中,等待数据合适的时候进行发送。 */
struct api_msg *current_msg;
#endif /* LWIP_TCP */
/* netconn相关的回调函数。socket API时使用。 */
netconn_callback callback;
};
部分变量描述:
type
:连接类型:TCP、UDP、RAW。 state
:当前连接状态。 pcb
:协议栈内核连接控制块:TCP控制块、UDP控制块等等。 err
:记录当前连接上函数调用的执行结果。 op_completed
:是上下两部分API实现同步的重要字段, netconn_xxx()
函数在投递完消息后,便会阻塞在连接的这个信号量上,当内核的 do_xxx()
执行完成后便释放信号量。 recvmbox
:该连接的数据接收邮箱,也是缓冲队列。内核接收到属于该连接的数据包(封装在netbuf中)投递到该邮箱。应用程序调用数据接收函数,就是从该队列中读取数据。 recv_avail
:记录当前接收邮箱中已经缓冲好的数据总长度。 acceptmbox
:该连接作为TCP服务器时使用到,内核会把所有新建立好的连接结构 netconn
投递到该邮箱,服务器程序调用 netconn_accept()
函数便会得到一个新的连接结构。 mbox_threads_waiting
:表示读阻塞在当前连接的应用程序数量,在关闭连接时,需要往 recvmbox
邮箱发送 mbox_threads_waiting
个邮件来解除这些应用层的阻塞。 。
/** @ingroup netconn_common
* 协议族和netconn类型。
*
*/
enum netconn_type {
NETCONN_INVALID = 0, /* 无效类型 */
/** TCP IPv4 */
NETCONN_TCP = 0x10,
#if LWIP_IPV6
/** TCP IPv6 */
NETCONN_TCP_IPV6 = NETCONN_TCP | NETCONN_TYPE_IPV6 /* 0x18 */,
#endif /* LWIP_IPV6 */
/** UDP IPv4 */
NETCONN_UDP = 0x20,
/** UDP IPv4 lite */
NETCONN_UDPLITE = 0x21,
/** UDP IPv4 no checksum */
NETCONN_UDPNOCHKSUM = 0x22,
#if LWIP_IPV6
/** UDP IPv6 (dual-stack by default, unless you call @ref netconn_set_ipv6only) */
NETCONN_UDP_IPV6 = NETCONN_UDP | NETCONN_TYPE_IPV6 /* 0x28 */,
/** UDP IPv6 lite (dual-stack by default, unless you call @ref netconn_set_ipv6only) */
NETCONN_UDPLITE_IPV6 = NETCONN_UDPLITE | NETCONN_TYPE_IPV6 /* 0x29 */,
/** UDP IPv6 no checksum (dual-stack by default, unless you call @ref netconn_set_ipv6only) */
NETCONN_UDPNOCHKSUM_IPV6 = NETCONN_UDPNOCHKSUM | NETCONN_TYPE_IPV6 /* 0x2a */,
#endif /* LWIP_IPV6 */
/** Raw connection IPv4 */
/* RAW ipv4 连接 */
NETCONN_RAW = 0x40
#if LWIP_IPV6
/** Raw connection IPv6 (dual-stack by default, unless you call @ref netconn_set_ipv6only) */
, NETCONN_RAW_IPV6 = NETCONN_RAW | NETCONN_TYPE_IPV6 /* 0x48 */
#endif /* LWIP_IPV6 */
};
。
/* 当前netconn接口数据结构所处的状态。
如当前netconn被netconn_write()接口调用,就处于WRITE状态。
也可以理解为当前netconn被哪些netconn API占用 */
enum netconn_state {
NETCONN_NONE, /* 空闲状态 */
NETCONN_WRITE, /* 正在发送数据 */
NETCONN_LISTEN, /* 侦听状态 */
NETCONN_CONNECT, /* 连接状态 */
NETCONN_CLOSE /* 关闭状态 */
};
。
上层收:按次数统计。NETCONN中有多少个可被上层接收.
上层发:按是否有无统计。NETCONN中是否可写.
/*
* netconn_x()API 通知更上层(如socket)的事件。
*
* 事件说明:
* 在netconn实现中,有三种方法来阻塞客户端:
* - accept mbox:netconn_accept()函数中的sys_arch_mbox_fetch()
* - receive mbox:netconn_recv_data()函数中的sys_arch_mbox_fetch()
* - send queue if full:lwip_netconn_do_write()函数中的sys_arch_sem_wait()
*
* 这些事件都是给这些mboxes/semaphores标记状态的事件。
* 对于非阻塞式的连接,我们可以通过这些事件,提前知道调用netconn API是否会阻塞。
*
* NETCONN_EVT_RCVPLUS: 加。mboxes/semaphores 对象,可安全调用相关netconn API不会被阻塞的次数+1。
* 如在sockets中是按次计数:如accept mbox连续收到三个NETCONN_EVT_RCVPLUS事件,
* 则可以连续三次调用netconn_accept()不会被阻塞。receive mbox也一样。
*
* NETCONN_EVT_RCVMINUS: 减。mboxes/semaphores 对象,可安全调用相关netconn API不会被阻塞的次数-1。
* 一般在调用对应函数成功后,统计一次。
*
* 而对于TX,没有次数统计,只是一个标志。
*
* NETCONN_EVT_SENDPLUS: 表示调用netconn_send()发送数据不会阻塞。
* 一般发生在发送缓冲区中的数据被ACK了,缓冲区空闲空间增加时会回调该事件到上层。
*
* NETCONN_EVT_SENDMINUS: 表示调用netconn_send()会阻塞。
* 一般发生在协议栈内部PCB不可发送数据时会通过该事件通知上层,此时调用netconn_send()会阻塞,如发送缓冲区不足,内存不足等等。
* 触发该事件后,内部PCB会在pcb->poll()函数会检查PCB是否可发送数据,如果可发,就会触发NETCONN_EVT_SENDPLUS事件通知上层。
*
*/
enum netconn_evt {
NETCONN_EVT_RCVPLUS, /* 收到数据。可安全调用API不会被阻塞次数+1 */
NETCONN_EVT_RCVMINUS, /* 可安全调用API不会被阻塞次数-1 */
NETCONN_EVT_SENDPLUS, /* PCB可发送数据事件 */
NETCONN_EVT_SENDMINUS,/* PCB不可发送事件 */
NETCONN_EVT_ERROR /* 错误事件 */
};
。
。
前提实现可以自行看源码.
netbuf 操作接口:
/* Network buffer functions: */
struct netbuf * netbuf_new (void);
void netbuf_delete (struct netbuf *buf);
void * netbuf_alloc (struct netbuf *buf, u16_t size);
void netbuf_free (struct netbuf *buf);
err_t netbuf_ref (struct netbuf *buf,
const void *dataptr, u16_t size);
void netbuf_chain (struct netbuf *head, struct netbuf *tail);
err_t netbuf_data (struct netbuf *buf,
void **dataptr, u16_t *len);
s8_t netbuf_next (struct netbuf *buf);
void netbuf_first (struct netbuf *buf);
。
。
在学完TCP、UDP内核实现后,就知道我们需要往这些内核里注册回调函数,用于内核和上层交互。如tcp的 tcp_recv() 就是往内核注册接收回调函数.
所以在实现NETCONN接口时,需要编写这些回调函数,并注册到内核中.
。
TCP: setup_tcp() :
/**
* 注册netconn tcp基础接口相关的回调到TCP层
*
*/
static void
setup_tcp(struct netconn *conn)
{
struct tcp_pcb *pcb;
pcb = conn->pcb.tcp;
tcp_arg(pcb, conn); // PCB绑定NETCONN接口控制块
tcp_recv(pcb, recv_tcp); // 注册接收回调
tcp_sent(pcb, sent_tcp); // 注册发送回调
tcp_poll(pcb, poll_tcp, NETCONN_TCP_POLL_INTERVAL); // 注册poll
tcp_err(pcb, err_tcp); // 注册异常回调
}
。
recv_tcp() 是TCP netconn注册到tcp的 tcp_pcb->recv() 接收回调函数.
TCP内核收到数据后会通过当前回调函数发送数据包到 conn->recvmbox ,如果投递失败,则不能删除这些 pbuf ,因为 tcp_fasttmr() 会在后面再次通知我们上层接收.
这里发送失败但是不能删除这些 pbuf 的原因:我们TCP已经ACK了这些数据,对端不会再发这些数据了的,所以我们不能完全删除,只能晚点上交给应用层.
和 recv_udp() 略有区别,这里不封装 netbuf ,在调用上层调用 netconn_recv() 函数中再把 pbuf 装成 netbuf .
这样做的目的是因为TCP数据包的封装、处理设计其它很多额外的操作,而当前函数却是一个回调函数,不适合多做业务及长时间占有.
static err_t
recv_tcp(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
{
struct netconn *conn;
u16_t len;
void *msg;
LWIP_UNUSED_ARG(pcb);
LWIP_ASSERT("recv_tcp must have a pcb argument", pcb != NULL);
LWIP_ASSERT("recv_tcp must have an argument", arg != NULL);
LWIP_ASSERT("err != ERR_OK unhandled", err == ERR_OK);
LWIP_UNUSED_ARG(err); /* for LWIP_NOASSERT */
conn = (struct netconn *)arg;
if (conn == NULL) {
return ERR_VAL;
}
LWIP_ASSERT("recv_tcp: recv for wrong pcb!", conn->pcb.tcp == pcb);
if (!NETCONN_MBOX_VALID(conn, &conn->recvmbox)) {
/* recvmbox已经被删除了。如shutdown RX */
if (p != NULL) {
tcp_recved(pcb, p->tot_len); /* 把这些数据从TCP接收缓冲区中全部读走,并更新接收窗口 */
pbuf_free(p); /* 然后释放这些pbuf */
}
return ERR_OK; /* 算是接收成功 */
}
/* 与UDP或RAW pcb不同,不要使用recv_avail检查可用空间,因为这可能会破坏连接。
(这些数据都是已经被我们ACK了的) */
if (p != NULL) { /* 有数据 */
msg = p;
len = p->tot_len;
} else { /* 没数据也触发当前回调,说明TCP协议栈底层是想表示连接已断开 */
msg = LWIP_CONST_CAST(void *, &netconn_closed);
len = 0;
}
if (sys_mbox_trypost(&conn->recvmbox, msg) != ERR_OK) {
/* 不要释放p:它稍后会从tcp_fasttmr再次给我们! */
return ERR_MEM;
} else {
#if LWIP_SO_RCVBUF
SYS_ARCH_INC(conn->recv_avail, len);
#endif /* LWIP_SO_RCVBUF */
/* 通知上层,有数据可读 */
API_EVENT(conn, NETCONN_EVT_RCVPLUS, len);
}
return ERR_OK;
}
。
sent_tcp() 是TCP netconn注册到tcp的 tcp_pcb->sent() 发送回调函数.
当TCP收到更多ACK,发送缓冲区可用空间增大了,就会调用当前回调函数.
主要是唤醒阻塞等待连接关闭或数据发送的应用程序线程.
检查和通知接口层(netconn、socket),有更多缓冲空间了,如果有数据,可以发过来.
static err_t
sent_tcp(void *arg, struct tcp_pcb *pcb, u16_t len)
{
struct netconn *conn = (struct netconn *)arg;
LWIP_UNUSED_ARG(pcb);
LWIP_ASSERT("conn != NULL", (conn != NULL));
if (conn) { /* 接口连接还存在 */
if (conn->state == NETCONN_WRITE) { /* 接口层需要发送数据 */
lwip_netconn_do_writemore(conn WRITE_DELAYED); /* 把数据写入TCP发送缓冲区 */
} else if (conn->state == NETCONN_CLOSE) { /* 接口层已经关闭了当前连接 */
lwip_netconn_do_close_internal(conn WRITE_DELAYED); /* TCP内部资源也关闭 */
}
/* 检查水位线:TCP发送缓冲区 可用空间size在水位线上 && 当前pbuf数量在水位线下
即可通知上层,可往TCP发送缓冲区写入数据。 */
if ((conn->pcb.tcp != NULL) && (tcp_sndbuf(conn->pcb.tcp) > TCP_SNDLOWAT) &&
(tcp_sndqueuelen(conn->pcb.tcp) < TCP_SNDQUEUELOWAT)) {
netconn_clear_flags(conn, NETCONN_FLAG_CHECK_WRITESPACE); /* 清除 检查缓冲区可写 标志 */
API_EVENT(conn, NETCONN_EVT_SENDPLUS, len); /* 通知接口层,当前TCP发送缓冲区可写 */
}
}
return ERR_OK;
}
。
poll_tcp() 是TCP netconn注册到tcp的 tcp_pcb->poll() 周期函数.
tcp_pcb->poll() 被TCP慢时钟 tcp_slowtmr() 时钟调用.
NETCONN_TCP_POLL_INTERVAL==2 ,表示每秒会轮询一次该函数.
主要是唤醒阻塞等待连接关闭或数据发送的应用程序线程.
解除应用程序线程阻塞的方式:发送信号量conn->sem.
如果关闭失败,netconn_close()等待conn->sem.
static err_t
poll_tcp(void *arg, struct tcp_pcb *pcb)
{
struct netconn *conn = (struct netconn *)arg;
LWIP_UNUSED_ARG(pcb);
LWIP_ASSERT("conn != NULL", (conn != NULL));
if (conn->state == NETCONN_WRITE) { /* 如果netconn处于正在发送数据状态,那tcp层就继续从netconn取数据发出去 */
lwip_netconn_do_writemore(conn WRITE_DELAYED);
} else if (conn->state == NETCONN_CLOSE) { /* netconn已经close当前连接了,内部tcp层也要close */
#if !LWIP_SO_SNDTIMEO && !LWIP_SO_LINGER /* 没开socket发送超时 && 没开close()后残留数据超时 */
if (conn->current_msg && conn->current_msg->msg.sd.polls_left) {
conn->current_msg->msg.sd.polls_left--;
}
#endif /* !LWIP_SO_SNDTIMEO && !LWIP_SO_LINGER */
lwip_netconn_do_close_internal(conn WRITE_DELAYED);
}
/* 之前是否有非阻塞的写操作失败?有就检查写缓冲区是否有可用空间 */
if (conn->flags & NETCONN_FLAG_CHECK_WRITESPACE) { /* 之前存在非阻塞写入失败 */
/* 检查发送缓冲区:缓冲区可用size是否足够 和 pbuf数量是否超限 */
if ((conn->pcb.tcp != NULL) && (tcp_sndbuf(conn->pcb.tcp) > TCP_SNDLOWAT) &&
(tcp_sndqueuelen(conn->pcb.tcp) < TCP_SNDQUEUELOWAT)) {
netconn_clear_flags(conn, NETCONN_FLAG_CHECK_WRITESPACE); /* tcp层有更多的发送缓冲区空间可用,则清除该标记 */
API_EVENT(conn, NETCONN_EVT_SENDPLUS, 0); /* 触发一个可写事件到netconn的上层(如socket层) */
}
}
return ERR_OK;
}
。
poll_tcp() 是TCP netconn注册到tcp的 tcp_pcb->errf() 异常回调函数.
TCP PCB出现错误时,会调用当前函数回调到接口层处理:
ERROR
、 RCVPLUS
、 SENDPLUS
事件; recv_mboxes
、 accept_mboxes
发送异常事件; 这种做法的目的就是唤醒因各种情况而阻塞的应用程序,告知当前连接发生错误,需要处理.
static void
err_tcp(void *arg, err_t err)
{
struct netconn *conn;
enum netconn_state old_state;
void *mbox_msg;
SYS_ARCH_DECL_PROTECT(lev);
conn = (struct netconn *)arg;
LWIP_ASSERT("conn != NULL", (conn != NULL));
SYS_ARCH_PROTECT(lev); /* 系统保护:进入临界 */
/* 发生错误,PCB就会被释放,所以可在接口层解除绑定 */
conn->pcb.tcp = NULL;
/* 保存错误码 */
conn->pending_err = err;
/* 防止应用程序线程在'recvmbox'/'acceptmbox'上阻塞 */
conn->flags |= NETCONN_FLAG_MBOXCLOSED;
/* 在唤醒其它线程前,重置当前状态 */
old_state = conn->state;
conn->state = NETCONN_NONE;
SYS_ARCH_UNPROTECT(lev); /* 退出临界 */
/* 通知socket层,当前连接异常。 */
API_EVENT(conn, NETCONN_EVT_ERROR, 0);
/* 给socket层一个可读、可写事件,可让应用层不会阻塞于读、写。 */
API_EVENT(conn, NETCONN_EVT_RCVPLUS, 0);
API_EVENT(conn, NETCONN_EVT_SENDPLUS, 0);
mbox_msg = lwip_netconn_err_to_msg(err); /* err翻译成msg */
/* 通过error message到recvmbox来唤醒阻塞于recv的应用层线程 */
if (NETCONN_MBOX_VALID(conn, &conn->recvmbox)) {
/* use trypost to prevent deadlock */
/* 使用trypost,可以防止死锁 */
sys_mbox_trypost(&conn->recvmbox, mbox_msg);
}
/* 通过error message到acceptmbox来唤醒阻塞于accept的应用层线程 */
if (NETCONN_MBOX_VALID(conn, &conn->acceptmbox)) {
/* 使用trypost,可以防止死锁 */
sys_mbox_trypost(&conn->acceptmbox, mbox_msg);
}
if ((old_state == NETCONN_WRITE) || (old_state == NETCONN_CLOSE) ||
(old_state == NETCONN_CONNECT)) { /* 处于非监听的所有有效态 */
/* PCB已经被干掉了,所以没必要调用lwip_netconn_do_writemore()、lwip_netconn_do_close_internal()这些函数了 */
int was_nonblocking_connect = IN_NONBLOCKING_CONNECT(conn); /* 获取当前netconn是否处于非阻塞连接 */
SET_NONBLOCKING_CONNECT(conn, 0); /* 清除netconn中该标记 */
if (!was_nonblocking_connect) { /* 不处于非阻塞连接状态 */
sys_sem_t *op_completed_sem;
/* set error return code */
LWIP_ASSERT("conn->current_msg != NULL", conn->current_msg != NULL);
if (old_state == NETCONN_CLOSE) {
/* netconn处于close状态,则返回OK,表示close成功 */
conn->current_msg->err = ERR_OK;
} else {
/* 如果处于写或连接状态,则返回对应ERR,表示当前连接异常。 */
conn->current_msg->err = err;
}
/* 获取当前netconn的同步信号量 */
op_completed_sem = LWIP_API_MSG_SEM(conn->current_msg);
LWIP_ASSERT("invalid op_completed_sem", sys_sem_valid(op_completed_sem));
conn->current_msg = NULL; /* 解绑netconn中的当前的同步信号量 */
/* 唤醒阻塞与写或连接的应用程序线程 */
sys_sem_signal(op_completed_sem);
} else { /* 应用程序线程是非阻塞连接 */
/* @todo: 测试非阻塞连接的错误情况 */
}
} else {
/* netconn处于监听态或空闲态 */
LWIP_ASSERT("conn->current_msg == NULL", conn->current_msg == NULL);
}
}
。
accept_function() 是TCP netconn注册到tcp的 lpcb->accept() accept回调函数.
tcp_accept()
API注册。 lpcb->accept()
,用于TCP服务器,监听类型的pcb。 。
recv_udp() :
。
。
通过lwip内核实现的学习,我们知道,lwip内核实现是需要线程安全的.
目前有两种方式:
LWIP_TCPIP_CORE_LOCKING
内核安全锁功能,使用该锁来实现lwip内核的线程安全。 。
。
netconn用户接口形式: netconn_xxx() 。
netconn内核接口形式: lwip_netconn_xxx() 。
用户调用netconn用户接口时,用户接口的目的就是把netconn的内核接口通过安全锁或api消息发送到wlip内核线程执行.
。
netconn用户接口使用 netconn_apimsg() --> tcpip_send_msg_wait_sem() 来共同实现.
。
netconn_apimsg() :
tcpip_callback_fn fn
:需要线程安全的netconn内核API。 struct api_msg *apimsg
:API的指针形参(既然形参是指针,说明是双向参数)
static err_t
netconn_apimsg(tcpip_callback_fn fn, struct api_msg *apimsg)
{
err_t err;
#ifdef LWIP_DEBUG
/* 捕获不设置错误的函数 */
apimsg->err = ERR_VAL;
#endif /* LWIP_DEBUG */
#if LWIP_NETCONN_SEM_PER_THREAD
apimsg->op_completed_sem = LWIP_NETCONN_THREAD_SEM_GET(); /* 获取同步信号量 */
#endif /* LWIP_NETCONN_SEM_PER_THREAD */
/* 把fn()搞到tcpip内核锁内执行 */
err = tcpip_send_msg_wait_sem(fn, apimsg, LWIP_API_MSG_SEM(apimsg));
if (err == ERR_OK) {
return apimsg->err;
}
return err;
}
。
tcpip_send_msg_wait_sem() :
tcpip_callback_fn fn
:需要线程安全的netconn内核API。 void *apimsg
:API的指针形参。 sys_sem_t *sem
:同步信号量。用于阻塞。 tcpip_msg
,发送到 tcpip_mbox
,由TCPIP内核线程监测、执行。 fn
释放该同步信号量来解除调用者线程阻塞。 LWIP_TCPIP_CORE_LOCKING
内核安全锁,因为这是运行时开销最小的方法。
err_t
tcpip_send_msg_wait_sem(tcpip_callback_fn fn, void *apimsg, sys_sem_t *sem)
{
#if LWIP_TCPIP_CORE_LOCKING /* 开启了内核锁,直接在当前线程调用即可 */
LWIP_UNUSED_ARG(sem);
LOCK_TCPIP_CORE(); /* 内核锁上锁 */
fn(apimsg); /* 执行回调 */
UNLOCK_TCPIP_CORE(); /* 释放内核锁 */
return ERR_OK;
#else /* LWIP_TCPIP_CORE_LOCKING */ /* 没有开启内核锁,需要把回调函数外包到TCPIP内核线程 */
TCPIP_MSG_VAR_DECLARE(msg); /* 定义一个tcpip_msg */
LWIP_ASSERT("semaphore not initialized", sys_sem_valid(sem));
LWIP_ASSERT("Invalid mbox", sys_mbox_valid_val(tcpip_mbox));
TCPIP_MSG_VAR_ALLOC(msg); /* 开了MPU,这个就为NULL了 */
TCPIP_MSG_VAR_REF(msg).type = TCPIP_MSG_API; /* 无回传的API消息类型 */
TCPIP_MSG_VAR_REF(msg).msg.api_msg.function = fn; /* API */
TCPIP_MSG_VAR_REF(msg).msg.api_msg.msg = apimsg; /* apimsg */
sys_mbox_post(&tcpip_mbox, &TCPIP_MSG_VAR_REF(msg)); /* 往tcpip_mbox发送一个tcpip_msg */
sys_arch_sem_wait(sem, 0); /* 等待同步信号量被回调函数fn()释放 */
TCPIP_MSG_VAR_FREE(msg); /* 释放tcpip_msg */
return ERR_OK;
#endif /* LWIP_TCPIP_CORE_LOCKING */
}
。
这个同步信号量,就是用于阻塞的,具体是:netconn接口控制块中的 op_completed 信号量.
#define LWIP_API_MSG_SEM(msg) (&(msg)->conn->op_completed)
。
通过一个API例子来实例化线程安全的使用.
用户调用netconn用户接口 netconn_new() ,其实就是 netconn_new_with_proto_and_callback() :
#define netconn_new(t) netconn_new_with_proto_and_callback(t, 0, NULL)
而 netconn_new_with_proto_and_callback() 源码实现如下:
api_msg
资源,把需要内核执行的netconn内核接口和该接口需要的数据打包到 api_msg
。然后将该msg发送到lwip内核(或上锁)执行。如果是发送到lwip内核,则当前线程会等待同步信号量 conn->op_completed
,如果内核执行了netconn内核接口,这个接口会释放该信号量,表示内核已经执行了对应API。
struct netconn *
netconn_new_with_proto_and_callback(enum netconn_type t, u8_t proto, netconn_callback callback)
{
struct netconn *conn;
API_MSG_VAR_DECLARE(msg); // 定义一个api_msg数据结构
API_MSG_VAR_ALLOC_RETURN_NULL(msg); // 申请api_msg数据结构资源,指定错误时返回NULL
conn = netconn_alloc(t, callback); // 申请netconn控制块资源
if (conn != NULL) {
err_t err;
API_MSG_VAR_REF(msg).msg.n.proto = proto; // 把用户连接协议记录到api_msg中
API_MSG_VAR_REF(msg).conn = conn; // 把netconn控制块记录到api_msg中
// 把这个api_msg资源和lwip_netconn_do_newconn()函数封装好,在线程安全下跑(上锁或发送到lwip内核)
err = netconn_apimsg(lwip_netconn_do_newconn, &API_MSG_VAR_REF(msg));
if (err != ERR_OK) {
LWIP_ASSERT("freeing conn without freeing pcb", conn->pcb.tcp == NULL);
LWIP_ASSERT("conn has no recvmbox", sys_mbox_valid(&conn->recvmbox));
#if LWIP_TCP
LWIP_ASSERT("conn->acceptmbox shouldn't exist", !sys_mbox_valid(&conn->acceptmbox));
#endif /* LWIP_TCP */
#if !LWIP_NETCONN_SEM_PER_THREAD
LWIP_ASSERT("conn has no op_completed", sys_sem_valid(&conn->op_completed));
sys_sem_free(&conn->op_completed);
#endif /* !LWIP_NETCONN_SEM_PER_THREAD */
sys_mbox_free(&conn->recvmbox);
memp_free(MEMP_NETCONN, conn);
API_MSG_VAR_FREE(msg);
return NULL;
}
}
API_MSG_VAR_FREE(msg); // 释放api_msg资源
return conn;
}
。
lwip_netconn_do_newconn() :
void
lwip_netconn_do_newconn(void *m)
{
struct api_msg *msg = (struct api_msg *)m;
msg->err = ERR_OK;
if (msg->conn->pcb.tcp == NULL) {
pcb_new(msg); // 创建一个新的特定类型的PCB。
}
/* 释放信号量 */
TCPIP_APIMSG_ACK(msg);
}
。
参考./src/include/lwip/priv/api_msg.h 。
。
netconn内核接口是在LWIP线程安全的下运行的,要么上lwip内核资源锁,要么发送到lwip内核线程去执行,这些操作俊友netconn用户接口去实现.
netconn内核接口主要封装各个协议栈的RAW接口实现,如果线程安全是发送到lwip内核实现,则需要在业务执行完毕后调用 TCPIP_APIMSG_ACK(msg); 来解除调用者线程的阻塞.
。
部分接口列表:
void lwip_netconn_do_newconn (void *m);
void lwip_netconn_do_delconn (void *m);
void lwip_netconn_do_bind (void *m);
void lwip_netconn_do_bind_if (void *m);
void lwip_netconn_do_connect (void *m);
void lwip_netconn_do_disconnect (void *m);
void lwip_netconn_do_listen (void *m);
void lwip_netconn_do_send (void *m);
void lwip_netconn_do_recv (void *m);
#if TCP_LISTEN_BACKLOG
void lwip_netconn_do_accepted (void *m);
#endif /* TCP_LISTEN_BACKLOG */
void lwip_netconn_do_write (void *m);
void lwip_netconn_do_getaddr (void *m);
void lwip_netconn_do_close (void *m);
void lwip_netconn_do_shutdown (void *m);
#if LWIP_IGMP || (LWIP_IPV6 && LWIP_IPV6_MLD)
void lwip_netconn_do_join_leave_group(void *m);
void lwip_netconn_do_join_leave_group_netif(void *m);
#endif /* LWIP_IGMP || (LWIP_IPV6 && LWIP_IPV6_MLD) */
#if LWIP_DNS
void lwip_netconn_do_gethostbyname(void *arg);
#endif /* LWIP_DNS */
netconn接口就不分析源码了,直接描述功能.
这些接口在 ./src/include/lwip/api.h 路径中.
。
下面只列出部分API,这些API都有兄嘚API,可以查看上述路径.
。
这些接口都是把netconn内核接口封装到 api_msg 中,然后将其转发到lwip内核线程(或上锁)执行.
是一个宏,实际是 netconn_new_with_proto_and_callback() :
#define netconn_new(t) netconn_new_with_proto_and_callback(t, 0, NULL)
struct netconn *
netconn_new_with_proto_and_callback(enum netconn_type t, u8_t proto, netconn_callback callback)
{}
创建一个新的netconn,指定协议,指定回调函数.
协议类型查看 netconn_type .
回调函数查看 netconn_callback
/* 通知netconn事件的回调原型 */
typedef void (* netconn_callback)(struct netconn *, enum netconn_evt, u16_t len);
。
netconn_delete() 函数关闭一个netconn“连接”并释放它的资源.
UDP和RAW连接是完全关闭的,TCP pcb可能仍然在等待状态后返回.
。
获取netconn的local或remote的IP地址和端口号.
对于RAW类型的netconn,返回的不是端口号,而是协议.
。
netconn绑定指定的local IP地址和端口号.
一个netconn连续两次绑定同一个IP(注意任意IP)和端口号,第二次会响应绑定失败.
。
netconn连接到指定的remote IP和端口号.
。
netconn断开当前连接(仅对UDP netconn有效) 。
。
设置一个TCP netconn进入监听模式,设置backlog数量上限.
#define netconn_listen(conn) netconn_listen_with_backlog(conn, TCP_DEFAULT_LISTEN_BACKLOG)
TCP_DEFAULT_LISTEN_BACKLOG 默认0xff.
。
accept()一个新的TCP客户端连接.
服务器调用该函数可以从 conn->acceptmbox 邮箱中获取一个新的连接,如果邮箱为空,该函数会一直阻塞,直至有新的连接到来.
在调用次函数之前,先调用 netconn_listen() 让服务器加入监听状态.
。
该函数是从 conn->recvmbox 邮箱中等待数据消息:这些消息就是数据缓存队列:
recv_udp()
会先将接收到的UDP数据封装在 netbuf
结构中,然后将数据消息投递到邮箱中。 pbuf
封装的,在接收到数据包后,函数 netconn_recv()
才将这些 pbuf
封装成 netbuf
。然后发送一个API消息 lwip_netconn_do_recv()
到内核,告知TCP内核,本次已经从TCP缓存中接收了多少数据,让内核调用 tcp_recved()
函数滑动接收窗口。 。
通过过UDP或RAW网络(已经连接)发送数据.
大概内容就是从API消息中获取目的地址信息和数据报pbuf,然后调用 udp_send() 或 udp_sendto() 将数据放出去.
。
。
用于在稳定的TCP连接中发送数据.
。
最后此篇关于【lwip】15-NETCONN接口的文章就讲到这里了,如果你想了解更多关于【lwip】15-NETCONN接口的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在尝试在我的代码库中为我正在编写的游戏服务器更多地使用接口(interface),并了解高级概念以及何时应该使用接口(interface)(我认为)。在我的例子中,我使用它们将我的包相互分离,并使
我有一个名为 Widget 的接口(interface),它在我的整个项目中都在使用。但是,它也用作名为 Widget 的组件的 Prop 。 处理此问题的最佳方法是什么?我应该更改我的 Widget
有一个接口(interface)可以是多个接口(interface)之一 interface a {x:string} interface b {y:string} interface c {z:st
我遇到了一种情况,我需要调用第三方服务来获取一些信息。这些服务对于不同的客户可能会有所不同。我的界面中有一个身份验证功能,如下所示。 interface IServiceProvider { bool
在我的例子中,“RequestHandlerProxy”是一个结构,其字段为接口(interface)“IAdapter”,接口(interface)有可能被调用的方法,该方法的输入为结构“Reque
我有一个接口(interface)Interface1,它已由类A实现,并且设置了一些私有(private)变量值,并且我将类A的对象发送到下一个接受输入作为Interface2的类。那么我怎样才能将
假设我有这样的类和接口(interface)结构: interface IService {} interface IEmailService : IService { Task SendAs
有人知道我在哪里可以找到 XML-RPC 接口(interface)的定义(在 OpenERP 7 中)?我想知道创建或获取对象需要哪些参数和对象属性。每个元素的 XML 示例也将非常有帮助。 最佳答
最近,我一直在阅读有关接口(interface)是抽象的错误概念的文章。一篇这样的帖子是http://blog.ploeh.dk/2010/12/02/InterfacesAreNotAbstract
如果我有一个由第三方实现的现有 IInterface 后代,并且我想添加辅助例程,Delphi 是否提供了任何简单的方法来实现此目的,而无需手动重定向每个接口(interface)方法?也就是说,给定
我正在尝试将 Article 数组分配给我的 Mongoose 文档,但 Typescript 似乎不喜欢这样,我不知道为什么它显示此警告/错误,表明它不可分配. 我的 Mongoose 模式和接口(
我有两个接口(interface): public interface IController { void doSomething(IEntity thing); } public inte
是否可以创建一个扩展 Serializable 接口(interface)的接口(interface)? 如果是,那么扩展接口(interface)的行为是否会像 Serilizable 接口(int
我试图在两个存储之间创建一个中间层,它从存储 A 中获取数据,将其转换为相应类型的存储 B,然后存储它。由于我需要转换大约 50-100 种类型,我希望使用 map[string]func 并根据 s
我正在处理一个要求,其中我收到一个 JSON 对象,其中包含一个日期值作为字符串。我的任务是将 Date 对象存储在数据库中。 这种东西: {"start_date": "2019-05-29", "
我们的方法的目标是为我们现有的 DAO 和模型类引入接口(interface)。模型类由各种类型的资源 ID 标识,资源 ID 不仅仅是随机数,还带有语义和行为。因此,我们必须用对象而不是原始类型来表
Collection 接口(interface)有多个方法。 List 接口(interface)扩展了 Collection 接口(interface)。它声明与 Collection 接口(int
我有一个 Java 服务器应用程序,它使用 Jackson 使用反射 API 对 DTO 进行一般序列化。例如对于这个 DTO 接口(interface): package com.acme.libr
如果我在 Kotlin 中有一个接口(interface): interface KotlinInterface { val id: String } 我可以这样实现: class MyCla
我知道Java中所有访问修饰符之间的区别。然而,有人问了我一个非常有趣的问题,我很难找到答案:Java 中的 private 接口(interface)和 public 接口(interface)有什
我是一名优秀的程序员,十分优秀!