td;lr: trying to echo "Hello World" to an HTTP client but getting issues with the socket closing too soon and mysterious read errors from wrk benchmark tool.
I am trying to make a simple "Hello World" HTTP server with the picoev event loop library but the client/peer connection is dropping too soon and wrk benchmark tool returns read errors for whatever reason I'm not aware. This is the code I'm using:
#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;
}
Curling with 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
or the following after trying to benchmark thousands of concurrent connections a few times after another:
* 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
and wrk -t1 -c400 http://0.0.0.0:8080/
returns all errors being read:
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
I don't understand if the problem is either the socket closing too soon, the response (ret) being incorrect, zombie fd's not being killed or a combination of them. Trying to strace the program doesn't give any valuable info as to where the issue lies, just a lot of epoll_wait's. I've already tried many HTTP response variations to no avail and as you can see I'm trying to kill any zombie or erring fd as soon as necessary but either I'm doing it wrong or the problem lies elsewhere. Can someone help me pinpoint the issue where it belongs?
In this line of code:
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);
You use ret
for the third parameter of your call to write()
. This parameter is used to indicate to write()
how many bytes should be written.
However, ret
was used to store the result of a call to read()
. Thus, there is no relationship between the value passed to write()
and the size of the message you want to send.
Fix this by initializing ret
with the length of the message you want to send.
const char *msg = "HTTP/1.1 ...";
ret = strlen(msg);
write(fd, msg, ret);