我已经编写了一个简单的HTTP服务器来研究select
函数的工作原理。
有代码:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/capsicum.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
static const char ok_response[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Content-Length: 76\r\n"
"\r\n"
"<html>"
"<head>"
"<title> Kokoko </title>"
"</head>"
"<body>"
"Hello kokoko"
"</body>"
"</html>"
"\r\n";
struct write_state
{
const char *ptr;
size_t position;
size_t length;
};
static struct write_state states[FD_SETSIZE];
int main ()
{
int res;
int acceptor = socket (PF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (acceptor == -1) {
perror ("Error creating socket");
goto end;
}
int optval = 1;
setsockopt(acceptor, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof (optval));
struct sockaddr_in addr;
memset (&addr, 0, sizeof (struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
res = bind (acceptor, (struct sockaddr*) &addr, sizeof (struct sockaddr));
if (res == -1) {
perror ("bind() error");
goto end;
}
res = listen (acceptor, 50);
if (res == -1) {
perror ("listen() error");
goto end;
}
fd_set acceptor_set;
fd_set connection_set;
int i;
FD_ZERO (&acceptor_set);
FD_ZERO (&connection_set);
FD_SET (acceptor, &acceptor_set);
res = cap_enter();
if (res == -1) {
perror ("cap_enter() error");
goto end;
}
cap_rights_t acc_rights;
cap_rights_limit (acceptor, cap_rights_init (&acc_rights, CAP_ACCEPT,
CAP_READ, CAP_WRITE, CAP_EVENT,
/* FIXME */ CAP_FCNTL, CAP_SHUTDOWN));
while (1) {
fd_set read_set = acceptor_set;
fd_set write_set = connection_set;
res = select (FD_SETSIZE, &read_set, &write_set, NULL, NULL);
if (res == -1 && errno != EINTR) {
perror ("select() error");
goto end;
}
if (res > 0) {
if (FD_ISSET (acceptor, &read_set)) {
int fd = accept (acceptor, NULL, NULL);
if (fd == -1) {
perror ("accept() error");
goto end;
}
int flags = fcntl (fd, F_GETFL);
if (flags == -1) {
perror ("fcntl() error");
goto end;
}
flags = fcntl (fd, F_SETFL, flags | O_NONBLOCK);
if (flags == -1) {
perror ("fcntl() error");
goto end;
}
states[fd].ptr = ok_response;
states[fd].position = 0;
states[fd].length = strlen (ok_response);
FD_SET (fd, &connection_set);
}
for (i=0; i<FD_SETSIZE; i++) {
if (FD_ISSET (i, &write_set)) {
int length = write (i, states[i].ptr + states[i].position,
states[i].length - states[i].position);
if (length == -1) {
perror ("write error");
goto end;
}
states[i].position += length;
if (states[i].position == states[i].length) {
res = shutdown (i, SHUT_RDWR);
if (res == -1) {
perror ("shutdown() error");
goto end;
}
char buffer[32];
int length;
for (;;) {
length = read (i, buffer, sizeof (buffer));
if (length == -1)
{
perror ("read");
break;
}
if (length == 0) break;
}
close (i);
FD_CLR (i, &connection_set);
}
}
}
}
}
end:
if (acceptor != -1) close (acceptor);
for (i=0; i<FD_SETSIZE; i++) {
if (FD_ISSET (i, &connection_set)) close (i);
}
return 0;
}
它会在地址localhost:8080
侦听连接,直到您点击C-c。当我使用像这样的httperf测试这个服务器时:
httperf --hog --client=0/1 --server=localhost --port=8080 --uri=/ --rate=9000 --send-buffer=4096 --recv-buffer=16384 --ssl-protocol=auto --num-conns=10000 --num-calls=1
Maximum connect burst length: 10
Total: connections 10000 requests 10000 replies 10000 test-duration 1.111 s
Connection rate: 8998.0 conn/s (0.1 ms/conn, <=10 concurrent connections)
Connection time [ms]: min 0.1 avg 0.2 max 0.4 median 0.5 stddev 0.0
Connection time [ms]: connect 0.1
Connection length [replies/conn]: 1.000
Request rate: 8998.0 req/s (0.1 ms/req)
Request size [B]: 62.0
Reply rate [replies/s]: min 0.0 avg 0.0 max 0.0 stddev 0.0 (0 samples)
Reply time [ms]: response 0.1 transfer 0.0
Reply size [B]: header 64.0 content 76.0 footer 0.0 (total 140.0)
Reply status: 1xx=0 2xx=10000 3xx=0 4xx=0 5xx=0
CPU time [s]: user 0.04 system 0.30 (user 3.3% system 27.0% total 30.3%)
Net I/O: 1775.0 KB/s (14.5*10^6 bps)
Errors: total 0 client-timo 0 socket-timo 0 connrefused 0 connreset 0
Errors: fd-unavail 0 addrunavail 0 ftab-full 0 other 0
我在LAST_ACK状态下获得了太多(数千个)套接字。 Netstat输出如下:
tcp4 0 62 127.0.0.1.1696 127.0.0.1.8080 LAST_ACK
当服务器负载不重时,LAST_ACK
状态中没有连接。
根据我对LAST_ACK
状态的了解,我可以假设连接正在关闭,发送FIN
,并等待最后ACK
关闭连接。我还发现这个图非常有用:http://www4.cs.fau.de/Projects/JX/Projects/TCP/tcpstate.html
我仍然不明白发生了什么,是否正常。我想也许我会以错误的方式关闭连接。我是否在提供的代码中正确执行此操作?当我有数据要读取时,我可以安全地关闭TCP连接吗?如您所见,我完全忽略HTTP请求并始终发送相同的响应。
P.S。使用httperf以相同的方式测试nginx会在TIME_WAIT
状态下留下大量连接,但我认为这是正常的。