三次握手
四次挥手
SOCKET
在内核中,会维护两个队列:
- 半连接队列(syn 队列),存放处于 SYN-RCVD 状态的 socket
- 全连接队列(accept 队列),存放处于 ESTABLISHED 状态的 socket
accept 函数会从全连接队列里取出一个连接,如果没有则阻塞。这里取出的是一个新的连接,叫已连接 socket,不同于一开始创建的监听 socket。Socket 在 Linux 中以文件的形式存在,有自己的文件描述符,读写就如同一个文件流。
socket 结构里有一个发送队列和一个接收队列,里面保存着缓存 sk_buff,里面可以看到完整的包结构。
数据接收流程
网卡接收到数据后写入内存,并向 CPU 发起中断,操作系统执行中断程序唤醒对应 socket 等待队列里的进程。
如何同时监听多个 socket:
- select
将进程放入所有需要监听的 socket 的等待队列里,如果有一个 socket 接收到了数据就唤醒进程,然后遍历 socket 列表查看是哪个 socket 接收到了数据。
- epoll
int epfd = epoll_create(...)
epoll_ctl(epfd, ...)
while(1){int n = epoll_wait(...)
for (接收到数据的 socket){// 处理}
}
利用 epoll_ctl 将需要监视的 socket 添加到 epoll_create 创建的对象(eventpoll)中,然后利用 epoll_wait 进行阻塞,将进程放入 eventpoll 的等待队列中。当有 socket 接收到数据,中断程序将其被 eventpoll 的就绪列表所(rdllist)引用,然后唤醒进程,进程只需要遍历就绪列表就可以了。socket 在 eventpoll 中利用红黑树存储,而就绪列表利用双向链表存储。