Linux网络连接内核
1. 连接是一个数据结构
连接的本质是内核中的一种数据结构(即结构体)。Linux 系统内核,会用 C 语言设计多态结构,其中套接字编程就是如此设计的。
我们使用的 socket()
函数,在内核中有一个 struct socket
结构体管理连接的基本信息,其中 short type
字段,就是决定创建的是UDP还是TCP的。而 struct sock *sk
就是网络 socket 的接口,它所指向的就是UDP或TCP的结构体。
c
struct socket {
socket_state state; /* 当前 socket 的状态 */
short type; /* socket 类型(SOCK_STREAM, SOCK_DGRAM 等) */
unsigned long flags; /* socket 标志 */
struct file *file; /* 与 socket 相关的文件描述符 */
struct sock *sk; /* 指向内核中的 sock 结构体 */
const struct proto_ops *ops; /* socket 操作集合 */
};
在UDP和TCP的结构体中,它们都是由多种套接字结构体嵌套组成的,这种俄罗斯套娃式的结构体,就是c语言风格的多态结构。
以TCP为例,TCP的结构体是 struct tcp_sock
,它的最上边是 struct sock
, struct sock
又在 struct inet_sock
之中,struct inet_sock
又在 struct inet_connection_sock
之中。C 语言使用多态的方式,是使用一个指针类型,想要访问 TCP 里的 struct sock
就把指针强转成 struct *sock
类型,访问 struct inet_sock
就把指针强转成 struct *inet_sock
类型,以此类推。

struct sock:
c
struct sock {
/* 同步控制相关 */
spinlock_t sk_lock; /* 用于保护此 sock 的自旋锁 */
atomic_t sk_refcnt; /* 引用计数 */
/* 协议层状态 */
struct sock_common __sk_common;
struct proto *sk_prot; /* 指向协议相关的函数操作 */
void *sk_user_data; /* 用户数据 */
/* 发送和接收缓冲区 */
struct sk_buff_head sk_receive_queue; /* 接收队列 */
struct sk_buff_head sk_write_queue; /* 发送队列 */
/* 协议相关的状态变量 */
unsigned long sk_flags; /* socket 标志 */
unsigned int sk_rcvbuf; /* 接收缓冲区大小 */
unsigned int sk_sndbuf; /* 发送缓冲区大小 */
/* ... 还有很多其他字段 */
};
struct tcp_sock:
c
struct tcp_sock {
struct inet_sock inet; /* IPv4 socket */
u32 rcv_nxt; /* 下一个要接收的序列号 */
u32 snd_nxt; /* 下一个要发送的序列号 */
u32 snd_una; /* 已确认的第一个字节 */
u32 snd_wnd; /* 发送窗口 */
u32 rcv_wnd; /* 接收窗口 */
u32 srtt; /* 平滑的往返时间 */
u32 mdev; /* 往返时间的均方差 */
u32 retransmit_high; /* 重传的最高序列号 */
/* 拥塞控制相关 */
u32 snd_cwnd; /* 拥塞窗口 */
u32 snd_ssthresh; /* 慢启动阈值 */
/* ... 其他和 TCP 协议相关的字段 */
};
struct udp_sock:
c
struct udp_sock {
struct inet_sock inet; /* IPv4 协议相关的 socket */
__u16 len; /* UDP 数据长度 */
__u16 pending; /* 未决的数据包状态 */
/* 用于UDP-Lite传输的校验和,普通UDP协议不使用 */
__u8 encap_type; /* 封装类型 */
__u8 encap_enabled; /* 是否启用了封装 */
__u8 no_check6_tx; /* 不校验传出数据 */
__u8 no_check6_rx; /* 不校验传入数据 */
int (*encap_rcv)(struct sock *sk, struct sk_buff *skb);
int (*encap_err_rcv)(struct sock *sk, struct sk_buff *skb,
struct iphdr *iph, u32 info);
/* UDP-Lite特有的字段(在标准 UDP 协议下不使用) */
__u16 pcslen; /* 部分校验和长度 */
__u16 pcflag; /* 部分校验标志 */
/* ... 其他字段 */
};
2. 一切皆文件与套接字
Linux 具有一切皆文件的特点,在套接字编程中,Linux 能够将 sockfd
当成普通的文件描述符使用、关闭,但在 Windows 环境下,必须为 sockfd
额外写一个关闭文件描述符的函数。那么,Linux 是如何做到将连接当成文件看待的呢?
根据文件操作,进程的PCB结构体 task_struct
中有一个 *file
指针,指向 struct file_struct
结构体,这个结构体中,有一个 struct file *fd_array[]
记录着所有打开的文件描述符 fd
,它的元素指向对应打开的文件,也是由一个 struct file
结构体管理的,其中的 void *private_data
在套接字编程中,就是指向 struct socket
套接字结构体,这一串的联系,使得套接字能够像文件一样被管理,也是 Linux 具有一切皆文件的特点的原因。
