Linux网络编程 select/epoll得知socket有数据可读,如何判断数据全部被读取完毕?

摘要:
当使用select或epoll的LT模式时,数据是否被读取并不重要。如果select/epoll检测到有数据要读取,则正常。当函数返回-1并且错误号为EAGAIN或EWOULDBLOCK时,表示所有数据都已读取。事实上,无论使用什么平台编写网络程序,我认为都应该使用select+NONBLOCK套接字的方法。事实上,所谓的读取完成意味着内核中与该套接字对应的inputdataqueue中的所有数据都已被读取,因此该套接字在内核中被设置为不可读状态。因此,例如,如果在LAN中,发送方继续发送数据,我们可以在选择recvsocket后继续读取数据。
补充一点:只有在使用epoll ET(Edge Trigger)模式的时候,才需要关注数据是否读取完毕了。使用select或者epoll的LT模式,其实根本不用关注数据是否读完了,select/epoll检测到有数据可读去读就OK了。

这里有两种做法:

1. 针对TCP,调用recv方法,根据recv方法的返回值,如果返回值小于我们指定的recv buffer的大小,则认为数据已经全部接收完成。在Linux epoll的manual中,也有类似的描述:
For stream-oriented files (e.g., pipe, FIFO, stream socket), the condition that the read/write I/O space is exhausted can also be detected  by  checking the  amount  of data read from / written to the target file descriptor.  For example, if you call read(2) by asking to read a certain amount of data and read(2) returns a lower number of bytes, you can be sure of having exhausted the read I/O space for the file descriptor.  The same is true when  writing using write(2).  (Avoid this latter technique if you cannot guarantee that the monitored file descriptor always refers to a stream-oriented file.)
2. TCP和UDP都适用。将socket设成NONBLOCK(使用fcntl函数),然后select到该socket可读之后,使用read/recv来读取数据。当函数返回-1,同时errno是EAGAIN或EWOULDBLOCK的时候,表示数据已经全部读取完毕。
实验结论:
第一种方法是错误的。简单来说,如果发送了4K字节,recv的时候使用一个2K的buffer,那么,recv两次之后就再也没有数据可以recv了,此时recv就会block。永远不会出现recv返回值小于2K的情况(注:recv/read返回0表示对端socket已经关闭)。
所以推荐使用第二种方法,第二种方法正确而且对TCP和UDP都管用。事实上,不论什么平台编写网络程序,我认为都应该使用select+NONBLOCK socket的方式。这样可以保证你的程序至少不会在recv/send/accept/connect这些操作上发生block从而将整个网络服务都停下来。不好的地方就是不太利于Debug,如果是block的socket,那么GDB一跟就能知道阻塞在什么地方了。。。
其实所谓读取完毕指的是kernel中该socket对应的input data queue中的数据全部被读取了出来,从而该socket在kernel中被设置成了unreadable的状态。所以如果比如在局域网内,sender一直不断发送数据,则select到recv socket可读之后,我们就可以一直不停的读取到数据。所以,如果一个网络程序接收端想一次把数据全部接收完并且将所有接收到的数据都保存在内存中的话,就需要考虑到这种情况,避免占用过多的内存。
下面是测试代码,代码中client读取了4K了之后就退出了,因为sender每次发送4K,所以client select到一次readable之后,就只会读取到4K。

Client.c:

#include <stdio.h>
#include 
<stdlib.h>
#include 
<errno.h>
#include 
<string.h>
#include 
<netdb.h>
#include 
<sys/types.h>
#include 
<netinet/in.h>
#include 
<sys/socket.h>
#include 
<fcntl.h>
#include 
<unistd.h>
#include 
<sys/select.h>

#define SERVPORT 3333
#define RECV_BUF_SIZE 1024

void setnonblocking(int sock)
{
    
int opts;
    opts
=fcntl(sock,F_GETFL);
    
if(opts<0)
    {
        perror(
"fcntl(sock,GETFL)");
        exit(
1);
    }
    opts 
= opts|O_NONBLOCK;
    
if(fcntl(sock,F_SETFL,opts)<0)
    {
        perror(
"fcntl(sock,SETFL,opts)");
        exit(
1);
    }
}

int main(int argc, char *argv[])
{
    
int sockfd, iResult;
    
char buf[RECV_BUF_SIZE];
    
struct sockaddr_in serv_addr;
    fd_set readset, testset;

    sockfd 
= socket(AF_INET, SOCK_STREAM, 0);
    setnonblocking(sockfd);

    memset(
&serv_addr, 0sizeof(serv_addr));
    serv_addr.sin_family
=AF_INET;
    serv_addr.sin_port
=htons(SERVPORT);
    serv_addr.sin_addr.s_addr 
= inet_addr("127.0.0.1");

    connect(sockfd, (
struct sockaddr *)&serv_addr, sizeof(serv_addr));

    FD_ZERO(
&readset);
    FD_SET(sockfd, 
&readset);

    testset 
= readset;
    iResult 
= select(sockfd + 1&testset, NULL, NULL, NULL);

    
while (1) {
        iResult 
= recv(sockfd, buf, RECV_BUF_SIZE, 0);
        
if (iResult == -1) {
            
if (errno == EAGAIN || errno == EWOULDBLOCK) {
                printf(
"recv finish detected, quit...\n");
                
break;
            }
        }
        printf(
"Received %d bytes\n", iResult);
    }

    printf(
"Final iResult: %d\n", iResult);
    
return 0;
}

Server.c:

#include <stdio.h>
#include 
<stdlib.h>
#include 
<errno.h>
#include 
<string.h>
#include 
<sys/types.h>
#include 
<netinet/in.h>
#include 
<sys/socket.h>
#include 
<sys/wait.h>

#define SERVPORT 3333
#define BACKLOG 10
#define SEND_BUF_SIZE 4096

int main(int argc, char *argv[])
{
    
int sockfd, client_fd, i;
    
struct sockaddr_in my_addr;
    
char *buffer = NULL;

    sockfd 
= socket(AF_INET, SOCK_STREAM, 0);
    memset(
&my_addr, 0sizeof(my_addr));
    my_addr.sin_family
=AF_INET;
    my_addr.sin_port
=htons(SERVPORT);
    my_addr.sin_addr.s_addr 
= inet_addr("127.0.0.1");

    bind(sockfd, (
struct sockaddr *)&my_addr, sizeof(struct sockaddr));
    listen(sockfd, BACKLOG);

    client_fd 
= accept(sockfd, NULL, NULL);

    buffer 
= malloc(SEND_BUF_SIZE); 

    
for (i = 0; i < 100; i++) {
        send(client_fd, buffer, SEND_BUF_SIZE, 
0);
        sleep(
1);
    }

    sleep(
10);
    close(client_fd);
    close(sockfd);
    free(buffer);
    
return 0;
}

免责声明:文章转载自《Linux网络编程 select/epoll得知socket有数据可读,如何判断数据全部被读取完毕?》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇基础篇:JAVA.Stream函数,优雅的数据流操作点击label时click事件被触发两次的坑下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

父进程非阻塞回收子进程(适用LINUX下C语言的clientserver模型)

      众所周知,子进程退出后(不管是正常还是异常退出),其父进程需要通过wait或waitpid来回收子进程的一些资源。回收是没有疑义的,但是父进程在哪儿回收,以什么方式回收,却影响着设计思路和效率。        一般的回收机制都属于阻塞回收,父进程阻塞等待子进程技术,收到子进程的退出状态。然而在实验中我需要实现的属于client-server模型...

转://Linux MultiPath多路径软件实施说明

Multipath的工作原理 当multipath启动的时候,它通过系统命令scsi_id -eg -s /block/sdX得到proc/partitions 里面所有块设备的 UUID(universally unique identify),然后把所有具有同一个UUID的块设备组成一个Group,在/dev/mapper 生产一个对应的单独的设备。当...

SQL分组取每组前一(或几)条记录(排名)

mysql分组取每组前几条记录(排名) 附group by与order by的研究 http://www.jb51.net/article/31590.htm --按某一字段分组取最大(小)值所在行的数据 代码如下: /* 数据如下: name val memo a 2 a2(a的第二个值) a 1 a1--a的第一个值 a 3 a3:a...

Linux 信号signal处理机制

信号是Linux编程中非常重要的部分,本文将详细介绍信号机制的基本概念、Linux对信号机制的大致实现方法、如何使用信号,以及有关信号的几个系统调用。 信号机制是进程之间相互传递消息的一种方法,信号全称为软中断信号,也有人称作软中断。从它的命名可以看出,它的实质和使用很象中断。所以,信号可以说是进程控制的一部分。 一、信号的基本概念 本节先介绍信号的一...

[Linux实用工具]Linux监控工具munin的安装和配置

〇、摘要 munin是用于Linux系统(也可以监控windows系统)的监控软件。munin除了可以监控系统的各项数值之外,最大的好处是可以自己编写插件自定义监控需要的数值。整个系统的架构简单明了,操作方便。如果是使用Debian或者Ubuntu安装,安装过程也非常简单。munin除了可以监控结果,也可以设置报警。对于我个人对性能测试的工作来说,是个非常...

MYSQL SQL语句 之 select

select语句在数据库操作中是操作频率最高的语句,使用方式也是多种多样,它的基本功能是:从表中选取数据,结果存储在一个结果集中。可以联合where,and,or,Order By,distinct,top, like,等一起使用。 一. select 最基本的用法如下: select 字段 from 表名 字段是什么,选出的结果集中就包括什么字段 例如:...