操作系统-小林coding-网络系统
本系列笔记为作者在跟随小林coding学习的时候做的笔记。感谢小林大大。
什么是零拷贝
减少「用户态与内核态的上下文切换」和「内存拷贝」的次数
两种实现方式
- mmap+write
- sendfile
- 真正的零拷贝技术:网卡支持 SG-DMA(The Scatter-Gather Direct Memory Access)技术(和普通的 DMA 有所不同)
I/O 多路复用:select/poll/epoll
最基本的 Socket 模型
创建 Socket 的时候,可以指定网络层使用的是 IPv4 还是 IPv6,传输层使用的是 TCP 还是 UDP
udp很简单这里只讲tcp
服务端调用 socket() 函数,创建网络协议为 IPv4,以及传输协议为 TCP 的 Socket ,接着调用 bind() 函数,给这个 Socket 绑定一个 IP 地址和端口,最后调用 listen() 函数进行监听
netstat 或 ss 查看正在被监听的端口号
客户端创建 Socket 后,调用 connect() 函数指明服务端的 IP 地址和端口号发起连接,然后开始 TCP 三次握手
服务端 TCP 连接的过程中每个 Socket 维护了两个队列:
- TCP 半连接队列,syn_rcvd状态
- TCP 全连接队列,established状态
TCP 全连接队列不为空时,服务端的 accept() 函数从内核中的 TCP 全连接队列里拿出一个已经完成连接的 Socket 返回应用程序,后续数据传输都用这个 Socket
监听的 Socket 和真正用来传数据的 Socket 是不同的
连接建立后,客户端和服务端都可以通过 read() 和 write() 函数来读写数据
Socket 也是以「文件」的形式存在的,也是有对应的文件描述符,Socket 文件的 inode 指向了内核中的 Socket 结构,它有发送队列和接收队列字段,保存链表形式的 sk_buff 结构体
sk_buff 结构体表示各层的数据包,为了不发生拷贝,发送和接收数据包时通过修改 skb->data 的值,来修改头部范围
多进程模型
多线程模型
I/O 多路复用
select/poll/epoll使得进程可以通过一个系统调用函数从内核中获取多个事件
select/poll
select 将已连接的 Socket 都放到一个文件描述符集合,通过遍历文件描述符集合检查是否有事件产生,如果有,将此 Socket 标记为可读或可写,把整个文件描述符集合拷贝回用户态里,用户态需要再通过遍历的方法找到可读或可写的 Socket 对其处理。2 次「遍历」文件描述符集合,2 次「拷贝」文件描述符集合
select 使用固定长度的 BitsMap,支持的文件描述符的个数有限制,默认最大值为 1024,只能监听 0~1023 的文件描述符
poll 使用动态数组以链表形式组织,突破了 select 的文件描述符个数限制
它们都使用「线性结构」存储进程关注的 Socket 集合,因此都需要遍历文件描述符集合来找到可读或可写的 Socket,时间复杂度为 O(n),而且也需要在用户态与内核态之间拷贝文件描述符集合
epoll
|
|
epoll通过2个方法优化了select/poll
- 红黑树来跟踪进程所有待检测的文件描述字,通过 epoll_ctl() 函数加入内核中的红黑树里,增删改一般时间复杂度是 O(logn),只需要传入一个待检测的 socket 而不是所有 socket 集合
- 事件驱动,维护了一个链表来记录就绪事件,只将有事件发生的 Socket 集合传递给应用程序
epoll 被称为解决 C10K 问题的利器
边缘触发和水平触发
- 边缘触发:服务器端只会从 epoll_wait 中苏醒一次
- 水平触发:服务器端不断地从 epoll_wait 中苏醒,直到内核缓冲区数据被 read 函数读完才结束
select/poll 只有水平触发
epoll 支持边缘触发和水平触发,默认是水平触发
什么是一致性哈希
提高均衡度:将虚拟节点映射到哈希环上,并将虚拟节点映射到实际节点,所以这里有「两层」映射关系