linux0.99网络模块-传输层(TCP接收)

上一篇文章《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);


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方法
不过在分析该方法之前还是先分析一相关的数据结构和方法
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 {
通过对比上面的函数调用可以推测各个参数的意义如下:
@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;
由于要匹配对应的sock需要源IP:源端口 与 目的IP:目的端口均匹配,这里就是比较源端口

1765       if (!ip_addr_match (s->saddr, laddr))
1766         continue;
检查sock的源地址与数据报的目的地址是否匹配,如果不匹配,跳过
1767       return (s);
找到了匹配的sock(源地址,目的地址均匹配)
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)||
接收到的数据报序号在确认号之后,并落在窗口内,可接受
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)))
接收到的数据报序号在确认号之前,同时接收数据延伸到了窗口外面,这时也是可接收的
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     }
如接收到的数据报的序号都落在了窗口外侧,这可能是由于窗口在中间更新过,这时需要重新通知对方确认信息(包括了窗口信息)

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     }
===============================================================
第一步:对确认号不在[rcv_ack_seq-1,send_seq+1]之间的确认报文的处理,也就是下图阴影部分:

它包括两种情况
1.对还未发送报文的确认报文
2.对已经确认报文的确认
对于第1种情况的处理是直接返回0;对于第2种情况,如果不是TCP_ESTABLISHEDTCP_CLOSE_WAIT状态,直接返回0. 如果是其中一种状态,执行1891-1894行,其中1892行重设定时器(如果连接没有关闭),1893行设置重传标记为0.最后返回1。


到这里说明接收到的是[rcv_ack_seq-1,send_seq+1]之间的确认报文,即下图阴影部分:
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值