每日速看!LwIP中TCP协议是如何实现的
前言说明
本文章代码非常多,并且难懂,如非特别需要,否则不建议阅读!建议学习TCP协议理论,等基础扎实后再去阅读lwip源码,本文章的源码只是辅助真正有需要的人阅读!
(资料图片仅供参考)
TCP控制块
与其他协议一样,为了描述TCP
协议,LwIP定义了一个名字叫tcp_pcb
的结构体,可以称之为TCP控制块
,其内定义了大量的成员变量,基本定义了整个TCP协议运作过程的所有需要的东西,如发送窗口、接收窗口、数据缓冲区。超时处理、拥塞控制、滑动窗口等等。
1/** TCP协议控制块 */ 2struct tcp_pcb 3{ 4 IP_PCB; 5/** 协议特定的PCB成员 */ 6 TCP_PCB_COMMON(struct tcp_pcb); 7 8 /* 远端端口号 */ 9 u16_t remote_port; 10 11 tcpflags_t flags; 12#define TF_ACK_DELAY 0x01U /* 延迟发送ACK */ 13#define TF_ACK_NOW 0x02U /* 立即发送ACK. */ 14#define TF_INFR 0x04U /* 在快速恢复。 */ 15#define TF_CLOSEPEND 0x08U /* 关闭挂起 */ 16#define TF_RXCLOSED 0x10U /* rx由tcp_shutdown关闭 */ 17#define TF_FIN 0x20U /* 连接在本地关闭 */ 18#define TF_NODELAY 0x40U /* 禁用Nagle算法*/ 19#define TF_NAGLEMEMERR 0x80U /* 本地缓冲区溢出 */ 20#define TF_TIMESTAMP 0x0400U /* Timestamp option enabled */ 21#endif 22#define TF_RTO 0x0800U /* RTO计时器 */ 23 24 u8_t polltmr, pollinterval; 25 /* 控制块被最后一次处理的时间 */ 26 u8_t last_timer; 27 u32_t tmr; 28 29 /* 接收窗口相关的字段 */ 30 u32_t rcv_nxt; /* 下一个期望收到的序号 */ 31 tcpwnd_size_t rcv_wnd; /* 接收窗口大小 */ 32 tcpwnd_size_t rcv_ann_wnd; /* 告诉对方窗口的大小 */ 33 u32_t rcv_ann_right_edge; /* 窗口的右边缘 */ 34 35 /* 重传计时器。*/ 36 s16_t rtime; 37 38 u16_t mss; /* 最大报文段大小 */ 39 40 /* RTT(往返时间)估计变量 */ 41 u32_t rttest; /* RTT估计,以为500毫秒递增 */ 42 u32_t rtseq; /* 用于测试RTT的报文段序号 */ 43 s16_t sa, sv; /* RTT估计得到的平均值与时间差 */ 44 45 s16_t rto; /* 重传超时 */ 46 u8_t nrtx; /* 重传次数 */ 47 48 /* 快速重传/恢复 */ 49 u8_t dupacks; 50 u32_t lastack; /* 接收到的最大确认序号 */ 51 52 /* 拥塞避免/控制变量 */ 53 tcpwnd_size_t cwnd; /* 连接当前的窗口大小 */ 54 tcpwnd_size_t ssthresh; /* 拥塞避免算法启动的阈值 */ 55 56 57 u32_t rto_end; 58 59 u32_t snd_nxt; /* 下一个要发送的序号 */ 60 u32_t snd_wl1, snd_wl2; /* 上一次收到的序号和确认号 */ 61 u32_t snd_lbb; /* 要缓冲的下一个字节的序列号 */ 62 tcpwnd_size_t snd_wnd; /* 发送窗口大小 */ 63 tcpwnd_size_t snd_wnd_max; /* 对方的最大发送方窗口 */ 64 65 /* 可用的缓冲区空间(以字节为单位)。 */ 66 tcpwnd_size_t snd_buf; 67 68 tcpwnd_size_t bytes_acked; 69 70 struct tcp_seg *unsent; /* 未发送的报文段 */ 71 struct tcp_seg *unacked; /* 已发送但未收到确认的报文段 */ 72 struct tcp_seg *ooseq; 73 /* 以前收到但未被上层处理的数据 */ 74 struct pbuf *refused_data; 75 76#if LWIP_CALLBACK_API|| TCP_LISTEN_BACKLOG 77 struct tcp_pcb_listen* listener; 78#endif 79 80//TCP协议相关的回调函数 81#if LWIP_CALLBACK_API 82 /* 当数据发送成功后被调用 */ 83 tcp_sent_fn sent; 84 /* 接收数据完成后被调用 */ 85 tcp_recv_fn recv; 86 /* 建立连接后被调用 */ 87 tcp_connected_fn connected; 88 /* 该函数被内核周期调用 */ 89 tcp_poll_fn poll; 90 /* 发送错误时候被调用 */ 91 tcp_err_fn errf; 92#endif 93 94 /* 保持活性 */ 95 u32_t keep_idle; 96 /* 坚持计时器计数器值 */ 97 u8_t persist_cnt; 98 u8_t persist_backoff; 99 u8_t persist_probe;100101 /* 保持活性报文发送次数 */102 u8_t keep_cnt_sent;103104};
IP_PCB
又是一个宏定义,定义了IP层需要的一些成员变量:
1#define IP_PCB 2 /* 本地ip地址与远端IP地址 */ 3 ip_addr_t local_ip; 4 ip_addr_t remote_ip; 5 /* 绑定netif索引 */ 6 u8_t netif_idx; 7 /* 套接字选项 */ 8 u8_t so_options; 9 /* 服务类型 */ 10 u8_t tos; 11 /* 生存时间 */ 12 u8_t ttl 13 /* 链路层地址解析提示 */ 14 IP_PCB_NETIFHINT
TCP_PCB_COMMON
则是定义了一些特定的TCP控制块的成员变量:
1#define TCP_PCB_COMMON(type) 2 type *next; /* 指向链表中的下一个控制块 */ 3 void *callback_arg; 4 TCP_PCB_EXTARGS 5 enum tcp_state state; /* TCP状态 */ 6 u8_t prio; 7 /* 本地主机端口号 */ 8 u16_t local_port
LwIP
中除了定义了一个完整的TCP控制块
之外,还定义了一个删减版
的TCP控制块
——tcp_pcb_listen
,它用于描述处于监听状态
的TCP连接,因为分配完整的TCP控制块
是比较消耗内存资源的,而TCP协议在建立连接之前是无传输数据的,因此在监听的时候只需要把建立连接的主机的相关信息得到,然后无缝切换到完整的TCP控制块中,这样子就能节省不少资源(毕竟在嵌入式设备中资源是有限的)。除了tcp_pcb_listen
外,LwIP还定义了4
个链表来维护TCP
连接时的各种状态,分别是:
新绑定的端口链表
,用于记录新绑定端口
的TCP控制块。监听链表
:用于记录处于监听状态
的TCP控制块 。活动状态链表
:用于记录处于其他(活动)状态
的TCP控制块。TIME_WAIT链表
:用于记录处于TIME_WAIT状态
的控制块。1/** 用于监听的TCP协议控制块 */ 2struct tcp_pcb_listen { 3/** 所有PCB类型的通用成员 */ 4 IP_PCB; 5/** 协议特定的PCB成员 */ 6 TCP_PCB_COMMON(struct tcp_pcb_listen); 7}; 8 9/* TCP 控制块链表. */10/** 新绑定的端口 */11struct tcp_pcb *tcp_bound_pcbs;12/** 处于监听状态的TCP控制块 */13union tcp_listen_pcbs_t tcp_listen_pcbs;14/** 稳定的TCP连接 */15struct tcp_pcb *tcp_active_pcbs;16/** 处于TIME_WAIT状态的控制块 */17struct tcp_pcb *tcp_tw_pcbs;
tcp_bound_pcbs
链表上的TCP控制块可以看做是处于CLOSED
状态,那些新绑定的端口初始的时候都是处于这个状态。tcp_listen_pcbs
链表用于记录处于监听状态的TCP控制块,一般就是记录tcp_pcb_listen
控制块。tcp_tw_pcbs
链表用于记录连接中处于TIME_WAIT
状态下的TCP控制块。而tcp_active_pcbs
链表用于记录所有其他状态(活动状态)
的TCP控制块,这些端口是活跃的,可以不断进行状态转移。
窗口
关于窗口的理论我不想讲太多,大家有兴趣可以看一下网络上的其他博文,都是描述得很详细的。
TCP控制块中关于接收窗口的成员变量有rcv_nxt、rcv_wnd、rcv_ann_wnd、rcv_ann_right_edge
,rcv_nxt
表示下次期望接收到的数据编号,rcv_wnd
表示接收窗口的大小,rcv_ann_wnd
用于告诉发送方窗口的大小,rcv_ann_right_edge
记录了窗口的右边界,这4个成员变量都会在数据传输的过程中动态改变的。
TCP控制块中关于发送窗口的成员变量有lastack、snd_nxt、snd_lbb、snd_wnd
,lastack
记录了已经确认的最大序号,snd_nxt
表示下次要发送的序号,snd_lbb
是表示下一个将被应用线程缓冲的序号,而snd_wnd
表示发送窗口的大小,是由接收已方提供的。这些值也是动态变化的,当发送的数据收到确认,就会更新lastack
,并且随着数据的发送出去,窗口会向右移动,即snd_nxt
的值在增加。
每条TCP 连接的每一端都必须设有两个窗口:一个发送窗口和一个接收窗口
,TCP 的可靠传输机制用字节的序号(编号)进行控制,TCP 所有的确认都是基于数据的序号而不是基于报文段,发送过的数据未收到确认之前必须保留,以便超时重传时使用,发送窗口在没收到确认序号之前是保持不动的,当收到确认序号就会向右移动,并且更新lastack
的值。
发送缓冲区用来暂时存放应用程序发送给对方的数据,这是主机已发送出但未收到确认的数据。接收缓存用来暂时存放按序到达的、但尚未被接收应用程序读取的数据以及 不按序到达的数据。
关于窗口的概念必须强调2点:
发送方的发送窗口并不总是和 接收方接收窗口一样大,因为有一定的时间滞后。TCP 标准没有规定对不按序到达的数据应如何处理,通常是先临时存放在接收窗口中,等到字节流中所缺少的字节收到后,再按序交付上层的应用进程。TCP报文段发送
每个已经连接的TCP控制块中维护了3个是指针,分别是unsent、unacked、ooseq
,unsent
指向未发送的报文段缓冲队列,unacked
指向已发送但未收到确认的报文段缓冲队列,ooseq
指向已经收到的无序报文段缓冲队列。
简单来说TCP协议发送报文就是将应用层的数据写入发送缓冲区(缓冲队列)中,根据窗口大小进行发送。在LwIP
中,为了更高效发送数据,Nagle算法是默认打开的。因此LwIP的处理是调用tcp_write()
函数将应用层的数据写入TCP
报文段缓冲队列,即TCP控制块中的unsent
指针所指向的队列中,但是不会立即
发送,而是存储在缓冲区里面,等待更多的数据进行高效的发送。当然只要你写入的数据满足Nagle算法
的大小MSS
,这是可以立即发送出去的,否则就等待超时或者数据达到MSS
就会将数据发送出去。当然,我们也能将Nagle算法
禁用。ps:关于写入缓冲队列的操作大家可以直接看源码即可。
当然,我们也能手动在写入数据后直接调用TCP协议的发送数据函数来发送这些数据(RAW API
比较常用这种方法),LwIP
是调用tcp_output()
函数来发送这些数据的,这样子一个应用层的数据就通过TCP协议传递给IP层了。
关于Nagle算法
的介绍,我引用维基百科的一段描述:
TCP/IP协议中,无论发送多少数据,总是要在数据前面加上协议头,同时,对方接收到数据,也需要发送ACK表示确认。为了尽可能的利用网络带宽,TCP总是希望尽可能的发送足够大的数据。(一个连接会设置MSS参数,因此,TCP/IP希望每次都能够以MSS尺寸的数据块来发送数据)。Nagle算法就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。
Nagle算法的基本定义是任意时刻,最多只能有一个未被确认的小段。所谓“小段”,指的是小于MSS尺寸的数据块,所谓“未被确认”,是指一个数据块发送出去后,没有收到对方发送的ACK确认该数据已收到。
代码的实现如下:
1err_t 2tcp_output(struct tcp_pcb *pcb) 3{ 4 struct tcp_seg *seg, *useg; 5 u32_t wnd, snd_nxt; 6 err_t err; 7 struct netif *netif; 8 9 //如果控制块有数据在处理,直接返回 10 if (tcp_input_pcb == pcb) { 11 return ERR_OK; 12 } 13 14 //得到合适的发送窗口 15 wnd = LWIP_MIN(pcb->snd_wnd, pcb->cwnd); 16 17 //找到控制块中的未发送数据缓冲区链表 18 seg = pcb->unsent; 19 20 //根据控制块IP地址信息找到合适的网卡发送 21 netif = tcp_route(pcb, &pcb->local_ip, &pcb->remote_ip); 22 if (netif == NULL) { 23 return ERR_RTE; 24 } 25 26 /* 如果没有本地IP地址,我们会从netif获得一个 */ 27 if (ip_addr_isany(&pcb->local_ip)) { 28 const ip_addr_t *local_ip = 29 ip_netif_get_local_ip(netif, &pcb->remote_ip); 30 if (local_ip == NULL) { 31 return ERR_RTE; 32 } 33 ip_addr_copy(pcb->local_ip, *local_ip); 34 } 35 36 /* 处理当前不适合窗口的报文段 */ 37 if (lwip_ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len > wnd) 38 { 39 //开始持续定时器40 if (wnd == pcb->snd_wnd && pcb->unacked == NULL && 41 pcb->persist_backoff == 0) 42 { 43 pcb->persist_cnt = 0; 44 pcb->persist_backoff = 1; 45 pcb->persist_probe = 0; 46 } 47 /* 我们需要ACK,但现在无法发送数据(无法捎带),所以发送一个ACK报文段 */ 48 if (pcb->flags & TF_ACK_NOW) { 49 return tcp_send_empty_ack(pcb); 50 } 51 goto output_done; 52 } 53 /* 停止持续计时器 */ 54 pcb->persist_backoff = 0; 55 56 /* useg指向未应答队列中的最后一个tcp_seg结构 */ 57 useg = pcb->unacked; 58 if (useg != NULL) { 59 for (; useg->next != NULL; useg = useg->next); 60 } 61 /* 可用数据和窗口允许它发送报文段,直到把未发送链表的数据完全发送出去或者直到填满发送窗口 */ 62 while (seg != NULL &&lwip_ntohl(seg->tcphdr->seqno) 63 - pcb->lastack + seg->len <= wnd) 64 { 65 if ((tcp_do_output_nagle(pcb) == 0) && 66 ((pcb->flags & (TF_NAGLEMEMERR | TF_FIN)) == 0)) { 67 break; 68 } 69 70 if (pcb->state != SYN_SENT) { 71 TCPH_SET_FLAG(seg->tcphdr, TCP_ACK); 72 } 73 74 //真正发送TCP报文的函数,此处发送TCP报文段 75 err = tcp_output_segment(seg, pcb, netif); 76 77 if (err != ERR_OK) 78 { 79 tcp_set_flags(pcb, TF_NAGLEMEMERR); 80 return err; 81 } 82 83 //得到下一个未发送的tcp_seg 84 pcb->unsent = seg->next; 85 if (pcb->state != SYN_SENT) 86 { 87 tcp_clear_flags(pcb, TF_ACK_DELAY | TF_ACK_NOW); 88 } 89 //计算snd_nxt的值 90 snd_nxt = lwip_ntohl(seg->tcphdr->seqno) + TCP_TCPLEN(seg); 91 92 //更新下一个要发送的数据编号 93 if (TCP_SEQ_LT(pcb->snd_nxt, snd_nxt)) 94 { 95 pcb->snd_nxt = snd_nxt; 96 } 97 /* 如果发送出去的数据长度>0,则将这些报文段放在未确认链表中 */ 98 if (TCP_TCPLEN(seg) > 0) 99 {100 seg->next = NULL;101 /* 未确认链表为空,插入即可 */102 if (pcb->unacked == NULL) {103 pcb->unacked = seg;104 useg = seg;105106 } 107 //如果不为空,按照顺序插入未确认链表中108 else 109 {110 if (TCP_SEQ_LT(lwip_ntohl(seg->tcphdr->seqno),111 lwip_ntohl(useg->tcphdr->seqno))) 112 {113 struct tcp_seg **cur_seg = &(pcb->unacked);114 while (*cur_seg &&115 TCP_SEQ_LT(lwip_ntohl((*cur_seg)->tcphdr->seqno), lwip_ntohl(seg->tcphdr->seqno))) {116 cur_seg = &((*cur_seg)->next );117 }118 seg->next = (*cur_seg);119 (*cur_seg) = seg;120 }121 else 122 {123 useg->next = seg;124 useg = useg->next;125 }126 }127 }128 else 129 {130 tcp_seg_free(seg);131 }132 seg = pcb->unsent;133 }134135output_done:136 tcp_clear_flags(pcb, TF_NAGLEMEMERR);137 return ERR_OK;138}
总的来说,流程还是很简单明了的,如果控制块的flags
字段被设置为TF_ACK_NOW
,表示需要立即响应对方,但是此时还没有数据发送,就只发送一个纯粹的ACK
应答报文段,如果能发送数据,那就将ACK
应答捎带过去(捎带机制
),这样子就能减少网络中的流量。TCP
协议在发送的时候先找到未发送队列unsent
,然后调用tcp_output_segment()->ip_output_if()
函数进行发送,将TCP报文段传递到IP层,直到把未发送队列的数据完全发送
出去或者直到填满发送窗口
才算是完成一次发送操作,同时要更新发送窗口相关字段,还要将这些已发送但是未确认的数据存储在已发送但未确认链表unacked
中,以防丢失数据进行重发操作,放入未确认链表的时候是按序号升序进行排序的。
TCP报文段接收
IP数据报
中如果是递交给TCP
协议的数据,就会调用tcp_input()
函数往上层传递,因此TCP协议的报文段接收函数就是tcp_input()
函数。只不过这个函数太过于复杂,我自己都不想看它,就简单用文字描述一下处理过程吧,然后删减一下代码让大伙看看。
tcp_input()
函数会对传递进来的IP数据报
进行处理,做一些校验
,检查数据报是否正确等操作,查看一下数据报中是否有数据
,如果没有就丢掉,再看一下是不是多播、广播
报文,如果是就不做处理,释放pbuf。将TCP
首部中的各字段内容提取出来,首先在 tcp_active_pcbs
链表中寻找对应的TCP
控制块,找到了就调用tcp_process()
函数进行处理;如果找不到
就去tcp_tw_pcbs
链表中查找,找到了就调用tcp_timewait_input()
函数处理它;如果还是找不到
就去tcp_listen_pcbs
链表中找,如果找到就调用tcp_listen_input()
函数处理,如果还是找不到的话,那没办法了,这收到的是垃圾数据,释放pbu。
还要注意的是,TCP协议很可能收到不是正常数据,而是一些特殊TCP报文段
:
复位报文
或终止连接应答报文
,那么就释放pbuf,终止连接如果是收到了应答报文段
(发送数据后必须等待应答),那么就调用宏TCP_EVENT_SENT
(其实是一个sent的回调函数)去处理,并且更新窗口如果报文段中包含有效的数据,就调用TCP_EVENT_RECV
去处理它,此时将产生应答报文与更新接收窗口的操作如果是收到FIN报文,则调用TCP_EVENT_CLOSED
去处理它,此时将产生应答并且开始终止连接代码如下:
1void 2tcp_input(struct pbuf *p, struct netif *inp) 3{ 4 struct tcp_pcb *pcb, *prev; 5 struct tcp_pcb_listen *lpcb; 6 7 u8_t hdrlen_bytes; 8 err_t err; 9 10 LWIP_UNUSED_ARG(inp); 11 12 PERF_START; 13 14 TCP_STATS_INC(tcp.recv); 15 MIB2_STATS_INC(mib2.tcpinsegs); 16 17 tcphdr = (struct tcp_hdr *)p->payload; 18 19 /* 检查报文段是否有有效数据 */ 20 if (p->len < TCP_HLEN) 21 { 22 /* 如果没有就丢掉报文段 */ 23 TCP_STATS_INC(tcp.lenerr); 24 goto dropped; 25 } 26 27 /* 不处理传入的广播/多播报文段。 */ 28 if (ip_addr_isbroadcast(ip_current_dest_addr(), 29 ip_current_netif()) || 30 ip_addr_ismulticast(ip_current_dest_addr())) 31 { 32 TCP_STATS_INC(tcp.proterr); 33 goto dropped; 34 } 35 36 /* 检查TCP报文段首部长度 */ 37 hdrlen_bytes = TCPH_HDRLEN_BYTES(tcphdr); 38 if ((hdrlen_bytes < TCP_HLEN) || (hdrlen_bytes > p->tot_len)) 39 { 40 TCP_STATS_INC(tcp.lenerr); 41 goto dropped; 42 } 43 44 /* 移动pbuf指针,指向TCP报文段数据区域 */ 45 tcphdr_optlen = (u16_t)(hdrlen_bytes - TCP_HLEN); 46 tcphdr_opt2 = NULL; 47 if (p->len >= hdrlen_bytes) 48 { 49 tcphdr_opt1len = tcphdr_optlen; 50 pbuf_remove_header(p, hdrlen_bytes); 51 } 52 53 /* 将TCP首部中的各字段内容提取出来。 */ 54 tcphdr->src = lwip_ntohs(tcphdr->src); 55 tcphdr->dest = lwip_ntohs(tcphdr->dest); 56 seqno = tcphdr->seqno = lwip_ntohl(tcphdr->seqno); 57 ackno = tcphdr->ackno = lwip_ntohl(tcphdr->ackno); 58 tcphdr->wnd = lwip_ntohs(tcphdr->wnd); 59 60 flags = TCPH_FLAGS(tcphdr); 61 tcplen = p->tot_len; 62 63 if (flags & (TCP_FIN | TCP_SYN)) 64 { 65 tcplen++; 66 if (tcplen < p->tot_len) 67 { 68 /* u16_t溢出,无法处理这个 */ 69 TCP_STATS_INC(tcp.lenerr); 70 goto dropped; 71 } 72 } 73 74 prev = NULL; 75 76 //遍历tcp_active_pcbs链表寻找对应的TCP控制块 77 for (pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) 78 { 79 /* 检查控制块是否与对应的网卡绑定 */ 80 if ((pcb->netif_idx != NETIF_NO_INDEX) && 81 (pcb->netif_idx != 82 netif_get_index(ip_data.current_input_netif))) 83 { 84 prev = pcb; 85 continue; 86 } 87 /* ··· */ 88 /* 省略处理 */ 89 /* ··· */ 90 91 if (pcb == NULL) 92 { 93 /* 如果TCP控制块没有处于连接状态,就去tcp_tw_pcbs链表中找 */ 94 for (pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next) 95 { 96 /* 检查控制块是否与对应的网卡绑定 */ 97 if ((pcb->netif_idx != NETIF_NO_INDEX) && 98 (pcb->netif_idx != netif_get_index 99 (ip_data.current_input_netif))) 100 {101 continue;102 }103104 if (pcb->remote_port == tcphdr->src &&105 pcb->local_port == tcphdr->dest &&106 ip_addr_cmp(&pcb->remote_ip, ip_current_src_addr()) &&107 ip_addr_cmp(&pcb->local_ip, ip_current_dest_addr())) 108 {109 //找到了就处理它110 tcp_timewait_input(pcb);111112 pbuf_free(p);113 return;114 }115 }116117 /* 还是找不到就去tcp_listen_pcbs链表中找 */118 prev = NULL;119 for (lpcb = tcp_listen_pcbs.listen_pcbs; 120 lpcb != NULL; lpcb = lpcb->next) 121 {122 /* 检查控制块是否与对应的网卡绑定 */123 if ((lpcb->netif_idx != NETIF_NO_INDEX) &&124 (lpcb->netif_idx != netif_get_index(ip_data.current_input_netif))) {125 prev = (struct tcp_pcb *)lpcb;126 continue;127 }128 /* ··· */129 /* 省略处理 */130 /* ··· */131132 //找到了处于监听状态的TCP控制块133 if (lpcb != NULL) 134 {135 if (prev != NULL) {136 ((struct tcp_pcb_listen *)prev)->next = lpcb->next;137 lpcb->next = tcp_listen_pcbs.listen_pcbs;138 tcp_listen_pcbs.listen_pcbs = lpcb;139 } else {140 TCP_STATS_INC(tcp.cachehit);141 }142 //处理报文段143 tcp_listen_input(lpcb);144 pbuf_free(p);145 return;146 }147 }148149 /* ··· */150 /* 省略处理 */151 /* ··· */152153 tcp_input_pcb = pcb;154 err = tcp_process(pcb);155156 /* ··· */157 /* 省略处理 */158 /* ··· */159160 }161}