LINUX中的阻塞与非阻塞
阻塞与非阻塞的区别在于没有数据到达的时候是否立刻返回。以读来说,读的过程其实远不止读这个动作,无论是read()还是recv(),都只是负责把数据从底层缓冲(socket缓冲区)copy到我们指定的位置(用户程序的数组)。
对读来说:
- 阻塞的情况下,read/recv/msgrcv的行为:如果没有发现数据再socket缓冲区中会一直等待。如果发现有数据的时候会把数据读到用户指定的缓冲区,但是如果这个时候读到的数据量比较少,比参数中指定的长度要小,read并不会一直等待下去,而是立刻返回。总结一下就是在数据不超过指定的长度的时候有多少读多少,没有数据就会一直等待。
- 再非阻塞的情况下,read的行为,如果发现没有数据就直接返回。如果发现有数据那么也是采用有多少读多少的进行处理
EPOLLSHOT事件
即使可以使用ET事件,一个socket上的某个事件还是可能被触发多次。这在并发程序中就会引发一个问题,比如一个线程在读取某个socket上的数据后开始处理这些数据,而在数据的处理过程中该socket又有新数据可读,此时另外一个线程被唤醒来读取这些新的数据,于是就发现两个线程同时操作一个socket的局面,一个socket连接在任一时刻都只被一个线程处理,可以使用epoll的EPOLLONESHOT事件实现。
对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个可读、可写或异常事件,且只触发一次。除非我们使用epoll_ctl()函数重置该文件描述符上的EPOLLONESHOT事件。
这样,当一个线程在处理某个socket时,其他线程是不可能有机会操作该socket的。但反过来思考,注册了EPOLLONESHOT事件的socket一旦被某个线程处理完毕,该线程就应该立即重置这个socket上的EPOLLONESHOT事件,以确保这个socket下一次可读时,其EPOLLIN事件能被触发,进而让其他工作线程有机会继续处理这个socket。
为什么在高性能服务器开发中socket设置为非阻塞
- 当socket用于数据传输的时候,由于epoll使用的ET模式,必须在一次EPOLLIN事件中循环读出所有的socket数据,则必须使用非阻塞,不然在最后一次循环中,会一直阻塞
- 当socket用于监听的时候,在三次握手后客户端的connect()函数返回,而服务端还未执行accept()函数的这个过程中,客户当通过RST报文取消连接,当服务端执行accept()函数的时候,此时发现内核中已连接队列为空,服务端进程阻塞在accept()函数这,无法响应其他已连接套接字的事件。为了防止出现上面的场景,我们需要把监听套接字设置为非阻塞。