jjzjj

c - 多进程和多线程 C 服务器上的持久连接

coder 2023-09-19 原文

我第三次尝试问这个问题,也许这次我能更好地解释我的问题。

我有一个多进程服务器,每个进程都执行 accept()(避免文件锁定的 Thundering Herd 问题,不用担心)。每个进程都初始化一个线程池(管理其他进程的主要进程除外)。当 accept() 成功时,文件描述符被传递到线程池,并且这些线程之一被 pthread_cond_signal() 唤醒。此后,进程返回文件锁定等待通过它,以便它可以再次等待 accept()。同时,线程读取文件描述符并完成它的工作:读取 HTTP 请求并在读取-服务的无限循环中为它提供服务(为了获得 HTTP 持久连接)。仅当发生错误或超时到期时,循环才会被打破。

到目前为止一切顺利。但是在请求被正确服务之后会发生一些事情:事实上,第一个请求被读取并被完全服务,但是当线程重新启动循环并进入读取循环时它仍然卡住,因为它只读取几个字母,如“GE”或“GET” ",取代了整个请求。如果我删除无限循环(对于持久连接),每个请求都由不同的线程提供服务并且不会发生错误!!

这是阅读周期:

for (;;) {
 ssize_t readn, writen;
 size_t nleft;
 char buff[BUFF_SIZE];
 char *ptr = buff;

 errno = 0;

 nleft = BUFF_SIZE;

 while(nleft > 0) {                             
     //I will read as much as I can using the MSG_DONTWAIT flag making the call non-blocking
     //that means that or the call will succed or it will be closed by the other side
     if ((readn = recv(connsd, ptr, nleft, MSG_DONTWAIT)) < 0) { 

         //If the non-blocking recv fails, it could set errno with one of the following errorcode
         if (errno == EAGAIN || errno == EWOULDBLOCK) {

             //This check has been implemented due to an error that happened several times
             //The buffer was empty even if a new data was sent.
             //This check gives a sort of second chance to the recv.            
             if (strlen(buff) < 10) { 
                 errno = 0;                 //It is important to reset the errno!!
                 continue;
             //If other things occured then I will terminate the string and exit the cicle  
             } else {
                 break;
             }
         // If the conenction has been closed by the client
         } else if (errno == EINTR) readn = 0;
         // If other things occured I will simply shutdown the connection
         else {
             shutdown_sequence(connsd);
             return EXIT_FAILURE;
         }
     // If I read nothing
     } else if (readn == 0) break;

     nleft -= readn;
     ptr += readn;
 }
 buff[strlen(buff)-1] = '\0';
 //request parsing...
 //request serving...
 }

感谢大家的耐心等待!

EDIT1:刚刚尝试使用 Wireshark 以查看发生了什么。第一个请求被正确读取和服务,但随后我收到“继续或非 HTTP 流量”和 [TCP 窗口已满]...我正在 Ubuntu 14.04 的虚拟机上尝试此服务器

EDIT2:我尝试了一个简单的循环:

while(nleft > 0) {
        printf("Entering cylce and reading\n");
        fflush(stdout);
        if ((readn = recv(connsd, ptr, nleft, 0)) > 0) { 
            nleft -= readn;
            ptr += readn;
            printf("reading...\n");
            fflush(stdout);
        }
        if (readn == 0) {
            printf("connection closed or nothing more to read\n");
            fflush(stdout);
            break;
        }
        if (readn == -1) {
            printf("error occurred\n");
            fflush(stdout);
            break;
        }
    }

在终端上我只读到:

Entering cylce and reading
reading...
Entering cylce and reading

虽然 Httperf(使用 --num-calls=2 --num-conns=1 调用)使用了 50% 的 CPU。当我按 Ctrl+C 终止它时,终端打印:

connection closed or nothing more to read 
buff =
GET /1262662405106.jpg HTTP/1.1
User-Agent: httperf/0.9.0
Host: localhost

EDIT3:回应大卫:

while(nleft > 0) {
        printf("I'm going on the read\n");
        fflush(stdout);
        if ((readn = recv(connsd, ptr, nleft, 0)) > 0) { 
            nleft -= readn;
            ptr += readn;
            if (*(ptr-2) == '\r' && *(ptr-1) == '\n') {
                printf("It's an HTTP request\n");
                fflush(stdout);
                break;
            } else  continue;

        } else if (errno == EINTR || readn == 0) {
            break;
        }
    }

它完美地识别了第一个 HTTP 请求,因为它打印了消息。但是对于第二个,它会打印一次“我正在阅读”。当我按 Ctrl+C 时,循环会无限期地继续打印相同的消息。

编辑4: 所以...问题出在 HTTP 响应中... header 错误和字符串分配错误。谢谢你,大卫先生!

最佳答案

如果您要执行非阻塞 I/O 并且不想 100% 消耗 CPU,您必须在代码中留出一些地方让您等待数据到达。你没有这样的代码,所以你在等待数据到达时以 100% 的速度紧紧地燃烧 CPU。听起来你想要阻塞 I/O。 (从摆脱 MSG_DONTWAIT 开始。)

此外,不要使用 strlen 来计算非字符串的长度。如果您需要知道收到了多少字节,请自行跟踪。

in fact, the first request is read and served entirely but when the thread restarts the cycle and enters the read cycle it remains stuck because it reads only few letters like "GE" or "GET", insted of the entire request.

如果您还没有阅读整个请求,请再次调用读取函数,直到您有一个完整的请求。使用阻塞读取。

基本上:

  1. 在缓冲区中已有任何数据之后,对我们的请求缓冲区进行阻塞读取。
  2. 我是遇到错误还是连接已关闭?如果是这样,请停止。
  3. 根据 HTTP 协议(protocol),我是否有完整的请求?如果不是,请转到第 1 步。
  4. 处理请求,发送响应。
  5. 转到第 1 步。

关于c - 多进程和多线程 C 服务器上的持久连接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32180859/

有关c - 多进程和多线程 C 服务器上的持久连接的更多相关文章

  1. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

    我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

  2. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

    在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

  3. ruby - 通过 ruby​​ 进程共享变量 - 2

    我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是

  4. ruby-on-rails - 启动 Rails 服务器时 ImageMagick 的警告 - 2

    最近,当我启动我的Rails服务器时,我收到了一长串警告。虽然它不影响我的应用程序,但我想知道如何解决这些警告。我的估计是imagemagick以某种方式被调用了两次?当我在警告前后检查我的git日志时。我想知道如何解决这个问题。-bcrypt-ruby(3.1.2)-better_errors(1.0.1)+bcrypt(3.1.7)+bcrypt-ruby(3.1.5)-bcrypt(>=3.1.3)+better_errors(1.1.0)bcrypt和imagemagick有关系吗?/Users/rbchris/.rbenv/versions/2.0.0-p247/lib/ru

  5. ruby-on-rails - date_field_tag,如何设置默认日期? [ rails 上的 ruby ] - 2

    我想设置一个默认日期,例如实际日期,我该如何设置?还有如何在组合框中设置默认值顺便问一下,date_field_tag和date_field之间有什么区别? 最佳答案 试试这个:将默认日期作为第二个参数传递。youcorrectlysetthedefaultvalueofcomboboxasshowninyourquestion. 关于ruby-on-rails-date_field_tag,如何设置默认日期?[rails上的ruby],我们在StackOverflow上找到一个类似的问

  6. ruby-on-rails - s3_direct_upload 在生产服务器中不工作 - 2

    在Rails4.0.2中,我使用s3_direct_upload和aws-sdkgems直接为s3存储桶上传文件。在开发环境中它工作正常,但在生产环境中它会抛出如下错误,ActionView::Template::Error(noimplicitconversionofnilintoString)在View中,create_cv_url,:id=>"s3_uploader",:key=>"cv_uploads/{unique_id}/${filename}",:key_starts_with=>"cv_uploads/",:callback_param=>"cv[direct_uplo

  7. ruby-on-rails - openshift 上的 rails 控制台 - 2

    我将我的Rails应用程序部署到OpenShift,它运行良好,但我无法在生产服务器上运行“Rails控制台”。它给了我这个错误。我该如何解决这个问题?我尝试更新ruby​​gems,但它也给出了权限被拒绝的错误,我也无法做到。railsc错误:Warning:You'reusingRubygems1.8.24withSpring.UpgradetoatleastRubygems2.1.0andrun`gempristine--all`forbetterstartupperformance./opt/rh/ruby193/root/usr/share/rubygems/rubygems

  8. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  9. ruby-on-rails - Rails 模型——非持久类成员或属性? - 2

    对于Rails模型,是否可以/建议让一个类的成员不持久保存到数据库中?我想将用户最后选择的类型存储在session变量中。由于我无法从我的模型中设置session变量,我想将值存储在一个“虚拟”类成员中,该成员只是将值传递回Controller。你能有这样的类(class)成员吗? 最佳答案 将非持久属性添加到Rails模型就像任何其他Ruby类一样:classUser扩展解释:在Ruby中,所有实例变量都是私有(private)的,不需要在赋值前定义。attr_accessor创建一个setter和getter方法:classUs

  10. ruby-on-rails - 相关表上的范围为 "WHERE ... LIKE" - 2

    我正在尝试从Postgresql表(table1)中获取数据,该表由另一个相关表(property)的字段(table2)过滤。在纯SQL中,我会这样编写查询:SELECT*FROMtable1JOINtable2USING(table2_id)WHEREtable2.propertyLIKE'query%'这工作正常:scope:my_scope,->(query){includes(:table2).where("table2.property":query)}但我真正需要的是使用LIKE运算符进行过滤,而不是严格相等。然而,这是行不通的:scope:my_scope,->(que

随机推荐