【Unix 网络编程】说说 socket 套接字

套接字描述符同文件描述符一样是一个整数类型的值,是通信端点的抽象。对于每个程序系统都有单独的表,精确地讲,系统为每个运行的进程维护一张单独的文件描述符表。当进程打开一个文件时,系统把一个指向此文件内部数据结构的指针写入文件描述符表,并把该表的索引值返回给调用者。为了将不同类型的 IO 与对应的文件描述符绑定,则需要不同的初始化函数,这相当于面对对象里面的多态。


socket 是 inode 结构中的一部分,在以前的内核版本中,把 inode 结构内部的一个 union 用作 socket 结构,新的内核中的 inode 节点中已经没有 u 这个联合体了,对应的是一个包含 socket 和 inode 的一个结构体。


普通文件就通过 open 函数,指定对应的文件路径,操作系统通过路径能够找到对应的文件系统类型,当应用程序读取该文件时,只需知道该文件描述符,然后操作系统把该描述符作为索引访问进程描述符表,通过指针找到保存该文件所有的信息的数据结构。对于网络,则通过 socket 函数来初始化,socket 函数就通过(domain, type, protocol)来找到对应的网络协议栈。套接字描述和文件描述符在本质上是一样的,秉承 Linux 一切皆文件的设计原则。


在系统调用时,和文件描述符一样,套接字描述符会被映射成一个表示 socket 的结构体,该结构体保存了该 socket 的所有属性和数据。

这里我们主要看看 socket 对象记录了哪些信息

//include/linux/net.h   Linux-2.6.32.61
struct socket {
	socket_state		state;

	kmemcheck_bitfield_begin(type);
	short			type;
	kmemcheck_bitfield_end(type);

	unsigned long		flags;
	struct fasync_struct	*fasync_list;
	wait_queue_head_t	wait;

	struct file		*file;
	struct sock		*sk;
	const struct proto_ops	*ops;
};
上面是 socket 结构体,我们主要关心里面的 struct sock 和 struct proto_ops


struct proto_ops 是协议相关的一组操作集,里面包含了几乎所有的协议操作函数,所以 socket 描述符可以发起各类操作函数

struct proto_ops {
	int		family;
	struct module	*owner;
	int(*release)   (struct socket *sock);
	int(*bind)	     (struct socket *sock, struct sockaddr *myaddr, int sockaddr_len);
	int(*connect)   (struct socket *sock, struct sockaddr *vaddr, int sockaddr_len, int flags);
	int(*socketpair)(struct socket *sock1, struct socket *sock2);
	int(*accept)    (struct socket *sock, struct socket *newsock, int flags);
	int(*getname)   (struct socket *sock, struct sockaddr *addr, int *sockaddr_len, int peer);
	unsigned int(*poll)	     (struct file *file, struct socket *sock, struct poll_table_struct *wait);
	int(*ioctl)     (struct socket *sock, unsigned int cmd, unsigned long arg);
	int(*compat_ioctl) (struct socket *sock, unsigned int cmd, unsigned long arg);
	int(*listen)    (struct socket *sock, int len);
	int(*shutdown)  (struct socket *sock, int flags);
	int(*setsockopt)(struct socket *sock, int level, int optname, char __user *optval, unsigned int optlen);
	int(*getsockopt)(struct socket *sock, int level, int optname, char __user *optval, int __user *optlen);
	int(*compat_setsockopt)(struct socket *sock, int level, int optname, char __user *optval, unsigned int optlen);
	int(*compat_getsockopt)(struct socket *sock, int level, int optname, char __user *optval, int __user *optlen);
	int(*sendmsg)   (struct kiocb *iocb, struct socket *sock, struct msghdr *m, size_t total_len);
	int(*recvmsg)   (struct kiocb *iocb, struct socket *sock, struct msghdr *m, size_t total_len, int flags);
	int(*mmap)	     (struct file *file, struct socket *sock, struct vm_area_struct * vma);
	ssize_t(*sendpage)  (struct socket *sock, struct page *page, int offset, size_t size, int flags);
	ssize_t(*splice_read)(struct socket *sock, loff_t *ppos, struct pipe_inode_info *pipe, size_t len, unsigned int flags);
};

struct sock 是根据使用的协议而挂入 socket ,每一种协议都有此结构变量,将 sock 从socket 中分离出来是因为 socket  是通用的套接字结构体,而 sock 与具体使用的协议相关,把与文件系统关系密切的放在 socket 结构中,把与通信关系密切的放在 sock 中。总而言之,公共的通用部分放在 socket 结构中,而通用部分的放在 sock 结构体中,还有专用部分则是具体协议族使用的结构,如 inet_sock 结构体。

sock 结构体定义内容相对庞大,这里只贴出局部,具体参见源码

//include/net/sock.h
struct sock {

	struct sock_common	__sk_common;     //与 inet_timewait_sock 共享使用
#define sk_node			__sk_common.skc_node
#define sk_nulls_node		__sk_common.skc_nulls_node
#define sk_refcnt		__sk_common.skc_refcnt

#define sk_copy_start		__sk_common.skc_hash
#define sk_hash			__sk_common.skc_hash
#define sk_family		__sk_common.skc_family
    ……
};

由上面可知,socket 与 sock 是互指的关系

还有专用的 socket 如 inet_sock 是INET域专用的一个socket,它是在 struct sock 的基础上进行的扩展,在基本 socket 的属性已具备的基础上,struct inet_sock 提供了INET域专有的一些属性。

struct inet_sock {
	/* sk and pinet6 has to be the first two members of inet_sock */
	struct sock		sk;          //sock 结构体
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
	struct ipv6_pinfo	*pinet6;
#endif
	/* Socket demultiplex comparisons on incoming packets. */
	__be32			daddr;         //IPv4的目的地址(远程地址)
	__be32			rcv_saddr;     //IPv4的本地接收地址
	__be16			dport;         //目的端口(远程)
	__u16			num;             //本地端口(主机字节序)
	__be32			saddr;        
	
	……
}; 

这里的 inet_sock 结构体不仅记录了本地的IP和端口号,还记录了目的IP和端口号。


可以直接将 sock 结构体变量强制转换为 inet_sock 结构体变量,因为在分配 sock 结构体变量时,真正分配的是 tcp_sock(TCP,udp_sock(UDP)),由于 tcp_sock、inet_sock。sock 之间均为0处偏移量,因此可以直接将 tcp_sock 强制转换为 inet_sock。这类似于C++面向对象中的 is-a 思想。

struct tcp_sock {
	/* inet_connection_sock has to be the first member of tcp_sock */
	struct inet_connection_sock	inet_conn;
	u16	tcp_header_len;	/* Bytes of tcp header to send		*/
	……
};

struct inet_connection_sock {
	/* inet_sock has to be the first member! */
	struct inet_sock	  icsk_inet;
	struct request_sock_queue icsk_accept_queue;
	……
 };

struct inet_sock {
	/* sk and pinet6 has to be the first two members of inet_sock */
	struct sock		sk;
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
	struct ipv6_pinfo	*pinet6;
#endif
	……
};

其内存布局大致如下图(图片来源于网络)


inet_connection_sock 是所有面向连接的协议的 socket 的相关信息,tcp_sock 是TCP协议专用的一个 socket 表示,它是在 inet_connection_sock 基础上进行的扩展。

这里只是简单的介绍了一下 socket 套接字,其余具体需要跟踪 socket 的创建才能更深的了解。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值