草庐IT

关于C#:为什么客户端忙于接收数据时select()有时会超时

codeneng 2023-03-28 原文

Why select() timeouts sometimes when the client is busy receiving data

我已经编写了简单的C/S应用程序来测试非阻塞套接字的特性,这里有一些关于服务器和客户端的简要信息:

//On linux The server thread will send 
//a file to the client using non-blocking socket      
void *SendFileThread(void *param){
    CFile* theFile = (CFile*) param;
    int sockfd = theFile->GetSocket();
    set_non_blocking(sockfd);
    set_sock_sndbuf(sockfd, 1024 * 64); //set the send buffer to 64K

    //get the total packets count of target file
    int PacketCOunt = theFile->GetFilePacketsCount();
    int CurrPacket = 0;
    while (CurrPacket < PacketCount){
        char buffer[512];
        int len = 0;

        //get packet data by packet no.
        GetPacketData(currPacket, buffer, len);

        //send_non_blocking_sock_data will loop and send
        //data into buffer of sockfd until there is error
        int ret = send_non_blocking_sock_data(sockfd, buffer, len);
        if (ret < 0 && errno == EAGAIN){
            continue
          } else if (ret < 0 || ret == 0 ){
             break;
         } else {
             currPacket++;
         }


         ......
     }
 }
//On windows, the client thread will do something like below
//to receive the file data sent by the server via block socket
void *RecvFileThread(void *param){
    int sockfd = (int) param; //blocking socket
    set_sock_rcvbuf(sockfd, 1024 * 256); //set the send buffer to 256

    while (1){
        struct timeval timeout;
        timeout.tv_sec = 1;
        timeout.tv_usec = 0;

        fd_set rds;
        FD_ZERO(&rds);
        FD_SET(sockfd, &rds)'

        //actually, the first parameter of select() is
        //ignored on windows, though on linux this parameter
        //should be (maximum socket value + 1)
        int ret = select(sockfd + 1, &rds, NULL, NULL, &timeout );
        if (ret == 0){
            // log that timer expires
            CLogger::log("RecvFileThread---Calling select() timeouts\
");
        } else if (ret) {
            //log the number of data it received
            int ret = 0;
            char buffer[1024 * 256];
            int len = recv(sockfd, buffer, sizeof(buffer), 0);
            // handle error
            process_tcp_data(buffer, len);
        } else {
            //handle and break;
            break;
        }

    }
}

令我惊讶的是,由于套接字缓冲区已满,服务器线程经常失败,例如要发送一个 14M 大小的文件,它会报告 50000 次失败,且 errno = EAGAIN。但是,通过日志记录我观察到传输过程中有数十次超时,流程如下:

  • 第N次循环,select()成功,成功读取256K的数据。
  • 在第 (N 1) 个循环中,select() 因超时而失败。
  • 在第 (N 2) 次循环中,select() 成功并成功读取 256K 的数据。
  • 为什么在接收过程中会出现交错的超时?谁能解释一下这个现象?

    [更新]
    1.上传一个14M的文件到服务器只需要8秒
    2. 使用与1)相同的文件,服务器需要将近30秒的时间将所有数据发送到客户端。
    3. 客户端使用的所有套接字都是阻塞的。服务器使用的所有套接字都是非阻塞的。

    关于#2,我认为超时是#2比#1花费更多时间的原因,我想知道为什么客户端忙于接收数据时会有这么多超时。

    [更新2]
    感谢@Duck、@ebrobe、@EJP、@ja_mesa 的评论,我今天会做更多的调查
    然后更新这篇文章。
    关于为什么我在服务器线程中每个循环发送 512 个字节,这是因为我发现服务器线程发送数据的速度比客户端线程接收它们的速度快得多。我很困惑为什么客户端线程会发生超时。

    • 为什么 GetFilePacketsCount() 与服务器端的 CurrPacket 有任何关系? 512 字节缓冲区的长度是不是有点随意?此外,在服务器端,您似乎会得到一大堆 EAGAIN ,但是当您正确处理它们时应该没问题。也许在 EAGAIN 上进行某种睡眠会是个好主意?等等,GetPacketData 似乎在消耗数据,所以您可能会因为 EAGAIN 多次调用它而造成间隙?
    • 您如何声明/处理 rds?我认为在调用 select(...) 之前,每次循环都需要 FD_SET / FD_ZERO
    • @ebyrob 谢谢,我更新了源代码。令我惊讶的是,在客户端线程忙于接收数据而服务器线程报告数千次EAGAIN失败期间,select调用发生了超时!
    • 如果您使用的是非阻塞套接字,那么它不会等待实际发送数据。数据不会立即传输,有一些速度限制,这就是你得到这些超时的原因
    • @西蒙是的!客户端和服务器都在(慢得多的)网络上等待,而它们的处理器在许多等待状态下竞争......
    • 在写入的情况下,EAGAIN 不是"失败":这意味着套接字发送缓冲区已满。这表明您正在以最大速度书写。类似地,等待套接字变为可写的 select() 超时意味着同样的事情。


    认为这更像是一个长评论而不是答案,但正如一些人所指出的那样,网络比您的处理器慢几个数量级。非阻塞 i/o 的关键在于差异如此之大,以至于您实际上可以使用它来完成实际工作而不是阻塞。在这里,您只是在按电梯按钮,希望有所作为。

    我不确定你的代码有多少是真实的,有多少是为了发布而被砍掉的,但在服务器中你没有考虑 (ret == 0),即对等方的正常关闭。

    客户端中的select 错误。同样,不确定这是否是草率的编辑,但如果不是,那么参数的数量是错误的,但更令人担忧的是,第一个参数 - 即应该是 select 查看的最高文件描述符加一 - 为零。根据 select 的实现,我想知道这是否实际上只是将 select 变成了一个花哨的 sleep 语句。

    • 如果他使用的是 MS WinSock,则忽略第一个参数并仅保留与 Berkeley 套接字的兼容性。无论如何,参数的数量是错误的,我也想知道为什么他使用 1 秒超时并且在 select() 超时时什么都不做。当服务器发送 512 个字节时,他还为每个 read() 创建一个 256K 缓冲区。我认为我们有工具可以使用智慧和良心,否则我们最终可能会得到这种神秘的东西。


    您应该先调用 recv(),然后仅当 recv() 告诉您这样做时才调用 select()。不要先调用select(),那是浪费处理。 recv() 知道数据是立即可用还是必须等待数据到达:

    void *RecvFileThread(void *param){
        int sockfd = (int) param; //blocking socket
        set_sock_rcvbuf(sockfd, 1024 * 256); //set the send buffer to 256

        char buffer[1024 * 256];

        while (1){

            int ret = 0;
            int len = recv(sockfd, buffer, sizeof(buffer), 0);
            if (len == -1) {
                if (WSAGetLastError() != WSAEWOULDBLOCK) {
                    //handle error
                    break;
                }

                struct timeval timeout;
                timeout.tv_sec = 1;
                timeout.tv_usec = 0;

                fd_set rds;
                FD_ZERO(&rds);
                FD_SET(sockfd, &rds)'

                //actually, the first parameter of select() is
                //ignored on windows, though on linux this parameter
                //should be (maximum socket value + 1)
                int ret = select(sockfd + 1, &rds, NULL, &timeout );
                if (ret == -1) {
                    // handle error
                    break;
                }

                if (ret == 0) {
                    // log that timer expires
                    break;
                }

                // socket is readable so try read again
                continue;
            }

            if (len == 0) {
                // handle graceful disconnect
                break;
            }

            //log the number of data it received
            process_tcp_data(buffer, len);
        }
    }

    在发送端也做类似的事情。先调用 send(),然后调用 select() 等待可写性,前提是 send() 告诉你这样做。

    有关关于C#:为什么客户端忙于接收数据时select()有时会超时的更多相关文章

    1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

      类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

    2. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

      我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

    3. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

      我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

    4. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

      我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

    5. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

      为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

    6. ruby - ruby 中的 TOPLEVEL_BINDING 是什么? - 2

      它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput

    7. ruby - Infinity 和 NaN 的类型是什么? - 2

      我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串

    8. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

      如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

    9. ruby - 为什么 SecureRandom.uuid 创建一个唯一的字符串? - 2

      关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?

    10. ruby-on-rails - rspec should have_select ('cars' , :options => ['volvo' , 'saab' ] 不工作 - 2

      关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion在首页我有:汽车:VolvoSaabMercedesAudistatic_pages_spec.rb中的测试代码:it"shouldhavetherightselect"dovisithome_pathit{shouldhave_select('cars',:options=>['volvo','saab','mercedes','audi'])}end响应是rspec./spec/request

    随机推荐