1、socket与缓存区
http://c.biancheng.net/cpp/html/3040.html
epoll的ET触发并非缓存区可读可写,而是有新的网络IO事件。如果没有完全读取完毕而返回导致缓存区一直有数据可读。则只能等到下一次有网络IO时间的时候才能读取到。
2、阻塞与非阻塞的网络fd
阻塞与非阻塞是针对fd而言:
阻塞的fd在read不到数据的时候,会一直等待。
在非阻塞的fd中,如果输入操作不能被满足(对于TCP套接口即至少有一个字节的数据可读,对于UDP套接口即有一个完整的数据报可读),则返回-1,并且,设置errno为EWOULDBLOCK错误
2.1、read and write
1、当read()或者write()函数返回值大于0时,表示实际从缓冲区读取或者写入的字节数目
2、
当read()函数返回值为0时,表示对端已经关闭了 socket,这时候也要关闭这个socket,否则会导致socket泄露。netstat命令查看下,如果有closewait状态的socket,就是socket泄露了
当write()函数返回0时,表示当前写缓冲区已满,是正常情况,下次再来写就行了。
3、当read()或者write()返回-1时,一般要判断errno
如果errno == EINTR,表示系统当前中断了,直接忽略
如果errno == EAGAIN或者EWOULDBLOCK,非阻塞socket直接忽略
如果是阻塞的socket,一般是读写操作超时了,还未返回。这个超时是指socket的SO_RCVTIMEO与SO_SNDTIMEO两个属性
ps:read函数读取到缓存区的数据就会返回,不论参数的设置的大小或者fd是否阻塞
所以在使用阻塞socket时,不要将超时时间设置的过小。不然返回了-1,你也不知道是socket连接是真的断开了,还是正常的网络抖动。
一般情况下,阻塞的socket返回了-1,都需要关闭重新连接。
以上部分摘取自:https://www.cnblogs.com/myd620/p/6219994.html
2.2、accept
1、在accept新连接时,建议以非阻塞的方式监听listenfd:如果在触发accept可读的同时,客户端请求关闭连接,此时,此时,会将将其从等待连接的队列中删除,如果队列为空,accept阻塞,直到有新的连接
2、在后续的accept调用中忽略以下错误:EWOULDBLOCK(源自Berkeley的实现,客户终止连接时),ECONNABORTED(POSIX实现,客户终止连接时),EPROTO(SVR4实现,客户终止连接时)和EINTR(如果有信号被捕获)
3、使用举例
while ((conn_sock = accept(listenfd,(struct sockaddr *) &remote,
(size_t *)&addrlen)) > 0) {
handle_client(conn_sock);
}
if (conn_sock == -1) {
if (errno != EAGAIN && errno != ECONNABORTED
&& errno != EPROTO && errno != EINTR)
perror("accept");
}
2.3、问题
阻塞的FD为什么被SELECT OR EPOLL触发可读后,读取的时候还是可能会被阻塞?
以读为例:在多路复用中,被SELECT OR EPOLL触发可读后,但是被协议栈检查报文错误,则该报文被丢弃。其实是不可读的,此时线程OR进程会被卡在READ函数处。
对于非阻塞的套接口,如果输入操作不能被满足(对于TCP套接口即至少有一个字节的数据可读,对于UDP套接口即有一个完整的数据报可读)
3、epoll使用举例
1、LT配合非阻塞的fd,见 redis的非阻塞网络模型
2、LT配合阻塞的fd,由于2.2的原因,一般不用多路复用中一般不用阻塞的fd
/* read call happens here */
if ((nbytes = read(i, buf, sizeof(buf))) >= 0) {
handle_read(nbytes, buf);
} else {
/* real version needs to handle EINTR correctly */
perror("read");
exit(EXIT_FAILURE);
}
3、ET模式一般不和阻塞的fd一起使用,理由如下:
只说读的情况:
ET只有来数据时才会提醒,因此读数据时,你必须一次性地把缓冲区的数据读完(如果不读完,则缓冲区还残留着未读数据,然后对端又不继续发数据的话,ET是不会再通知你,也就是说你将永远读不到残留数据)为了一次性把缓冲区数据读完,你必须要写一个while循环来read,直到缓冲区里面数据被读完如果设成阻塞的话,你的程序就无法知道数据什么时候被读完,因为当数据读完时,会卡在while里面的read,一直在等数据,永远退不出while
如果设成非阻塞,当数据被读完,read就会返回,然后将errno设成EAGAIN并退出while,这才是正确的逻辑
4、ET配合非阻塞的fd:
n = 0;
while ((nread = read (sockfd, buf + n, BUFSIZ - 1)) > 0)
{
n += nread;
}
if (nread == -1 && errno != EAGAIN)
{
perror ("read error");
需要一直读取,直到返回-1。
如果I/O数据量很大,可能在读取数据的过程中其他文件得不到处理,造成饥饿。解决方法是维护一个就绪列表,在关联数据结构中标记文件描述符为就绪状态,由此可以记住哪些文件在等待,并对所有就绪文件作轮转处理。