为什么在LAST_ACK状态下有这么多连接?

时间:2017-08-26 18:07:00

标签: sockets tcp

我已经编写了一个简单的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状态下留下大量连接,但我认为这是正常的。

0 个答案:

没有答案