socket 源码解析之创建

本文详细剖析了socket的创建过程,包括数据结构如socket、sock、inet_sock及其相互关系,以及socket的创建步骤,如通过sock_create()分配并初始化socket结构,使用协议族的函数表进行初始化,分配并初始化sock结构。文中还探讨了socket如何与文件系统关联,以及bind()函数的工作原理,展示了从socket()到bind()的完整流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

数据结构 

/**
 *  struct socket - general BSD socket
 *  @state: socket state (%SS_CONNECTED, etc)
 *  @flags: socket flags (%SOCK_ASYNC_NOSPACE, etc)
 *  @ops: protocol specific socket operations
 *  @fasync_list: Asynchronous wake up list
 *  @file: File back pointer for gc
 *  @sk: internal networking protocol agnostic socket representation
 *  @wait: wait queue for several uses
 *  @type: socket type (%SOCK_STREAM, etc)
 */
struct socket {
	socket_state		state;	//socket 的状态
	unsigned long		flags;	//socket 的标志位
	const struct proto_ops	*ops; //socket 的函数操作表
	struct fasync_struct	*fasync_list;	//socket 的异步唤醒队列
	struct file		*file;	// 与socket关联的文件指针
	struct sock		*sk;	// 代表具体协议内容的 sock 结构指针
	wait_queue_head_t	wait;	// 等待队列
	short			type;	//socket 的类型
};

从 socket 结构体可以看出 socket 是通用的套接字结构体的公共部分,而其中的 sock 结构体则是与使用的具体协议相关的部分,可以理解成从 socket 中抽象出 sock 部分,sock 结构体是根据使用的协议挂入到 socket 中,下面了解下 sock 结构体。

struct sock {
	/*
	 * Now struct inet_timewait_sock also uses sock_common, so please just
	 * don't add nothing before this first member (__sk_common) --acme
	 */
	struct sock_common	__sk_common;	// 与 inet_timewait_sock 共享使用
#define sk_family		__sk_common.skc_family	// 地址族
#define sk_state		__sk_common.skc_state	// 连接状态
#define sk_reuse		__sk_common.skc_reuse	// 确定复用地址
#define sk_bound_dev_if		__sk_common.skc_bound_dev_if	//绑定设备 ID
#define sk_node			__sk_common.skc_node	// 链入主哈希表
#define sk_bind_node		__sk_common.skc_bind_node	// 链入绑定哈希表
#define sk_refcnt		__sk_common.skc_refcnt	// 使用计数
#define sk_hash			__sk_common.skc_hash	// 哈希值
#define sk_prot			__sk_common.skc_prot	// 协议函数表
#define sk_net			__sk_common.skc_net	// 所属的网络空间
	unsigned char		sk_shutdown : 2,	// 是否关闭,mask of %SEND_SHUTDOWN and/or %RCV_SHUTDOWN
				sk_no_check : 2,	// 是否检查数据包
				sk_userlocks : 4;	// 用户锁,%SO_SNDBUF and %SO_RCVBUF settings
	unsigned char		sk_protocol; // 使用协议族的哪一种协议
	unsigned short		sk_type;	// socket 的类型,例如 SOCK_STREAM 等
	int			sk_rcvbuf;	// 接受缓冲区的长度(字节数)
	socket_lock_t		sk_lock;	// 用于同步
	/*
	 * The backlog queue is special, it is always used with
	 * the per-socket spinlock held and requires low latency
	 * access. Therefore we special case it's implementation.
	 */
	struct {
		struct sk_buff *head;	// 记录最先接收到的数据包
		struct sk_buff *tail;	// 记录最后接收到的数据包
	} sk_backlog; // 后备队列
	wait_queue_head_t	*sk_sleep;	//sock 的等待队列
	struct dst_entry	*sk_dst_cache;	// 路由项缓存
	struct xfrm_policy	*sk_policy[2];	//流策略
	rwlock_t		sk_dst_lock;	// 路由项缓存锁
	atomic_t		sk_rmem_alloc;	// 接受队列的字节数
	atomic_t		sk_wmem_alloc;	// 发送队列的字节数
	atomic_t		sk_omem_alloc;	// 可选择/其他 的字节数
	int			sk_sndbuf;	// 发送缓存的总长度
	struct sk_buff_head	sk_receive_queue;	//接收队列(接收到的数据包队列)
	struct sk_buff_head	sk_write_queue;		//发送队列(正在发送的数据包队列)
	struct sk_buff_head	sk_async_wait_queue;	//DMA 复制的数据包 TODO
	int			sk_wmem_queued;	//全部数据包占用内存计数
	int			sk_forward_alloc;	//记录可用内存长度
	gfp_t			sk_allocation;	//分配模式
	int			sk_route_caps;	//路由的兼容性标志位
	int			sk_gso_type;	//GSO 通用分段类型 TODO
	unsigned int		sk_gso_max_size; //用于建立 GSO 通用分段的最大长度
	int			sk_rcvlowat;	//SO_RCVLOWAT 设置
	unsigned long 		sk_flags;	//SO_BROADCAST、SO_KEEPALIVE、SO_OOBINLINE、SO_LINGER 设置
	unsigned long	        sk_lingertime;	//停留时间,确定关闭时间
	struct sk_buff_head	sk_error_queue;	// 错误数据包队列
	struct proto		*sk_prot_creator;	//sock 创建接口
	rwlock_t		sk_callback_lock;	// 为后半部处理使用的锁
	int			sk_err,			//出错码
				sk_err_soft;	//持续出现的错误
	atomic_t		sk_drops;	//原始 socket 发送的计数器
	unsigned short		sk_ack_backlog;		//当前监听到的连接数量
	unsigned short		sk_max_ack_backlog;	//在 listen() 函数中监听到的连接数量
	__u32			sk_priority;	//优先级
	struct ucred		sk_peercred;	// SO_PEERCRED 设置
	long			sk_rcvtimeo;	// SO_RCVTIMEO 设置接受超时时间
	long			sk_sndtimeo;	// SO_SNDTIMEO 设置发送超时时间
	struct sk_filter      	*sk_filter;	//sock 的过滤器
	void			*sk_protinfo;	//私有区域,当不使用slab高速缓存时由协议族定义
	struct timer_list	sk_timer;	//sock 的冲刷定时器
	ktime_t			sk_stamp;		//最后接收数据包的时间
	struct socket		*sk_socket;	//对应的 socket 指针
	void			*sk_user_data;	//rpc 提供的数据
	struct page		*sk_sndmsg_page;	// 发送数据块所在的缓冲页
	struct sk_buff		*sk_send_head;	// 发送数据包的队列头
	__u32			sk_sndmsg_off;	//发送数据块在缓冲页的结尾
	int			sk_write_pending;	//等待发送的数量
	void			*sk_security;	//用于安全模式
	__u32			sk_mark;	//通用的数据包掩码
	/* XXX 4 bytes hole on 64 bit */
	void			(*sk_state_change)(struct sock *sk);			//sock 状态改变后调用的函数
	void			(*sk_data_ready)(struct sock *sk, int bytes);	//在数据被处理完成后调用的函数
	void			(*sk_write_space)(struct sock *sk);				//发送空间可以使用后调用的函数
	void			(*sk_error_report)(struct sock *sk);			//处理错误的函数
  	int			(*sk_backlog_rcv)(struct sock *sk,					//处理库存数据包函数
						  struct sk_buff *skb);  
	void                    (*sk_destruct)(struct sock *sk);		//sock 的销毁函数
};

与应用程序密切相关的共用部分放在了socket结构中,而与协议相关的内容则放在sock结构中,然后使socket与sock挂钩,设计灵活巧妙。

我们看到sock中数据包的结构通过sk_buff来体现,每个协议都是通过sk_buff结构体用于封装、载运数据包,我们可以看下其数据结构。

struct sk_buff {
	/* These two members must be first. */
	struct sk_buff		*next;	//队列中的下一个数据包
	struct sk_buff		*prev;	//队列中的前一个数据包

	struct sock		*sk;	//指向所属的 sock 数据包
	ktime_t			tstamp;	//数据包到达的时间
	struct net_device	*dev;	//接收数据包的网络设备

	union {
		struct  dst_entry	*dst;	//路由项
		struct  rtable		*rtable;	//路由表
	};
	struct	sec_path	*sp;	//用于 xfrm 的安全路径

	/*
	 * This is the control buffer. It is free to use for every
	 * layer. Please put your private variables there. If you
	 * want to keep them across layers you have to do a skb_clone()
	 * first. This is owned by whoever has the skb queued ATM.
	 */
	char			cb[48];	// cb 控制块

	unsigned int		len,	//全部数据块的总长度
				data_len;		//分段、分散数据块的总长度
	__u16			mac_len,	//链路层头部的长度
				hdr_len;		//在克隆数据包时可写的头部长度
	union {
		__wsum		csum;		//校验和
		struct {
			__u16	csum_start;	//校验和在数据包头部 skb->head 中的起始位置
			__u16	csum_offset;//校验和保存到 csum_start 中的位置
		};
	};
	__u32			priority;	//数据包在队列中的优先级
	__u8			local_df:1,	//是否允许本地数据分段
				cloned:1,		//是否允许被克隆
				ip_summed:2,	//IP校验和标志
				nohdr:1,		//运载时使用,表示不能被修改头部
				nfctinfo:3;		//数据包连接关系
	__u8			pkt_type:3,	//数据包的类型
				fclone:2,		//数据包克隆关系
				ipvs_property:1,//数据包所属的 ipvs
				peeked:1,		//数据包是否属于操作状态
				nf_trace:1;		//netfilter 对数据包的跟踪标志
	__be16			protocol;	//底层驱动使用的数据包协议

	void			(*destructor)(struct sk_buff *skb);					//销毁数据包的函数
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
	struct nf_conntrack	*nfct;
	struct sk_buff		*nfct_reasm;
#endif
#ifdef CONFIG_BRIDGE_NETFILTER
	struct nf_bridge_info	*nf_bridge;	//关于网桥的数据
#endif

	int			iif;
#ifdef CONFIG_NETDEVICES_MULTIQUEUE
	__u16			queue_mapping;
#endif
#ifdef CONFIG_NET_SCHED
	__u16			tc_index;	/* traffic control index */
#ifdef CONFIG_NET_CLS_ACT
	__u16			tc_verd;	/* traffic control verdict */
#endif
#endif
#ifdef CONFIG_IPV6_NDISC_NODETYPE
	__u8			ndisc_nodetype:2;
#endif
	/* 14 bit hole */

#ifdef CONFIG_NET_DMA
	dma_cookie_t		dma_cookie;
#endif
#ifdef CONFIG_NETWORK_SECMARK
	__u32			secmark;
#endif

	__u32			mark;

	sk_buff_data_t		transport_header;	//指向数据块中传输层头部
	sk_buff_data_t		network_header;		//指向数据块中网络层头部
	sk_buff_data_t		mac_header;			//指向数据块中链路层头部
	/* These elements must be at the end, see alloc_skb() for details.  */
	sk_buff_data_t		tail;	//指向数据块的结束地址
	sk_buff_data_t		end;	//指向缓冲块的结束地址
	unsigned char		*head,	//指向缓冲块的开始地址
				*data;			//指向数据块的开始地址
	unsigned int		truesize;	//数据包的实际长度(结构长度与数据块长度之和)
	atomic_t		users;	//数据包的使用计数器
};

共用部分 socket 结构体、通用部分 sock 结构体、专用部分 inet_sock 结构体。

tcp_sock 内容与 tcp 协议紧密相关,我们看其内容

struct tcp_sock {
	/* inet_connection_sock has to be the first member of tcp_sock */
	struct inet_connection_sock	inet_conn;	//由注释看到该结构体必须在 tcp_sock 头部 TODO why?
	u16	tcp_header_len;	/* Bytes of tcp header to send	发送的 tcp 头部字节数	*/
	u16	xmit_size_goal;	/* Goal for segmenting output packets 分段传送的数据包数量 	*/

/*
 *	Header prediction flags 头部的预置位
 *	0x5?10 << 16 + snd_wnd in net byte order
 */
	__be32	pred_flags;

/*
 *	RFC793 variables by their proper names. This means you can
 *	read the code and the spec side by side (and laugh ...)
 *	See RFC793 and RFC1122. The RFC writes these in capitals.
 */
 	u32	rcv_nxt;	/* What we want to receive next 下一个要接收的目标	*/
	u32	copied_seq;	/* Head of yet unread data	代表还没有读取的数据	*/
	u32	rcv_wup;	/* rcv_nxt on last window update sent rcv_nxt 在最后一次窗口更新时内容	*/
 	u32	snd_nxt;	/* Next sequence we send	下一个要发送的目标	*/

 	u32	snd_una;	/* First byte we want an ack for 第一个要 ack 的字节	*/
 	u32	snd_sml;	/* Last byte of the most recently transmitted small packet 最近发送数据包中的尾字节 */
	u32	rcv_tstamp;	/* timestamp of last received ACK (for keepalives) 最后一次接收到 ack 的时间 */
	u32	lsndtime;	/* timestamp of last sent data packet (for restart window) 最后一次发送数据包的时间 */

	/* Data for direct copy to user 直接复制给用户的数据 */
	struct {
		struct sk_buff_head	prequeue;			//预处理队列
		struct task_struct	*task;				//预处理进程
		struct iovec		*iov;				//用户程序(应用程序)接收数据的缓冲区
		int			memory;						//预处理数据包计数器
		int			len;						//预处理长度
#ifdef CONFIG_NET_DMA
		/* members for async copy 异步复制的内容 */
		struct dma_chan		*dma_chan;
		int			wakeup;
		struct dma_pinned_list	*pinned_list;
		dma_cookie_t		dma_cookie;
#endif
	} ucopy;

	u32	snd_wl1;	/* Sequence for window update  窗口更新的顺序		*/
	u32	snd_wnd;	/* The window we expect to receive 期望接收的窗口	*/
	u32	max_window;	/* Maximal window ever seen from peer 从对方获得的最大窗口	*/
	u32	mss_cache;	/* Cached effective mss, not including SACKS 有效的 mss,不包括 SACKS TODO mss、SACKS */

	u32	window_clamp;	/* Maximal window to advertise	对外公布的最大窗口	*/
	u32	rcv_ssthresh;	/* Current window clamp		当前窗口	*/

	u32	frto_highmark;	/* snd_nxt when RTO occurred 在 rto 时的 snd_nxt */
	u8	reordering;	/* Packet reordering metric.	预设的数据包数量	*/
	u8	frto_counter;	/* Number of new acks after RTO  rto 后的 ack 次数 */
	u8	nonagle;	/* Disable Nagle algorithm?      是否使用 Nagle 算法 TODO Nagle    */
	u8	keepalive_probes; /* num of allowed keep alive probes 允许持有的数量	*/

/* RTT measurement */
	u32	srtt;		/* smoothed round trip time << 3 	*/
	u32	mdev;		/* medium deviation			*/
	u32	mdev_max;	/* maximal mdev for the last rtt period	*/
	u32	rttvar;		/* smoothed mdev_max			*/
	u32	rtt_seq;	/* sequence number to update rttvar	*/

	u32	packets_out;	/* Packets which are "in flight" 处于飞行中的数据包数量	*/
	u32	retrans_out;	/* Retransmitted packets out	转发的数据包数量	*/
/*
 *      Options received (usually on last packet, some only on SYN packets).
 */
	struct tcp_options_received rx_opt;

/*
 *	Slow start and congestion control (see also Nagle, and Karn & Partridge) TODO 慢启动与阻塞控制
 */
 	u32	snd_ssthresh;	/* Slow start size threshold	慢启动的起点值	*/
 	u32	snd_cwnd;	/* Sending congestion window	发送的阻塞窗口	*/
	u32	snd_cwnd_cnt;	/* Linear increase counter	线性计数器	*/
	u32	snd_cwnd_clamp; /* Do not allow snd_cwnd to grow above this 不允许 snd_cwnd 超过的值 */
	u32	snd_cwnd_used;
	u32	snd_cwnd_stamp;

	struct sk_buff_head	out_of_order_queue; /* Out of order segments go here 超出分段规则的队列 */

 	u32	rcv_wnd;	/* Current receiver window	当前接收窗口	*/
	u32	write_seq;	/* Tail(+1) of data held in tcp send buffer tcp 发送数据的顺序号 */
	u32	pushed_seq;	/* Last pushed seq, required to talk to windows 最后送出的顺序号,需要通知窗口 */

/*	SACKs data	*/
	struct tcp_sack_block duplicate_sack[1]; /* D-SACK block */
	struct tcp_sack_block selective_acks[4]; /* The SACKS themselves*/

	struct tcp_sack_block recv_sack_cache[4];

	struct sk_buff *highest_sack;   /* highest skb with SACK received
					 * (validity guaranteed only if
					 * sacked_out > 0)
					 */

	/* from STCP, retrans queue hinting */
	struct sk_buff* lost_skb_hint;

	struct sk_buff *scoreboard_skb_hint;
	struct sk_buff *retransmit_skb_hint;
	struct sk_buff *forward_skb_hint;

	int     lost_cnt_hint;
	int     retransmit_cnt_hint;

	u32	lost_retrans_low;	/* Sent seq after any rxmit (lowest) */

	u16	advmss;		/* Advertised MSS			*/
	u32	prior_ssthresh; /* ssthresh saved at recovery start	*/
	u32	lost_out;	/* Lost packets			*/
	u32	sacked_out;	/* SACK'd packets			*/
	u32	fackets_out;	/* FACK'd packets			*/
	u32	high_seq;	/* snd_nxt at onset of congestion	*/

	u32	retrans_stamp;	/* Timestamp of the last retransmit,
				 * also used in SYN-SENT to remember stamp of
				 * the first SYN. */
	u32	undo_marker;	/* tracking retrans started here. */
	int	undo_retrans;	/* number of undoable retransmissions. */
	u32	urg_seq;	/* Seq of received urgent pointer */
	u16	urg_data;	/* Saved octet of OOB data and control flags */
	u8	urg_mode;	/* In urgent mode		*/
	u8	ecn_flags;	/* ECN status bits.			*/
	u32	snd_up;		/* Urgent pointer		*/

	u32	total_retrans;	/* Total retransmits for entire connection */
	u32	bytes_acked;	/* Appropriate Byte Counting - RFC3465 */

	unsigned int		keepalive_time;	  /* time before keep alive takes place */
	unsigned int		keepalive_intvl;  /* time interval between keep alive probes */
	int			linger2;

	unsigned long last_synq_overflow; 

	u32	tso_deferred;

/* Receiver side RTT estimation */
	struct {
		u32	rtt;
		u32	seq;
		u32	time;
	} rcv_rtt_est;

/* Receiver queue space 接受队列空间 */
	struct {
		int	space;
		u32	seq;
		u32	time;
	} rcvq_space;

/* TCP-specific MTU probe information. TCP 指定的 MTU 检验内容 */
	struct {
		u32		  probe_seq_start;
		u32		  probe_seq_end;
	} mtu_probe;

#ifdef CONFIG_TCP_MD5SIG
/* TCP AF-Specific parts; only used by MD5 Signature support so far */
	struct tcp_sock_af_ops	*af_specific;

/* TCP MD5 Signagure Option information */
	struct tcp_md5sig_info	*md5sig_info;
#endif
};

demo

了解了一些数据结构之后,下面将正式开始介绍 socket 相关的源码。

我们先来看下正常的服务器使用的流程


int main()
{
    struct sockaddr_in server_address;
    struct sockaddr_in client_address;

    server_fd = socket(AF_INET,SOCK_STREAM,0);
 
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = inet_addr("192.168.1.1");
    server_address.sin_port = htons(54188);
    server_len = sizeof(server_address);
 
    bind(server_fd,(struct sockaddr*)&server_address,server_len);
    
    /*创建一个Socket的监听队列(允许接收10个连接),监听客户端Socket的连接请求*/
    listen(server_fd,10);
    
    while(1) {
        char recv[20];
        printf("server is waiting\n");
        /*程序运行到此处时,说明客户端的连接请求已经到来,接受它的连接请求,克隆出一个Socket与客户端建立连接,并将客户端的“电话号码”记录在client_address中,函数返回建立连接的ID号*/
        client_len = sizeof(client_address);
        client_fd = accept(server_fd,(struct sockaddr*)&client_address,&client_len);
        /*使用read和write函数接收客户端字符然后发回客户端*/
    
        read(client_fd,recv,20);
    
        write(client_fd,back,20);
        printf("received from client= %s\n",recv);
    
        close(client_fd);
    }
    close(server_fd);
    exit(0);
}

无非先是 socket() 创建服务器socket ,然后bind() 将地址结构与 socket 挂钩起来,于是 listen()监听客户端的连接请求,然后通过accept()然后得到fd,根据vfs即访问文件的方式访问套接字,read/write。

socket 的创建

服务器调用socket()函数,其调用的库函数在glibc源码中找到

#include <errno.h>
#include <sys/socket.h>

/* Create a new socket of type TYPE in domain DOMAIN, using
   protocol PROTOCOL.  If PROTOCOL is zero, one is chosen automatically.
   Returns a file descriptor for the new socket, or -1 for errors.  */
int
__socket (domain, type, protocol)
     int domain;
     int type;
     int protocol;
{
  __set_errno (ENOSYS);
  return -1;
}


weak_alias (__socket, socket)
stub_warning (socket)
#include <stub-tag.h>

这里看到使用 weak_alias() 函数为 socket() 函数声明了一个“函数别名”_socket(

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值