文档章节

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

shzwork
 shzwork
发布于 04/29 18:36
字数 1107
阅读 13
收藏 2

http://blog.csdn.net/ldd909/article/details/6168077

 

补充一点:只有在使用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,  0 ,  sizeof (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,  0 ,  sizeof (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 ;
}

 

本文转载自:https://blog.csdn.net/yangruibao/article/details/8215905?utm_source=blogxgwz6

shzwork
粉丝 12
博文 732
码字总数 10605
作品 0
厦门
私信 提问
linux epoll 开发指南-【ffrpc源码解析】

摘要 关于epoll的问题很早就像写文章讲讲自己的看法,但是由于ffrpc一直没有完工,所以也就拖下来了。Epoll主要在服务器编程中使用,本文主要探讨服务器程序中epoll的使用技巧。Epoll一般和异...

知然
2013/12/17
0
0
linux epoll使用详解

Linux2.6内核中epoll用法详解 引言 epoll是linux2.6内核中才有的机制,其他版本内核中是没有的,是Linux2.6内核引入的多路复用IO的一种方式,用于提高网络IO 性能的方法。在linux网络编程中,...

lichao19881026
2014/05/12
93
0
linux epoll使用详解

Linux2.6内核中epoll用法详解 引言 epoll是linux2.6内核中才有的机制,其他版本内核中是没有的,是Linux2.6内核引入的多路复用IO的一种方式,用于提高网络IO 性能的方法。在linux网络编程中,...

xiaot99
2014/02/07
933
0
如何使用 epoll? 一个 C 语言实例

通常的网络服务器实现, 是对每一个连接使用一个单独的线程或进程。对高性能应用而言,由于需要同时处理非常多的客户请求, 所以这种方式并不能工作得很好,因为诸如资源使用和上下文切换所需...

showme
2013/02/25
16.2K
38
Linux内核中网络数据包的接收-第二部分 select/poll/epoll

和前面文章的第一部分一样,这些文字是为了帮别人或者自己理清思路的,而不是所谓的源码分析,想分析源码的,还是直接debug源码最好,看任何文档以及书都是下策。因此这类帮人理清思路的文章...

dog250
2016/01/16
0
0

没有更多内容

加载失败,请刷新页面

加载更多

最简单的获取相机拍照的图片

  import android.content.Intent;import android.graphics.Bitmap;import android.os.Bundle;import android.os.Environment;import android.provider.MediaStore;import andr......

MrLins
31分钟前
4
0
说好不哭!数据可视化深度干货,前端开发下一个涨薪点在这里~

随着互联网在各行各业的影响不断深入,数据规模越来越大,各企业也越来越重视数据的价值。作为一家专业的数据智能公司,个推从消息推送服务起家,经过多年的持续耕耘,积累沉淀了海量数据,在...

个推
32分钟前
7
0
第三方支付-返回与回调注意事项

不管是支付宝,微信,还是其它第三方支付,第四方支付,支付机构服务商只要涉及到钱的交易都要进行如下校验,全部成功了才视为成功订单 1.http请求是否成功 2.校验商户号 3.校验订单号及状态...

Shingfi
35分钟前
4
0
简述Java内存分配和回收策略以及Minor GC 和 Major GC(Full GC)

内存分配: 1. 栈区:栈可分为Java虚拟机和本地方法栈 2. 堆区:堆被所有线程共享,在虚拟机启动时创建,是唯一的目的是存放对象实例,是gc的主要区域。通常可分为两个区块年轻代和年老代。更...

DustinChan
41分钟前
6
0
Excel插入批注:可在批注插入文字、形状、图片

1.批注一直显示:审阅选项卡-------->勾选显示批注选项: 2.插入批注快捷键:Shift+F2 组合键 3.在批注中插入图片:鼠标右键点击批注框的小圆点【重点不可以在批注文本框内点击】----->调出批...

东方墨天
今天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部