lwIP 细节之一:TCP 最大链接数
Posted 研究是为了理解
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了lwIP 细节之一:TCP 最大链接数相关的知识,希望对你有一定的参考价值。
lwIP 协议栈为每个 TCP 连接分配不同的 TCP_PCB
控制块。然而 lwIP 能分配的 TCP_PCB 控制块最大数目是编程人员指定的。这是因为嵌入式硬件 RAM 都很少,而每个 TCP_PCB 都要占用 RAM,具体数目根据配置的不同而不同,我这边的典型配置每个 TCP_PCB 占用 164
字节 RAM 。
宏 MEMP_NUM_TCP_PCB
指定了 lwIP 能分配的tcp_pcb 最大数量,即同时有效的 TCP 连接个数。
宏 MEMP_NUM_TCP_PCB
在 opt.h
文件中定义,默认 TCP 连接个数为 5。
注:除非特别说明,以下内容针对 lwIP 2.0.0 及以上版本。
/**
* MEMP_NUM_TCP_PCB: the number of simultaneously active TCP connections.
* (requires the LWIP_TCP option)
*/
#if !defined MEMP_NUM_TCP_PCB || defined __DOXYGEN__
#define MEMP_NUM_TCP_PCB 5
#endif
如果需要更改这个宏,要在 lwipopts.h
文件中重新定义该宏:
#define MEMP_NUM_TCP_PCB 6
直接在
opt.h
文件中修改宏MEMP_NUM_TCP_PCB
可以吗?
不可以!
opt.h
文件是 lwIP 协议栈内核的一部分,其中的宏可以理解为 lwIP 的默认值,如果用户需要更改这些默认值,则需要在lwipopts.h
文件中重新定义。
lwipopts.h
文件是用户提供的一个头文件,这样做的好处是将来 lwIP 协议栈更新后,可以直接替换内核文件而不影响应用程序的功能。(替换内核文件会覆盖opt.h
文件,其中的修改也会被还原)
同理,不要图快捷而修改 lwIP 内核的其它文件,这都会给将来的版本升级带来不确定性风险。特别是修改的人已经不在这个项目组,而接手的人又不能了解所有情况时。
对于某个应用程序,如果当前的 TCP 链接数已经达到宏 MEMP_NUM_TCP_PCB
指定的数目,此时再有一个客户端申请连接,lwIP 如何处理?
在 TCP 服务器模式下,负责处理连接接入的是监听(listen)模块。
当客户端发送 SYN
同步标志,表示要建立一个新的连接时,接收到的数据交由 tcp_input
函数处理。由于当前处于监听状态,tcp_input
函数会调用 tcp_listen_input
函数处理这个数据包。
tcp_input
是个功能复杂的函数,我们这里仅观察处于监听状态下,tcp_input
函数的处理流程:
/**
* The initial input processing of TCP. It verifies the TCP header, demultiplexes
* the segment between the PCBs and passes it on to tcp_process(), which implements
* the TCP finite state machine. This function is called by the IP layer (in
* ip_input()).
*
* @param p received TCP segment to process (p->payload pointing to the TCP header)
* @param inp network interface on which this segment was received
*/
void
tcp_input(struct pbuf *p, struct netif *inp)
// 通过一系列检测,报文是合法的
// 通过一系列操作, 在 tcp_listen_pcbs 中查找到了控制块
tcp_listen_input(lpcb); // <-- 这里
pbuf_free(p);
return;
tcp_listen_input
函数的主要功能之一是申请一个 TCP_PCB
控制块内存并初始化:
/**
* Called by tcp_input() when a segment arrives for a listening
* connection (from tcp_input()).
*
* @param pcb the tcp_pcb_listen for which a segment arrived
*
* @note the segment which arrived is saved in global variables, therefore only the pcb
* involved is passed as a parameter to this function
*/
static void
tcp_listen_input(struct tcp_pcb_listen *pcb)
// 通过一系列检查 没有错误
npcb = tcp_alloc(pcb->prio); // <-- 这里
if (npcb == NULL) // 内存错误处理
LWIP_DEBUGF(TCP_DEBUG, ("tcp_listen_input: could not allocate PCB\\n"));
TCP_EVENT_ACCEPT(pcb, NULL, pcb->callback_arg, ERR_MEM, err);
return;
// 申请成功,初始化新申请的pcb
// 发送 ACK|SYN 标志
return;
但是 TCP_PCB
控制块的数目是有限的,这里调用 tcp_alloc(pcb->prio)
函数申请 TCP_PCB
会因内存不足而失败。lwIP 会处理内存错误,通过宏 TCP_EVENT_ACCEPT(pcb, NULL, pcb->callback_arg, ERR_MEM, err)
调用 tcp_accept
API 注册的回调函数。宏展开代码(简化后)如下所示,注意第二个参数为 NULL
:
if(pcb->accept != NULL)
pcb->accept(pcb->callback_arg, NULL, ERR_MEM);
如果我们关心 TCP 连接个数是否不足,编程人员就可以在 tcp_accept
API 注册的回调函数中检查 err 是否为 ERR_MEM
来检查内存不足错误:
/* 客户端连接时, 回调此函数 */
static err_t telnet_accept(void *arg, struct tcp_pcb *pcb, err_t err)
char * p_link_info = "已连接到Telnet!\\r\\n";
if(pcb == NULL)
if(err == ERR_MEM)
// 处理 TCP 连接个数不足
return ERR_OK;
tcp_recv(pcb,telnet_recv);
tcp_err(pcb,NULL);
pcb->so_options |= SOF_KEEPALIVE; //增加保活机制
tcp_write(pcb, p_link_info, strlen(p_link_info), TCP_WRITE_FLAG_COPY);
return ERR_OK;
这里尤其要注意的是,lwIP 2.1.x 版本的 accept 回调函数 必须 在 accept
回调中处理 pcb 句柄为 NULL
的情况!
因此,当连接个数已经用完的前提下,再有客户端连接服务器,服务器不会有任何响应,但会将这个信息以特殊参数的形式提交给编程人员,编程人员在 accept
回调函数内做相应的处理。当然,这个是可选的,也可以不做任何处理。
还没结束!
以上都是针对 lwIP 2.0.0 ~ lwIP 2.1.3 版本,目前仍大量使用的 lwIP 1.4.1 与之不同,我们先看一下 1.4.1 版本的 tcp_listen_input
函数,照例做了简化
static err_t
tcp_listen_input(struct tcp_pcb_listen *pcb)
// 通过一系列检查 没有错误
npcb = tcp_alloc(pcb->prio);
/* If a new PCB could not be created (probably due to lack of memory),
we don't do anything, but rely on the sender will retransmit the
SYN at a time when we have more memory available. */
if (npcb == NULL)
LWIP_DEBUGF(TCP_DEBUG, ("tcp_listen_input: could not allocate PCB\\n"));
return ERR_MEM;
// 申请成功,初始化新申请的pcb
// 发送 ACK|SYN 标志
return ERR_OK;
最大的不同是 1.4.1 版本的 tcp_listen_input
函数有返回值,而 2.0.0 以上版本该函数返回类型为 void
。
返回值是给调用者使用的,我们再看一下在 1.4.1 版本中, 调用者tcp_input
函数如何使用这个返回值:
void
tcp_input(struct pbuf *p, struct netif *inp)
// 通过一系列检测,报文是合法的
// 通过一系列操作, 在 tcp_listen_pcbs 中查找到了控制块
tcp_listen_input(lpcb); // <-- 这里
pbuf_free(p);
return;
嗯~ 压根没有用到返回值!!
所以对于 lwIP 1.4.1 版本,当连接个数已经用完的前提下,再有客户端连接服务器,服务器不会有任何响应,编程人员也根本没有途径得知这一信息。
读后有收获,资助博主养娃 - 千金难买知识,但可以买好多奶粉 (〃‘▽’〃)
以上是关于lwIP 细节之一:TCP 最大链接数的主要内容,如果未能解决你的问题,请参考以下文章