Socket

Socket 这个名字很有意思,可以作插口或者插槽讲。虽然我们是写软件程序,但是你可以想象为弄一根
网线,一头插在客户端,一头插在服务端,然后进行通信。所以在通信之前,双方都要建立一个
Socket。
在建立 Socket 的时候,应该设置什么参数呢?Socket 编程进行的是端到端的通信,往往意识不到中间
经过多少局域网,多少路由器,因而能够设置的参数,也只能是端到端协议之上网络层和传输层的。
在网络层,Socket 函数需要指定到底是 IPv4 还是 IPv6,分别对应设置为 AF_INET 和 AF_INET6。另
外,还要指定到底是 TCP 还是 UDP。还记得咱们前面讲过的,TCP 协议是基于数据流的,所以设置为
SOCK_STREAM,而 UDP 是基于数据报的,因而设置为 SOCK_DGRAM

基于 TCP 协议的 Socket 程序函数调用过程

TCP 的服务端要先监听一个端口,一般是先调用 bind 函数,给这个 Socket 赋予一个 IP 地址和端口。
为什么需要端口呢?要知道,你写的是一个应用程序,当一个网络包来的时候,内核要通过 TCP 头里面
的这个端口,来找到你这个应用程序,把包给你。为什么要 IP 地址呢?有时候,一台机器会有多个网
卡,也就会有多个 IP 地址,你可以选择监听所有的网卡,也可以选择监听一个网卡,这样,只有发给这
个网卡的包,才会给你。
当服务端有了 IP 和端口号,就可以调用 listen 函数进行监听。在 TCP 的状态图里面,有一个 listen 状
态,当调用这个函数之后,服务端就进入了这个状态,这个时候客户端就可以发起连接了

在内核中,为每个 Socket 维护两个队列,一个是已经建立了连接的队列,这时候连接三次握手已经完
毕,处于 established 状态;一个是还没有完全建立连接的队列,这个时候三次握手还没完成,处于
syn_rcvd 的状态。
接下来,服务端调用 accept 函数,拿出一个已经完成的连接进行处理。如果还没有完成,就要等着。
在服务端等待的时候,客户端可以通过 connect 函数发起连接。先在参数中指明要连接的 IP 地址和端
口号,然后开始发起三次握手。内核会给客户端分配一个临时的端口。一旦握手成功,服务端的 accept
就会返回另一个 Socket。
这是一个经常考的知识点,就是监听的 Socket 和真正用来传数据的 Socket 是两个,一个叫作监听
Socket,一个叫作已连接 Socket。
连接建立成功之后,双方开始通过 read 和 write 函数来读写数据,就像往一个文件流里面写东西一样。
这个图就是基于 TCP 协议的 Socket 程序函数调用过程

image.png

说 TCP 的 Socket 就是一个文件流,是非常准确的。因为,Socket 在 Linux 中就是以文件的形式存在
的。除此之外,还存在文件描述符。写入和读出,也是通过文件描述符。
在内核中,Socket 是一个文件,那对应就有文件描述符。每一个进程都有一个数据结构 task_struct,
里面指向一个文件描述符数组,来列出这个进程打开的所有文件的文件描述符。文件描述符是一个整
数,是这个数组的下标。
这个数组中的内容是一个指针,指向内核中所有打开的文件的列表。既然是一个文件,就会有一个
inode,只不过 Socket 对应的 inode 不像真正的文件系统一样,保存在硬盘上的,而是在内存中的。
在这个 inode 中,指向了 Socket 在内核中的 Socket 结构。
在这个结构里面,主要的是两个队列,一个是发送队列,一个是接收队列。在这两个队列里面保存的是
一个缓存 sk_buff。这个缓存里面能够看到完整的包的结构。

基于 UDP 协议的 Socket 程序函数调用过程

对于 UDP 来讲,过程有些不一样。UDP 是没有连接的,所以不需要三次握手,也就不需要调用 listen
和 connect,但是,UDP 的的交互仍然需要 IP 和端口号,因而也需要 bind。UDP 是没有维护连接状
态的,因而不需要每对连接建立一组 Socket,而是只要有一个 Socket,就能够和多个客户端通信。也
正是因为没有连接状态,每次通信的时候,都调用 sendto 和 recvfrom,都可以传入 IP 地址和端口。
这个图的内容就是基于 UDP 协议的 Socket 程序函数调用过程


image.png

服务器最大连接数

我们先来算一下理论值,也就是最大连接数,系统会用一个四元组来标识一个 TCP 连接

{本机 IP, 本机端口, 对端 IP, 对端端口}

服务器通常固定在某个本地端口上监听,等待客户端的连接请求。因此,服务端端 TCP 连接四元组中只有对端 IP, 也就是客户端的 IP 和对端的端口,也即客户端的端口是可变的,因此,最大 TCP 连接数 =客户端 IP 数×客户端端口数。对 IPV4,客户端的 IP 数最多为 2 的 32 次方,客户端的端口数最多为 2的 16 次方,也就是服务端单机最大 TCP 连接数,约为 2 的 48 次方。当然,服务端最大并发 TCP 连接数远不能达到理论上限。首先主要是文件描述符限制,按照上面的原理,Socket 都是文件,所以首先要通过 ulimit 配置文件描述符的数目;另一个限制是内存,按上面的数据结构,每个 TCP 连接都要占用一定内存,操作系统是有限

image.png

如图所示,假设进程打开了 Socket m, n, x 等多个文件描述符,现在需要通过 epoll 来监听是否这些
Socket 都有事件发生。其中 epoll_create 创建一个 epoll 对象,也是一个文件,也对应一个文件描述
符,同样也对应着打开文件列表中的一项。在这项里面有一个红黑树,在红黑树里,要保存这个 epoll
要监听的所有 Socket。
当 epoll_ctl 添加一个 Socket 的时候,其实是加入这个红黑树,同时红黑树里面的节点指向一个结构,
将这个结构挂在被监听的 Socket 的事件列表中。当一个 Socket 来了一个事件的时候,可以从这个列表
中得到 epoll 对象,并调用 call back 通知它。
这种通知方式使得监听的 Socket 数据增加的时候,效率不会大幅度降低,能够同时监听的 Socket 的数
目也非常的多了。上限就为系统定义的、进程打开的最大文件描述符个数。因而,epoll 被称为解决
C10K 问题的利器。

——学自极客时间

你可能感兴趣的:(Socket)