我们编写所谓的高性能高并发网络服务器时,不免使用各种IO多路复用技术,比如select, poll, epoll等。那这种技术的关键是什么呢?没错,就是得到就绪的描述符。然后我们的服务器就可以从休眠状态或者其他状态醒来,处理这些就绪的描述符(比如socket,管道,socketpair等)。
那描述符就绪的条件又是什么呢?据说腾讯后台C/C++面试都会问类似的问题,所以深入了解一些技术细节,还是很必要的。
当然,这个问题,被视为圣经之一的《UNIX 网络编程卷1》中的《第6章 I/O复用》早就给我们指明了光明大道。
一、 满足下列四个条件中的任何一个时,一个套接字准备好读。
-
该套接字接收缓冲区中的数据字节数大于等于套接字接收缓存区低水位。对于TCP和UDP套接字而言,缓冲区低水位的值默认为1。那就意味着,默认情况下,只要缓冲区中有数据,那就是可读的。我们可以通过使用SO_RCVLOWAT套接字选项(参见setsockopt函数)来设置该套接字的低水位大小。此种描述符就绪(可读)的情况下,当我们使用read/recv等对该套接字执行读操作的时候,套接字不会阻塞,而是成功返回一个大于0的值(即可读数据的大小)。
-
该连接的读半部关闭(也就是接收了FIN的TCP连接)。对这样的套接字的读操作,将不会阻塞,而是返回0(也就是EOF)。
-
该套接字是一个listen的监听套接字,并且目前已经完成的连接数不为0。对这样的套接字进行accept操作通常不会阻塞。
-
有一个错误套接字待处理。对这样的套接字的读操作将不阻塞并返回-1(也就是返回了一个错误),同时把errno设置成确切的错误条件。这些待处理错误(pending error)也可以通过指定SO_ERROR套接字选项调用getsockopt获取并清除。
二、满足下列四个条件中的任何一个时,一个套接字准备好写。
-
该套接字发送缓冲区中的可用空间字节数大于等于套接字发送缓存区低水位标记时,并且该套接字已经成功连接(UDP套接字不需要连接)。对于TCP和UDP而言,这个低水位的值默认为2048,而套接字默认的发送缓冲区大小是8k,这就意味着一般一个套接字连接成功后,就是处于可写状态的。我们可以通过SO_SNDLOWAT套接字选项(参见setsockopt函数)来设置这个低水位。此种情况下,我们设置该套接字为非阻塞,对该套接字进行写操作(如write,send等),将不阻塞,并返回一个正值(例如由传输层接受的字节数,即发送的数据大小)。
-
该连接的写半部关闭。对这样的套接字的写操作将会产生SIGPIPE信号。所以我们的网络程序基本都要自定义处理SIGPIPE信号。因为SIGPIPE信号的默认处理方式是程序退出。
-
使用非阻塞的connect套接字已建立连接,或者connect已经以失败告终。即connect有结果了。
-
有一个错误的套接字待处理。对这样的套接字的写操作将不阻塞并返回-1(也就是返回了一个错误),同时把errno设置成确切的错误条件。这些待处理的错误也可以通过指定SO_ERROR套接字选项调用getsockopt获取并清除。
知道了这个细节,那就知道select, poll, epoll什么时候被唤醒,我们的程序又该什么时候监控套接字的读事件,什么时候监控套接字的写事件了。当然,面对腾讯的面试,也可以从容回答了,哈哈。
阅读(1992) | 评论(0) | 转发(0) |