并设置自己的TCP控制块。这里要被调到用的函数叫tcp_parseopt,它目前仅能够做的是提取选项中的MSS(最长报文大小)字段函数还可以调用tcp_eff_send_mss 来设置控 制块中mss字段的值,该函数可直译为“有效发送最长报文大小”,所谓有效,就是指收到SYN数据包中的MSS值不能大于我的硬件支持的最大发送报文长度,即硬件的MTU。因 此当收到的MSS值更大时,设置控制块中mss字段值会被设置为MTU,而不是MSS
这些变量是在tcp_input 函数中,通过接收数据段的各个字段的值进行设置的。全局变量tcphdr 指向收到的数据段TCP头部;全局变量seqno记录了TCP头部中的数据序号字段;全局变量ackno存储确认序号字段;全局变量flags表示TCP首部中的各个标志字段;全局变量tcplen 表示数据报中数据的长度,对于SYN包和FIN包,该长度为1;全局变量inseg用于描述收到的数据内容,它是tcp_seg类型的,tcp_seg这个结构体后面再讲;全局变量recv_flags用来标识tcp_process 函数对数据段的处理结果初始化为 0。
直接到达tcp_process 函数状态机部分,它就是对TCP状态转换图的简单代码诠释 switch (pcb->state) {
case SYN_SENT: // 客户端将SYN发送到服务器等待握手包返回
if ((flags & TCP_ACK) && (flags & TCP_SYN) //实际收到ACK和SYN包
&& ackno == ntohl(pcb->unacked->tcphdr->seqno) + 1) { pcb->snd_buf++; pcb->rcv_nxt = seqno + 1; pcb->lastack = ackno; pcb->snd_wnd = tcphdr->wnd;
pcb->snd_wl1 = seqno - 1; /* initialise to seqno - 1 to force window update */ pcb->state = ESTABLISHED;
tcp_parseopt(pcb); // 处理选项字段
#if TCP_CALCULATE_EFF_SEND_MSS // 根据需要设置有效发送mss字段
pcb->mss = tcp_eff_send_mss(pcb->mss, &(pcb->remote_ip)); #endif
pcb->ssthresh = pcb->mss * 10;//由于重新设置了mss字段值,所以要重设ssthresh值
pcb->cwnd = ((pcb->cwnd == 1) ? (pcb->mss * 2) : pcb->mss); // 设置阻塞窗口
--pcb->snd_queuelen; //要发送的数据段个数减1??
rseg = pcb->unacked; //取下要被确认的字段
E-mail:for_rest@foxmail.com pcb->unacked = rseg->next;
if(pcb->unacked == NULL) //没有字段需要被确认,则停止定时器 pcb->rtime = -1;
else { //否则重新开启定时器 pcb->rtime = 0; pcb->nrtx = 0; }
tcp_seg_free(rseg); //释放已经被确认了的段
TCP_EVENT_CONNECTED(pcb, ERR_OK, err); tcp_ack_now(pcb); //返回ACK握手包 }
else if (flags & TCP_ACK) { //仅仅只有ACK而无SYN标志
tcp_rst(ackno, seqno + tcplen, &(iphdr->dest), &(iphdr->src),
tcphdr->dest, tcphdr->src);//不支持半打开状态,所以返回一个复位包 } break;
case SYN_RCVD: // 服务器端发送出SYN+ACK后便处于该状态
if (flags & TCP_ACK && // 在SYN_RCVD状态接收到ACK返回包
!(flags & TCP_RST)) { // 判断ACK序号是否合法 if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt)) { u16_t old_cwnd;
pcb->state = ESTABLISHED; // 进入ESTABLISHED状态
old_cwnd = pcb->cwnd; // 保存旧的阻塞窗口
accepted_inseq = tcp_receive(pcb); // 若包?数据则接收数据段
pcb->cwnd = ((old_cwnd == 1) ? (pcb->mss * 2) : pcb->mss);//重新设置阻塞窗口
if ((flags & TCP_FIN) && accepted_inseq) { // 如果ACK包同时?有FIN位且
tcp_ack_now(pcb); //已经接收完了最后的数据,则响应FIN
pcb->state = CLOSE_WAIT;//进入CLOSE_WAIT状态 } }
else { //不合法的ACK序号则返回一个复位包 tcp_rst(ackno, &(iphdr->dest), &(iphdr->src), tcphdr->dest, tcphdr->src); } } break;
seqno
+
tcplen,
tcp的输入输出函数
tcp_enqueue,将数据包或者连接的控制握手包放到tcp 控制块的发送队列上。
tcp_enqueue( struct tcp_pcb *pcb, void *arg, u16_t len,u8_t flags, u8_t apiflags,u8_t *optdata, u8_t optlen )
其中有几个重要的输入参数:pcb是相应连接的TCP控制块;