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

Persistent Connection on a multiprocessed and multithreaded C server

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

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

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

这是阅读周期:

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] = '[=10=]';
 //request parsing...
 //request serving...
 }

感谢大家的耐心等待!

EDIT1:刚刚尝试使用 Wireshark 以查看发生了什么。第一个请求被正确读取和服务,但随后我收到 "Continuation or non-HTTP Traffic" 和 [TCP Window Full]...我正在 Ubuntu 14.04[= 中的虚拟机上尝试此服务器15=]

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 调用)使用了 CPU 的 50%。当我按 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 请求,因为它打印了消息。但是对于第二个,它打印 "I'm going on the read" 一次。当我按 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协议请求?如果不是,请转到步骤 1。
  4. 处理请求,发送响应。
  5. 转到步骤 1。