上一篇文章《linux0.99网络模块-网络层(接收)》中我们提到过,注册到IP层的协议有ICMP,TCP,UDP。本文就来分析TCP处理数据报的过程。
我们记得上一篇中网络层通过调用下面的函数来把数据报传递给TCP。
775 ipprot->handler (skb2, dev, &opt, iph->daddr,
776 net16(iph->tot_len) - iph->ihl*4,
777 iph->saddr, 0, ipprot);
776 net16(iph->tot_len) - iph->ihl*4,
777 iph->saddr, 0, ipprot);
net/tcp/protocols.c:
56 static struct ip_protocol tcp_protocol =
57 {
58 tcp_rcv,
59 tcp_err,
60 NULL,
61 IPPROTO_TCP,
62 0, /* copy */
63 NULL
64 };
58行的tcp_rcv对应的就是tcp注册的handler方法
57 {
58 tcp_rcv,
59 tcp_err,
60 NULL,
61 IPPROTO_TCP,
62 0, /* copy */
63 NULL
64 };
58行的tcp_rcv对应的就是tcp注册的handler方法
不过在分析该方法之前还是先分析一相关的数据结构和方法
net/tcp/sock.c
1738 /* This routine must find a socket given a tcp header. Everyhting
1739 is assumed to be in net order. */
sk=get_sock(&tcp_prot, net16(th->dest), saddr, th->source, daddr);
1741 volatile struct sock *get_sock (struct proto *prot, unsigned short num,
1742 unsigned long raddr,
1743 unsigned short rnum, unsigned long laddr)
1744 {
1739 is assumed to be in net order. */
sk=get_sock(&tcp_prot, net16(th->dest), saddr, th->source, daddr);
1741 volatile struct sock *get_sock (struct proto *prot, unsigned short num,
1742 unsigned long raddr,
1743 unsigned short rnum, unsigned long laddr)
1744 {
通过对比上面的函数调用可以推测各个参数的意义如下:
@prot:协议
@num:目的端口
@raddr::源IP地址
@rnum:源端口
@laddr:目的IP地址
1745 volatile struct sock *s;
1746 PRINTK ("get_sock (prot=%X, num=%d, raddr=%X, rnum=%d, laddr=%X)\n",
1747 prot, num, raddr, rnum, laddr);
1748
1749 /* SOCK_ARRAY_SIZE must be a power of two. This will work better
1750 than a prime unless 3 or more sockets end up using the same
1751 array entry. This should not be a problem because most
1752 well known sockets don't overlap that much, and for
1753 the other ones, we can just be careful about picking our
1754 socket number when we choose an arbitrary one. */
1756 for (s=prot->sock_array[num&(SOCK_ARRAY_SIZE-1)]; s != NULL; s=s->next)
1757 {
1758 if (s->num == num) //目的端口是否一致
1759 {
1760 /* we need to see if this is the socket that we want. */
1761 if (!ip_addr_match (s->daddr, raddr))
1762 continue;
检查sock的目的地址与参数中的源地址是否一致,不一致则跳过继续比较其他的。
该函数首先检查两个参数是否完全一致,如果是说明匹配,返回匹配。否则,自右向左逐个字节进行比较,如果相同,继续比较下一个字节,如果不同,就要看第一个参数前面的字节是否全为0,如果是,也可以匹配,如果不是返回不匹配。也就是全为0的前缀可以匹配同样长度的任何网络地址。
1763 if (s->dummy_th.dest != rnum && s->dummy_th.dest!= 0)
1764 continue;
1763 if (s->dummy_th.dest != rnum && s->dummy_th.dest!= 0)
1764 continue;
由于要匹配对应的sock需要源IP:源端口 与 目的IP:目的端口均匹配,这里就是比较源端口
1765 if (!ip_addr_match (s->saddr, laddr))
1766 continue;
1766 continue;
检查sock的源地址与数据报的目的地址是否匹配,如果不匹配,跳过
1767 return (s);
1767 return (s);
找到了匹配的sock(源地址,目的地址均匹配)
1768 }
1769 }
1770 return (NULL);
1771 }
图形化表示如下:
![](https://img-blog.csdn.net/20160730100748439?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
现在总结一下这个函数:我们在计算机网络书籍中也已经知道这样的事实,套接字如果匹配那么必须源IP:源端口与目的IP:目的端口完全匹配。在该函数中我们根据目的端口号从prot的sock_array数组中得到相应的链,也就是说同一个目的端口的sock会链接到一个链上保存到prot的sock_array的num&(SOCK_ARRAY_SIZE-1)索引位置。现在目的端口号已经匹配了,下面就遍历每一个sock,判断它的目的地址,源地址,源端口等是否匹配,如果找到这样的sock将其返回,否则返回NULL。
1768 }
1769 }
1770 return (NULL);
1771 }
图形化表示如下:
现在总结一下这个函数:我们在计算机网络书籍中也已经知道这样的事实,套接字如果匹配那么必须源IP:源端口与目的IP:目的端口完全匹配。在该函数中我们根据目的端口号从prot的sock_array数组中得到相应的链,也就是说同一个目的端口的sock会链接到一个链上保存到prot的sock_array的num&(SOCK_ARRAY_SIZE-1)索引位置。现在目的端口号已经匹配了,下面就遍历每一个sock,判断它的目的地址,源地址,源端口等是否匹配,如果找到这样的sock将其返回,否则返回NULL。
多说几句,在服务器编程时,每到一个请求,我们会为其创建一个sock(第一次创建,之后查询),在sock里面就保存了一对套接字信息,然后根据目的端口把它挂到相应协议(prot)的sock_array中相应链表中。
tcp_sequence (sk, th, len, opt, saddr)
2635 /* this functions checks to see if the tcp header is actually
2636 acceptible. */
这个函数用来判断到达的数据报是否是可接受的
2638 static int
2639 tcp_sequence (volatile struct sock *sk, struct tcp_header *th, short len,
2640 struct options *opt, unsigned long saddr)
2641 {
2642 /* this isn't quite right. sk->acked_seq could be more recent
2643 than sk->window. This is however close enough. We will accept
2644 slightly more packets than we should, but it should not cause
2645 problems unless someone is trying to forge packets. */
2647 PRINTK ("tcp_sequence (sk=%X, th=%X, len = %d, opt=%d, saddr=%X)\n",
2648 sk, th, len, opt, saddr);
2650 if (between(th->seq, sk->acked_seq, sk->acked_seq + sk->window)||
2636 acceptible. */
这个函数用来判断到达的数据报是否是可接受的
2638 static int
2639 tcp_sequence (volatile struct sock *sk, struct tcp_header *th, short len,
2640 struct options *opt, unsigned long saddr)
2641 {
2642 /* this isn't quite right. sk->acked_seq could be more recent
2643 than sk->window. This is however close enough. We will accept
2644 slightly more packets than we should, but it should not cause
2645 problems unless someone is trying to forge packets. */
2647 PRINTK ("tcp_sequence (sk=%X, th=%X, len = %d, opt=%d, saddr=%X)\n",
2648 sk, th, len, opt, saddr);
2650 if (between(th->seq, sk->acked_seq, sk->acked_seq + sk->window)||
接收到的数据报序号在确认号之后,并落在窗口内,可接受
2651 between(th->seq + len-sizeof (*th), sk->acked_seq,
2652 sk->acked_seq + sk->window) ||
2651 between(th->seq + len-sizeof (*th), sk->acked_seq,
2652 sk->acked_seq + sk->window) ||
接收到数据报的序号+首部长度之和落在确认号之后的窗口内,可接受
2653 (before (th->seq, sk->acked_seq) &&
2654 after (th->seq + len - sizeof (*th), sk->acked_seq + sk->window)))
2653 (before (th->seq, sk->acked_seq) &&
2654 after (th->seq + len - sizeof (*th), sk->acked_seq + sk->window)))
接收到的数据报序号在确认号之前,同时接收数据延伸到了窗口外面,这时也是可接收的
2655 {
2656 return (1);
2657 }
对应这几种情况图示如下:
![](https://img-blog.csdn.net/20160730150040647?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
2655 {
2656 return (1);
2657 }
对应这几种情况图示如下:
2661 /* if it's too far ahead, send an ack to let the other end
2662 know what we expect. */
2663 if (after (th->seq, sk->acked_seq + sk->window))
2664 {
2665 tcp_send_ack (sk->send_seq, sk->acked_seq, sk, th, saddr);
2666 return (0);
2667 }
如接收到的数据报的序号都落在了窗口外侧,这可能是由于窗口在中间更新过,这时需要重新通知对方确认信息(包括了窗口信息)
2662 know what we expect. */
2663 if (after (th->seq, sk->acked_seq + sk->window))
2664 {
2665 tcp_send_ack (sk->send_seq, sk->acked_seq, sk, th, saddr);
2666 return (0);
2667 }
如接收到的数据报的序号都落在了窗口外侧,这可能是由于窗口在中间更新过,这时需要重新通知对方确认信息(包括了窗口信息)
2669 /* in case it's just a late ack, let it through */
2670 if (th->ack && len == th->doff*4 && after (th->seq, sk->acked_seq - 32767) &&
2671 !th->fin && !th->syn) return (1);
迟到报文,也是可接收的
2673 if (!th->rst)
2674 {
2675 /* try to resync things. */
2676 tcp_send_ack (net32(th->ack_seq), sk->acked_seq, sk, th, saddr);
2677 }
运行到2673行说明,数据报不可接收,那么如果没有设置复位标记的话就发送确认数据报,使得发送方可以得知较新的信息
2680 return (0);
2681 }
1873 /* This routine deals with incoming acks, but not outgoing ones. */
//该函数只处理接收的确认报文,而不管发送的确认报文
1875 static int
1876 tcp_ack (volatile struct sock *sk, struct tcp_header *th, unsigned long saddr)
1877 {
1878 unsigned long ack;
1879 ack = net32(th->ack_seq);
1884 if (after (ack, sk->send_seq+1) || before (ack, sk->rcv_ack_seq-1))
1885 { //如果接收到的确认号大于发送报文的序号加1 或者 小于已接受的确认-1.
1886 if (after (ack, sk->send_seq) || (sk->state != TCP_ESTABLISHED &&
1887 sk->state != TCP_CLOSE_WAIT))
1888 {
1889 return (0);
1890 }
到这里说明收到一个合法的确认报文
1891 if (sk->keepopen)
1892 reset_timer ((struct timer *)&sk->time_wait);
每次收到一个合法确认报文都会重置定时器
1893 sk->retransmits = 0;
不需要重传
1894 return (1);
1895 }
1894 return (1);
1895 }
===============================================================
第一步:对确认号不在[rcv_ack_seq-1,send_seq+1]之间的确认报文的处理,也就是下图阴影部分:
![](https://img-blog.csdn.net/20160730105224611?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
它包括两种情况
1.对还未发送报文的确认报文
2.对已经确认报文的确认
对于第1种情况的处理是直接返回0;对于第2种情况,如果不是TCP_ESTABLISHED和TCP_CLOSE_WAIT状态,直接返回0. 如果是其中一种状态,执行1891-1894行,其中1892行重设定时器(如果连接没有关闭),1893行设置重传标记为0.最后返回1。
到这里说明接收到的是[rcv_ack_seq-1,send_seq+1]之间的确认报文,即下图阴影部分: