zoukankan      html  css  js  c++  java
  • TCP/IP协议栈(三)——linux 向下的报文处理

      应用程序连接服务器时,目的地套接字地址(端口号和IP地址)以参数形式传递给系统调用connect(tcp_v4_connect())。下面逐步介绍初始化该连接

    1. 检查内核路由表,查找给定目的地IP地址路由表。该路由包含传出设备信息,如果没有传出设备,则初始化APR特定信息,并缓冲以便后用。若没有找到则返回错误。
      /**
       * 特殊的路由查找函数,用于TCP。
       * 是对普通路由缓存查找函数的封装。
       */
      static inline int ip_route_connect(struct rtable **rp, u32 dst,
                         u32 src, u32 tos, int oif, u8 protocol,
                         u16 sport, u16 dport, struct sock *sk)
      {
          struct flowi fl = { .oif = oif,
                      .nl_u = { .ip4_u = { .daddr = dst,
                               .saddr = src,
                               .tos   = tos } },
                      .proto = protocol,
                      .uli_u = { .ports =
                             { .sport = sport,
                           .dport = dport } } };
      
          int err;
          if (!dst || !src) {
              err = __ip_route_output_key(rp, &fl);
              if (err)
                  return err;
              fl.fl4_dst = (*rp)->rt_dst;
              fl.fl4_src = (*rp)->rt_src;
              ip_rt_put(*rp);
              *rp = NULL;
          }
          return ip_route_output_flow(rp, &fl, sk, 0);
      }
      查找路由
    2. 为 SYN报文分配一个报文(sk_buff),构建tcp头,完成后将其发往IP层。
      * 构造并发送SYN段 */
      int tcp_connect(struct sock *sk)
      {
          struct tcp_sock *tp = tcp_sk(sk);
          struct sk_buff *buff;
      
          tcp_connect_init(sk);/* 初始化传输控制块中与连接相关的成员 */
      
          /* 为SYN段分配报文并进行初始化 */
          buff = alloc_skb(MAX_TCP_HEADER + 15, sk->sk_allocation);
          if (unlikely(buff == NULL))
              return -ENOBUFS;
      
          /* Reserve space for headers. */
          skb_reserve(buff, MAX_TCP_HEADER);
      
          TCP_SKB_CB(buff)->flags = TCPCB_FLAG_SYN;
          TCP_ECN_send_syn(sk, tp, buff);
          TCP_SKB_CB(buff)->sacked = 0;
          skb_shinfo(buff)->tso_segs = 1;
          skb_shinfo(buff)->tso_size = 0;
          buff->csum = 0;
          TCP_SKB_CB(buff)->seq = tp->write_seq++;
          TCP_SKB_CB(buff)->end_seq = tp->write_seq;
          tp->snd_nxt = tp->write_seq;
          tp->pushed_seq = tp->write_seq;
          tcp_ca_init(tp);
      
          /* Send it off. */
          TCP_SKB_CB(buff)->when = tcp_time_stamp;
          tp->retrans_stamp = TCP_SKB_CB(buff)->when;
      
          /* 将报文添加到发送队列上 */
          __skb_queue_tail(&sk->sk_write_queue, buff);
          sk_charge_skb(sk, buff);
          tp->packets_out += tcp_skb_pcount(buff);
          /* 发送SYN段 */
          tcp_transmit_skb(sk, skb_clone(buff, GFP_KERNEL));
          TCP_INC_STATS(TCP_MIB_ACTIVEOPENS);
      
          /* Timer for repeating the SYN until an answer. */
          /* 启动重传定时器 */
          tcp_reset_xmit_timer(sk, TCP_TIME_RETRANS, tp->rto);
          return 0;
      }
      构建并发送SYN段代码
    3. IP层首先检查传出报文的缓冲路由是否有效,若无效再次尝试获得有效路由。发生这种情况是因为路由从第一次发现目的路由时就已经改变,了能会产生路由失效。
    4. 若路由缓存有效,则构建报文的IP头
    5. 检查防火墙策略,查看其是否允许该报文发出。如果允许发出,则在计算IP校验和并将其放在IP头的checksum域。发送到链路层。
      /**
       * TCP和SCTP发送包所用的函数。
       * 此函数只接收两个输入参数,所有处理包所需要的信息都可以通过skb直接或者间接的存取。
       *        Skb:        要传输的包的缓冲区描述符。此数据结构有填入IP报头以及传输包所需要的所有参数(如下一跳网关)。记住,ip_queue_xmit用于处理本地产生的包。转发包没有相关的套接字。
       *        Ipfragok:    主要由SCTP使用的标志,用来指出是否允许分段。
       */
      int ip_queue_xmit(struct sk_buff *skb, int ipfragok)
      View Code
    6. 如果前面全部通过,报文得到一个最终的有效传出设备。接着构建链路层头,只有当目的地IP有一个硬件地址时,才能构建链路层头。如果不知道目的地硬件地址,就需要发送APR请求。从APR确认中得到目的地IP硬件地址。
    7. 将该报文放入设备队列做最终传输。
    8. 从设备队列取出报文并检查是否可以从设备传输报文,若可以,则当前帧利用设备DMA来发送报文否则重新将报文放入设备队列,使设备到CPU排队,并向CPU发出TxIRQ。CPU处理Tx softIRQ,取出报文并发送报文。
    9. 报文成功传输后产生Tx中断,成功发送的报文将从Tx中断释放。                     

      前面阐述啦首次连接设置的处理过程,在此过程缓存了一些重要信息,如路由、设备、APR等。下面将介绍TCP套接着写数据。

    • 查找与套接字描述符相对应的套接字,利用Inode和私有数据找到该套接字。
    • 向已连接的套接字写数据。TCP将数据复制到上一个报文(sk_buff没有满),或者创建一个新的报文(sk_buff)
    • 写完成后查询tcp状态机,检查是否可以立即发送数据。如果可以,接着就构建tcp头,将其发往ip层。否则,将报文放到tcp发送缓冲区结尾排队。
    • 若在结尾排队,检查是否可以发送首报文。若可以,则构建tcp头发往IP层。同时初始化TCP重传定时器。

      报文从套接字到达设备要经历三个阶段:

    1. 套接字发送队列sk->write_queue。
    2. 设备队列dev->q。
    3. 网络控制器的DMA Tx环缓冲区。
  • 相关阅读:
    常用集体名词的用法
    囊中羞涩的表达
    《当幸福来敲门》观后感
    <肖申克的救赎>观后感
    心语4
    补充:回答网友的问题,如何不用路径,而直接将CImage画到DC中,之后DC一起显示.
    线程中对变量的用法
    添加按键变量数组,就是很多同种类型按键关联变量,这些变量是一个数组;
    不容按钮、下拉框 执行同一个函数或者同一种函数的用法
    CImage显示位图与CDC双缓冲冲突,使用路径层解决.
  • 原文地址:https://www.cnblogs.com/yuhanghzsd/p/6358735.html
Copyright © 2011-2022 走看看