网络协议栈3:sock结构体

时间:2021-12-04 16:21:20

sock结构体是我们在网络编程中遇到的第一个庞大的结构体

 

struct sock {

  struct options      *opt;/*IP选项缓存于此处*/

  volatile unsigned long   wmem_alloc;/*当前写缓冲区大小,该值不可大于系统规定的最大值*/

  volatile unsigned long   rmem_alloc;/*当前读缓冲区大小,该值不可大于系统规定最大值*/

  unsigned long                 write_seq;/* write_seq 表示应用程序下一次写数据时所对应的第一个字节的序列号*/

  unsigned long                 sent_seq;/* sent_seq 表示本地将要发送的下一个数据包中第一个字节对应的序列号*/

  unsigned long                 acked_seq;/* acked_seq 表示本地希望从远端接收的下一个数据的序列号*/

   unsigned long                 copied_seq; /*  应用程序有待读取(但尚未读取)数据的第一个序列号。*/

  unsigned long                 rcv_ack_seq; /*  表示目前本地接收到的对本地发送数据的应答序列号。*/

   unsigned long                 window_seq;/* 窗口大小,是一个绝对值,表示本地将要发送数据包中所包含最后一个数据的序列号,不可大于 window_seq.*/

  unsigned long                 fin_seq; /*  该字段在对方发送 FIN数据包时使用,在接收到远端发送的 FIN数据包后,fin_seq 被初始化为对方的 FIN 数据包最后一个字节的序列号加 1,表示本地对此 FIN 数据包进行应答的序列号*/

   unsigned long                 urg_seq;

  unsigned long                 urg_data;

/*  以上两个字段用于紧急数据处理,urg_seq 表示紧急数据最大序列号。urg_data 是一个标志位,当设置为 1 时,表示接收到紧急数据。*/

  volatile char                 inuse,/*inuse=1 表示其它进程正在使用该 sock 结构,本进程需等待*/

                      dead,/* dead=1 表示该 sock 结构已处于释放状态*/

                      urginline,/* urginline=1 表示紧急数据将被当作普通数据处理。*/

                      intr,

                      blog,/* blog=1 表示对应套接字处于节制状态,此时接收的数据包均被丢弃*/

                      done,

                      reuse,

                      keepopen,/* keepopen=1 表示使用保活定时器 */

                      linger,/* linger=1 表示在关闭套接字时需要等待一段时间以确认其已关闭。*/

                      delay_acks,/* delay_acks=1表示延迟应答,可一次对多个数据包进行应答 */

                      destroy,/* destroy=1 表示该 sock 结构等待销毁*/

                      ack_timed,

                      no_check,

                      zapped,   /* In ax25 & ipx means not linked */

                      broadcast,

                      nonagle;/* noagle=1 表示不使用 NAGLE 算法*/

  unsigned long                   lingertime;/*表示等待关闭操作的时间,只有当 linger 标志位为 1 时,该字段才有意义。*/

  int                    proc;/* 该 sock 结构(即该套接字)所属的进程的进程号。*/

  struct sock                *next;

  struct sock                *prev;

  struct sock                *pair;

/* 以上三个字段用于 sock 的连接*/

 

  struct sk_buff      * volatile send_head;

  struct sk_buff      * volatile send_tail;

/* send_head, send_tail 用于 TCP协议重发队列。*/

 

  struct sk_buff_head       back_log;/* back_log为接收的数据包缓存队列。用于计算目前累计的应发送而未发送的应答数据包的个数*/

  struct sk_buff      *partial;/*创建最大长度的待发送数据包。*/

  struct timer_list         partial_timer;/*按时发送 partial 指针指向的数据包,以免缓存(等待)时间过长。*/

  long                      retransmits;/* 重发次数*/

/*

write_queue 指向待发送数据包,其与 send_head,send_tail 队列的不同之处在于send_head,send_tail 队列中数据包均已经发送出去,但尚未接收到应答。而 write_queue 中数据包尚未发送。 receive-queue为读队列,其不同于 back_log 队列之处在于 back_log 队列缓存从网络层传 上来的数据包,在用户进行读取操作时,不可操作 back_log 队列,而是从 receive_queue 队列中去数据包读取其中的数据,即数据包首先缓存在 back_log 队列中,然后从 back_log 队列中移动到 receive_queue队列中方可被应用程序读取。而并非所有back_log 队列中缓 存的数据包都可以成功的被移动到 receive_queue队列中,如果此刻读缓存区太小,则当 前从back_log 队列中被取下的被处理的数据包将被直接丢弃,而不会被缓存到receive_queue 队列中。如果从应答的角度看,在back_log队列中的数据包由于有可能被 丢弃,故尚未应答,而将一个数据包从 back_log 移动到 receive_queue时,表示该数据包 已被正式接收,即会发送对该数据包的应答给远端表示本地已经成功接收该数据包。 */

  struct sk_buff_head       write_queue,

                      receive_queue;

 

  struct proto               *prot;/*指向传输层处理函数集*/

  struct wait_queue           **sleep;/*进程等待sock的地位*/

  unsigned long                 daddr;/*套接字的远端地址*/

  unsigned long                 saddr;/*套接字的本地地址*/

  unsigned short          max_unacked;/* 最大未处理请求连接数(应答数) */

  unsigned short          window;/* 远端窗口大小 */

  unsigned short          bytes_rcv;/* 已接收字节总数*/

/* mss is min(mtu, max_window) */

  unsigned short          mtu;       /*最大传输单元*/

  volatile unsigned short  mss;       /*最大报文长度:MSS=MTU-IP 首部长度-TCP首部长度 */

  volatile unsigned short  user_mss;  /*用户指定的 MSS值*/

 

  volatile unsigned short  max_window;

  unsigned long          window_clamp;/*最大窗口大小和窗口大小钳制值 */

 

  unsigned short          num;/* 本地端口号*/

/*

以下三个字段用于拥塞算法

*/  

  volatile unsigned short  cong_window;

  volatile unsigned short  cong_count;

  volatile unsigned short  ssthresh;

  volatile unsigned short  packets_out;/* 本地已发送出去但尚未得到应答的数据包数目*/

  volatile unsigned short  shutdown;/* 本地关闭标志位,用于半关闭操作*/

  volatile unsigned long   rtt;/* 往返时间估计值*/

  volatile unsigned long   mdev;/* mean deviation, 即RTTD,  绝对偏差*/

  volatile unsigned long   rto;/* RTO是用 RTT 和 mdev 用算法计算出的延迟时间值*/

  volatile unsigned short  backoff;/* 退避算法度量值 */

  volatile short        err;/* 错误标志值*/

  unsigned char                 protocol;/* 传输层协议值*/

  volatile unsigned char   state;/* 套接字状态值,如 TCP_ESTABLISHED */

  volatile unsigned char   ack_backlog;/* 缓存的未应答数据包个数*/

  unsigned char                 max_ack_backlog;/* 最大缓存的未应答数据包个数*/

  unsigned char                 priority;/* 该套接字优先级,在硬件缓存发送数据包时使用 */

  unsigned char                 debug;

  unsigned short          rcvbuf;/* 最大接收缓冲区大小*/

  unsigned short          sndbuf;/* 最大发送缓冲区大小*/

  unsigned short          type;/* 类型值如 SOCK_STREAM */

  unsigned char                 localroute;    /* localroute=1 表示只使用本地路由,一般目的端在相同子网时使用。*/

#ifdef CONFIG_IPX

  ipx_address               ipx_dest_addr;

  ipx_interface             *ipx_intrfc;

  unsigned short          ipx_port;

  unsigned short          ipx_type;

#endif

#ifdef CONFIG_AX25

  ax25_address            ax25_source_addr,ax25_dest_addr;

  struct sk_buff *volatile   ax25_retxq[8];

  char                      ax25_state,ax25_vs,ax25_vr,ax25_lastrxnr,ax25_lasttxnr;

  char                      ax25_condition;

  char                      ax25_retxcnt;

  char                      ax25_xx;

  char                      ax25_retxqi;

  char                      ax25_rrtimer;

  char                      ax25_timer;

  unsigned char                 ax25_n2;

  unsigned short          ax25_t1,ax25_t2,ax25_t3;

  ax25_digi              *ax25_digipeat;

#endif 

#ifdef CONFIG_ATALK

  struct atalk_sock      at;

#endif

 

/* IP 'private area' or will be eventually */

  int                    ip_ttl;       /* IP首部 TTL 字段值,实际上表示路由器跳数*/

  int                    ip_tos;           /* IP首部 TOS字段值,服务类型值*/

  struct tcphdr             dummy_th;/* 缓存的 TCP首部,在 TCP协议中创建一个发送数据包时可以利用此字段快速创建 TCP 首部。*/

  struct timer_list         keepalive_timer;     /*保活定时器,用于探测对方窗口大小,防止对方通报窗口大小的数据包丢弃,从而造成 本地发送通道被阻塞。*/

  struct timer_list         retransmit_timer;    /*重发定时器,用于数据包超时重发*/

  struct timer_list         ack_timer;          /*延迟应答定时器,延迟应答可以减少应答数据包的个数,但不可无限延迟以免造成远端 重发,所以设置定时器定期发送应答数据包。 */

  int                    ip_xmit_timeout;     /*该字段为标志位组合字段,用于表示下文中 timer定时器超时的原因*/

#ifdef CONFIG_IP_MULTICAST 

  int                    ip_mc_ttl;                

  int                    ip_mc_loop;            

  char                      ip_mc_name[MAX_ADDR_LEN]; 

  struct ip_mc_socklist          *ip_mc_list;     

#endif 

/*以上4 个字段用于 IP多播*/

 

  int                    timeout;  

  struct timer_list         timer;

/* 以上两个字段用于通用定时,timeout 表示定时时间值,ip_xmit_timeout表示此次定时的 原因,timer为定时器。 */

  struct timeval       stamp;/* 时间戳*/

  struct socket             *socket;/*对应的socket结构体*/

 

  void                       (*state_change)(struct sock *sk);

  void                       (*data_ready)(struct sock *sk,int bytes);

  void                       (*write_space)(struct sock *sk);

  void                       (*error_report)(struct sock *sk);

/* 以上四个函数指针字段指向回调函数。这些字段的设置为自定义回调函数提供的很大的

灵活性,内核在发生某些时间时,会调用这些函数,如此可以实现自定义响应。目前这

种自定义响应还是完全有内核控制。 */  

};

在inet_create 函数中,这个结构体的成员基本上都被初始化了

 

static int inet_create(struct socket *sock, int protocol)
{

     ......


 switch(sock->type)
 {


    ......


  case SOCK_STREAM:
  case SOCK_SEQPACKET:
/*
在 socket 系统调用时,我们一般将 protocol 参数设置为 0。如果设置为非 0,
则对于不同的类型,必须赋予正确值,否则可能在此处处理时出现问题。
*/
   if (protocol && protocol != IPPROTO_TCP)
   {
    kfree_s((void *)sk, sizeof(*sk));
    return(-EPROTONOSUPPORT);
   }
   protocol = IPPROTO_TCP;
/*
TCP_NO_CHECK定义为 1,表示对于 TCP协议默认使用校验
*/ 
   sk->no_check = TCP_NO_CHECK;
/*
注意此处prot 变量被初始化为 tcp_prot,稍后 sock 结构的prot 字段将被初始
化为prot 变量值。
*/
   prot = &tcp_prot;
   break;


    ......

 

  }


    ......


 sk->socket = sock;/*建立与其对应的 socket结构之间的关系,socket结构先于 sock 结构建立。 */
#ifdef CONFIG_TCP_NAGLE_OFF
 sk->nonagle = 1;
#else   
 sk->nonagle = 0;
#endif 
 sk->type = sock->type;/*初始化 sock 结构 type 字段:套接字类型*/
 sk->stamp.tv_sec=0;
 sk->protocol = protocol;/*传输层协议*/
 sk->wmem_alloc = 0;
 sk->rmem_alloc = 0;
 sk->sndbuf = SK_WMEM_MAX;/*最大发送缓冲区大小*/
 sk->rcvbuf = SK_RMEM_MAX;/*最大接收缓冲区大小*/
 sk->pair = NULL;
 sk->opt = NULL;
 sk->write_seq = 0;
 sk->acked_seq = 0;
 sk->copied_seq = 0;
 sk->fin_seq = 0;
 sk->urg_seq = 0;
 sk->urg_data = 0;
 sk->proc = 0;
 sk->rtt = 0;    /*TCP_WRITE_TIME << 3;*/
 sk->rto = TCP_TIMEOUT_INIT;  /*TCP_WRITE_TIME*/
 sk->mdev = 0;
 sk->backoff = 0;
 sk->packets_out = 0;
/*
cong_window 设置为 1,即 TCP首先进入慢启动阶段。这是 TCP协议处理拥塞的 一种策略
*/
 sk->cong_window = 1; /* start with only sending one packet at a time. */
 sk->cong_count = 0;
 sk->ssthresh = 0;
 sk->max_window = 0;
 sk->urginline = 0;
 sk->intr = 0;
 sk->linger = 0;
 sk->destroy = 0;
 sk->priority = 1;
 sk->shutdown = 0;
 sk->keepopen = 0;
 sk->zapped = 0;
 sk->done = 0;
 sk->ack_backlog = 0;
 sk->window = 0;
 sk->bytes_rcv = 0;
 sk->state = TCP_CLOSE;/*由于尚未进行连接,状态设置为 CLOSE。*/
 sk->dead = 0;
 sk->ack_timed = 0;
 sk->partial = NULL;
 sk->user_mss = 0;
 sk->debug = 0;
/*
设置最大可暂缓应答的字节数
*/
 /* this is how many unacked bytes we will accept for this socket.  */
 sk->max_unacked = 2048; /* needs to be at most 2 full packets. */

 /* how many packets we should send before forcing an ack.
    if this is set to zero it is the same as sk->delay_acks = 0 */
 sk->max_ack_backlog = 0;
 sk->inuse = 0;
 sk->delay_acks = 0;
 skb_queue_head_init(&sk->write_queue);
 skb_queue_head_init(&sk->receive_queue);
 sk->mtu = 576;/*MTU设置为保守的576字节, 该大小在绝大多数连接中不会造成分片*/

 sk->prot = prot;
 sk->sleep = sock->wait;
 sk->daddr = 0;
 sk->saddr = 0 /* ip_my_addr() */;
 sk->err = 0;
 sk->next = NULL;
 sk->pair = NULL;
 sk->send_tail = NULL;
 sk->send_head = NULL;
 sk->timeout = 0;
 sk->broadcast = 0;
 sk->localroute = 0;
 init_timer(&sk->timer);
 init_timer(&sk->retransmit_timer);
 sk->timer.data = (unsigned long)sk;
 sk->timer.function = &net_timer;
 skb_queue_head_init(&sk->back_log);
 sk->blog = 0;
 sock->data =(void *) sk;
/*sock 结构之 dummy_th 字段是 tcphdr结构,该结构与 TCP首部各字段对应*/ 
 sk->dummy_th.doff = sizeof(sk->dummy_th)/4;
 sk->dummy_th.res1=0;
 sk->dummy_th.res2=0;
 sk->dummy_th.urg_ptr = 0;
 sk->dummy_th.fin = 0;
 sk->dummy_th.syn = 0;
 sk->dummy_th.rst = 0;
 sk->dummy_th.psh = 0;
 sk->dummy_th.ack = 0;
 sk->dummy_th.urg = 0;
 sk->dummy_th.dest = 0;
 sk->ip_tos=0;
 sk->ip_ttl=64;
#ifdef CONFIG_IP_MULTICAST
 sk->ip_mc_loop=1;
 sk->ip_mc_ttl=1;
 *sk->ip_mc_name=0;
 sk->ip_mc_list=NULL;
#endif
/*
对 sock 结构中几个回调函数字段的初始化
*/   
 sk->state_change = def_callback1;
 sk->data_ready = def_callback2;
 sk->write_space = def_callback3;
 sk->error_report = def_callback1;
/*
如果该套接字已经分配本地端口号,则对 sock 结构中 dummy_th 结构字段进行赋值
*/
 if (sk->num)
 {
 /*
  * It assumes that any protocol which allows
  * the user to assign a number at socket
  * creation time automatically
  * shares.
  */
  put_sock(sk->num, sk);
  sk->dummy_th.source = ntohs(sk->num);
 }


    ......


 return(0);
}

 

此后,数据的传送,都由sock结构体作为数据的携带者,传送给下层的传输层,网络层,链路层,链路层