有关网络IO的内容可能都涉及一点,但是只涉及一点,所以……杂谈。
网络IO的核心,是处理好四个问题:连接建立、连接断开、数据到达、数据可发送。
阻塞和非阻塞IO的区别,就是他们在对应缓冲区不可读或不可写(缓冲区满)的时候,是否直接返回。一句话说,就是数据未准备好,是否阻塞等待。这里的阻塞,是阻塞在网络线程上。非阻塞的设置如下
fcntl(fd, F_SETFL, O_NONBLOCK);
这里还要注意一点,非阻塞是指数据准备阶段,在数据准备好之后,用户空间到系统空间之间的拷贝都是阻塞的。
##网络模型
简单介绍两种网络模型。
一个线程处理一个连接。此时不会设置非阻塞IO,因为如果非阻塞,线程就会一直检测IO,浪费CPU。
这种模型,优点是响应及时,毕竟“专门服务”,但是缺点也很明显,线程的利用率不高,要是一直没有数据,那就是一直阻塞。
之前讨论过这个模型,这里再介绍一下epoll相关的问题,IO主要作用于数据准备阶段,但是epoll还可以检测其他相关的状态,如连接的状态、keepalive等。另外,对于EPOLLHUP这个状态,指fd的读通道和写通道都关闭。再提示一下,epoll_wait()中的timeout可与定时器相结合。
reactor模型这里需要注意,对于IO多路复用,读写IO阻塞与非阻塞都可以,但是reactor中一定要使用非阻塞IO。
单reactor模型redis使用的模型,网络与业务处理都在同一线程中。
单reactor+任务队列+线程池reactor处理网络事件,将业务处理扔给线程池处理,实现网络事件处理与业务流程想分离。
多reactor模型 多线程
典型代表是memcached。它是一个主线程处理accept,其余线程处理读写事件。线程间用pipe通信。这里简单介绍一下多线程pipe通信的模型。因为pipe是单向通信,只能一端读另一端写,所以在memcached中,主线程在pipe中写,其他线程负责读(使用select)。
多进程
典型代表是nginx。master进程产生多个worker进程,每个worker进程负责读写、监听,每个worker进程都有一套reactor。
最后再补充零星几点。
半关闭状态 半关闭状态CLOSE_WAIT是客户端关闭了写通道,shutdown(SHUT_WR)发送⼀个 FIN 包,服务器接受到了客户端的FIN包,但仍要继续向客户端发送数据的状态。skynet支持半关闭状态,而redis、memcached、nginx不支持,是直接close(fd)。
具体细节
shutdown(SHUT_WR)发送⼀个FIN包,并且标记该socket为SEND_SHUTDOWN;
shutdown(SHUT_RD)不发送任何包,但是标记该socket为RCV_SHUTDOWN;
发送端发送FIN包,并且标记该socket为SEND_SHUTDOWN;
收到FIN包标记该 socket 为RCV_SHUTDOWN。
另外对于epoll⽽⾔,如果⼀个socket同时标记为SEND_SHUTDOWN和RCV_SHUTDOWN;那么epoll会返回EPOLLHUP;
如果⼀个socket被标记为RCV_SHUTDOWN;epoll会返回EPOLLRDHUP。
这里再提示一点,close(fd)只是将系统中该fd的引用计数减一,并没有真正关闭。内核在检测到fd没有引用之后,才会释放相应的连接数据。
EINTR:fd被中断打断。系统调用时,不会发生线程切换,但是中断的优先级比系统调用高,如果发生了中断,线程就可能发生切换,即被打断了,此时errno置为EINTR。于EWOULDBLOCK一样,需下次重试。注意epoll中做法有区别,LT的话下次重新检测event即可,不需要额外操作,ET就必须自己重试。
ETIMEDOUT:TCP探活包超时。之前有介绍,TCP有keepalive机制,在指定时间内以指定间隔发送指定数量的SYN包,都没收到对端的ACK包。这些数量、间隔、时间,都可以在/etc/sysctl.conf中设置
net.ipv4.tcp_keepalive_time=7200 #单位是秒net.ipv4.tcp_keepalive_intvl=75 #单位是秒net.ipv4.tcp_keepalive_probes=9
上面是对系统全局设置,也可以对单个连接设置,可以使⽤三个属性设置:TCP_KEEPCNT、TCP_KEEPIDLE、TCP_KEEPINTVL。
setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val));setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val));setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val));