转自:https://mp.weixin.qq.com/s/yXO5hn2PiPmRWTXygnf8zQ
https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=MzU1MDkzMzQzNQ==&scene=161#wechat_redirect
一、深入理解 Linux socket
什么是 socket fd ?粗糙的来讲,就是网络 fd,比如我们最常见的 C/S 客户端服务端的编程模式,就是网络通信的一种方式。撇开底层和协议细节,网络通信和文件读写从接口上有本质区别吗?
其实没啥区别,不就是读过来和写过去嘛,简称 IO 。
我们先看一下 socket fd 是什么样子的?随便找了个进程
这里我们看到 fd 7、8 都是一个 socket fd,名字:socket:[18892]
整数句柄后面一般会跟一些信息,用于帮助我们了解这个 fd 是什么。举个例子,如果是文件 fd,那么箭头后面一般是路径名称。现在拆解一下这个名字:
socket :标识这是一个 socket 类型的 fd
[18892] :这个是一个 inode 号,能够唯一标识本机的一条网络连接;
思考下,这个 inode 号,还能再哪里能看到呢?
在 proc 的 net 目录下,因为我这个是一个走 tcp 的服务端,所以我们看一下 /proc/net/tcp 文件。这个文件里面能看到所有的 tcp 连接的信息。
root@ubuntu:~# grep -i "18892" /proc/net/tcp
18: 00000000:1F93 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 18892 1 ffff880197fba580 100 0 0 10 0
root@ubuntu:~# grep -i "18893" /proc/net/tcp
28: 00000000:1F7C 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 18893 1 ffff880197fbad00 100 0 0 10 0
知识点又来了,/proc/net/tcp 这个文件记录了 tcp 连接的信息,这份信息是非常有用的。包含了 TCP 连接的地址(16进制显示),inode 的信息,连接的状态等等。
socket fd 的类型
上面我们提到了套接字,这是我们网络编程的主体,套接字由 socket() 系统调用创建,但你可知套接字其实可分为两种类型,监听套接字和普通套接字。而监听套接字是由 listen() 把 socket fd 转化而成。
1 监听套接字
对于监听套接字,不走数据流,只管理连接的建立。accept 将从全连接队列获取一个创建好的 socket( 3 次握手完成),对于监听套接字的可读事件就是全连接队列非空。对于监听套接字,我们只在乎可读事件。
2 普通套接字
普通套接字就是走数据流的,也就是网络 IO,针对普通套接字我们关注可读可写事件。在说 socket 的可读可写事件之前,我们先捋顺套接字的读写大概是什么样子吧。
套接字层是内核提供给程序员用来网络编程的,程序猿读写都是针对套接字而言,那么 write( socketfd, /* 参数 /) 和 read( socketfd, / 参数 */) 都会发生什么呢?
write 数据到 socketfd,大部分情况下,数据写到 socket 的内存 buffer,就结束了,并没有发送到对端网络(异步发送);
read socketfd 的数据,也只是从 socket 的 内存 buffer 里读数据而已,而不是从网卡读(虽然数据是从网卡一层层递上来的);
也就是说,程序猿而言,是跟 socket 打交道,内核屏蔽了底层的细节。
那说回来 socket 的可读可写事件就很容易理解了。
socketfd 可读:其实就是 socket buffer 内有数据(超过阈值 SO_RCLOWAT );
socketfd 可写:就是 socket buffer 还有空间让你写(阈值 SO_SNDLOWAT );
涉及的一些函数调用:
__sys_socket
// 创建 struct socket 结构体
-> sock_create
// 创建 struct socket 结构,并且关联特殊 inode
-> sock_alloc
// pf 是根据 family 从 net_families 这个全局表中取出的操作函数表,用来创建具体网络协议结构的;
// 比如 IPv4 对应的 family 就是 AF_INET ,对应的函数是 inet_create
// 在这里面会赋值 sock->ops 为对应协议族的操作函数表(比如 inet_stream_ops)
-> pf->create
// struct sock 结构体的创建(sk->sk_prot 的赋值就在这里,比如 tcp_prot )
-> sk_alloc
// struct sock 结构体的初始化(比如 sk_receive_queue, sk_write_queue, sk_error_queue 就是在这里初始化的)
// 可读写的关键函数 sock_def_readable,sock_def_write_space 也是在这里赋值的
-> sock_init_data
// 创建 struct file 结构体,并且关联 struct socket
-> sock_map_fd
最后用一张简要的图展示结构体之间的关系: