网络协议栈3:sock结构体
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结构体作为数据的携带者,传送给下层的传输层,网络层,链路层,链路层