C HTTP 套接字过早关闭并明显出现读取错误

C HTTP sockets closing prematurely and apparently giving read errors

td;lr:试图将 "Hello World" 回显到 HTTP 客户端,但遇到套接字关闭过快的问题以及来自 wrk 基准工具的神秘读取错误。

我正在尝试使用 picoev 事件循环库制作一个简单的 "Hello World" HTTP 服务器,但是 client/peer 连接掉线太快了,wrk 基准测试工具 returns出于我不知道的任何原因读取错误。这是我正在使用的代码:

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include "picoev.h"


#define HOST 0 /* 0x7f000001 for localhost */
#define PORT 8080
#define MAX_FDS 1024 * 128
#define TIMEOUT_SECS 10

char buf[1024];
ssize_t response;
int listen_sock;


static void close_conn(picoev_loop* loop, int fd)
{
  picoev_del(loop, fd);
  close(fd);
}

static void write_callback(picoev_loop* loop, int fd, int events, void* cb_arg)
{
  // check whether neither events nor timeouts are present
  if ((events & PICOEV_TIMEOUT) != 0) {
    /* timeout */
    close_conn(loop, fd);

  } else if ((events & PICOEV_READ) != 0) {
    /* update timeout, and read */
    picoev_set_timeout(loop, fd, TIMEOUT_SECS);
    ret = read(fd, buf, sizeof(buf));


    if (ret == 0 | ret == -1) {
      close_conn(loop, fd);
    }

    else {
      write(fd, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 13\r\nConnection: close\r\n\r\nHello, world!", ret);
      close_conn(loop, fd);
    }

  }
}

static void accept_callback(picoev_loop* loop, int fd, int events, void* cb_arg)
{
  int newfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC);
  if (newfd != -1) {
    picoev_add(loop, newfd, PICOEV_READ, TIMEOUT_SECS, write_callback, NULL);
  }
}

int main(void)
{
  picoev_loop* loop;

  /* listen to port */
  listen_sock = socket(AF_INET, SOCK_STREAM, 0);
  setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, 1, sizeof(1));
  struct sockaddr_in listen_addr;
  listen_addr.sin_family = AF_INET;
  listen_addr.sin_port = htons(PORT);
  listen_addr.sin_addr.s_addr = htonl(HOST);
  bind(listen_sock, (struct sockaddr*)&listen_addr, sizeof(listen_addr));
  listen(listen_sock, 1000000);

  /* init picoev */
  picoev_init(MAX_FDS);
  /* create loop */
  loop = picoev_create_loop(60);
  /* add listen socket */
  picoev_add(loop, listen_sock, PICOEV_READ, 1, accept_callback, NULL);
  /* loop */
  while (1) {
    // Picoev async call to write etc..
    picoev_loop_once(loop, 10);
  }
  /* cleanup */
  picoev_destroy_loop(loop);
  picoev_deinit();

  return 0;
}

冰壶 curl http://0.0.0.0:8080/ -v returns:

*   Trying 0.0.0.0...
* TCP_NODELAY set
* Connected to 0.0.0.0 (127.0.0.1) port 8080 (#0)
> GET / HTTP/1.1
> Host: 0.0.0.0:8080
> User-Agent: curl/7.52.1
> Accept: */*
> 
< HTTP/1.1 200 OK
< Content-Type: text/html
< Content-Length: 13
* transfer closed with 13 bytes remaining to read
* Curl_http_done: called premature == 1
* stopped the pause stream!
* Closing connection 0
curl: (18) transfer closed with 13 bytes remaining to read

或在多次尝试对数千个并发连接进行基准测试后出现以下情况:

*   Trying 0.0.0.0...
* TCP_NODELAY set
* connect to 0.0.0.0 port 8080 failed: Connection refused
* Failed to connect to 0.0.0.0 port 8080: Connection refused
* Closing connection 0
curl: (7) Failed to connect to 0.0.0.0 port 8080: Connection refused

wrk -t1 -c400 http://0.0.0.0:8080/ returns 正在读取所有错误:

Running 10s test @ http://0.0.0.0:8080/
  1 threads and 400 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     0.00us    0.00us   0.00us    -nan%
    Req/Sec     0.00      0.00     0.00      -nan%
  0 requests in 10.08s, 9.05MB read
  Socket errors: connect 0, read 249652, write 0, timeout 0
Requests/sec:      0.00
Transfer/sec:      0.90MB

我不明白问题是套接字关闭太快、响应(ret) 不正确、僵尸fd 没有被杀死还是它们的组合。尝试跟踪程序并没有提供任何关于问题所在的有价值的信息,只是提供了很多 epoll_wait 的信息。我已经尝试了许多 HTTP 响应变体,但都无济于事,正如您所看到的,我正试图在必要时尽快杀死任何僵尸或错误的 fd,但要么我做错了,要么问题出在其他地方。有人可以帮我查明问题所在吗?

在这行代码中:

write(fd, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 13\r\nConnection: close\r\n\r\nHello, world!", ret);

您使用 ret 作为调用 write() 的第三个参数。此参数用于指示 write() 应写入多少字节。

然而,ret 用于存储调用 read() 的结果。因此,传递给 write() 的值与您要发送的消息的大小之间没有任何关系。

通过使用您要发送的消息的长度初始化 ret 来解决此问题。

const char *msg = "HTTP/1.1 ...";
ret = strlen(msg);
write(fd, msg, ret);