代码之家  ›  专栏  ›  技术社区  ›  user206268

EPIPE块服务器

  •  1
  • user206268  · 技术社区  · 14 年前

    我已经用C编写了一个在Linux上运行的单线程异步服务器:socket是非阻塞的,至于轮询,我使用epoll。基准测试表明服务器性能良好,根据Valgrind的说法,没有内存泄漏或其他问题。

    唯一的问题是,当write()命令被中断时(因为客户端关闭了连接),服务器将遇到一个EPIPE。我通过运行带有参数-b的基准测试实用程序“seave”来人为地中断测试。它在一行中执行许多请求,所有这些请求都可以完美地工作。现在我按下CTRL-C并重新开始“围攻”。有时我很幸运,服务器无法发送完整的响应,因为客户端的fd无效。正如所料,errno被设置为EPIPE。我处理这种情况,在fd上执行close(),然后释放与连接相关的内存。现在的问题是服务器阻塞并且不再正确应答。这是strace输出:

    epoll_wait(4, {{EPOLLIN, {u32=0, u64=0}}}, 128, -1) = 1
    accept(3, {sa_family=AF_INET, sin_port=htons(55328), sin_addr=inet_addr("127.0.0.1")}, [16]) = 5
    fcntl64(5, F_GETFL)                     = 0x2 (flags O_RDWR)
    fcntl64(5, F_SETFL, O_RDWR|O_NONBLOCK)  = 0
    epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLERR|EPOLLHUP|EPOLLET, {u32=144039912, u64=144039912}}) = 0
    epoll_wait(4, {{EPOLLIN, {u32=144039912, u64=144039912}}}, 128, -1) = 1
    read(5, "GET /user/register HTTP/1.1\r\nHos"..., 4096) = 161
    send(5, "HTTP/1.1 200 OK\r\nContent-Type: t"..., 106, MSG_NOSIGNAL) = 106 <<<<
    send(5, "00001000\r\n", 10, MSG_NOSIGNAL) = -1 EPIPE (Broken pipe)        <<<< Why did the previous send() work?
    close(5)                                = 0
    epoll_wait(4, {{EPOLLIN, {u32=0, u64=0}}}, 128, -1) = 1
    accept(3, {sa_family=AF_INET, sin_port=htons(55329), sin_addr=inet_addr("127.0.0.1")}, [16]) = 5
    ...
    

    (我从日志中删除了printf(),以防您怀疑)

    #include "Connection.h"
    
    static ExceptionManager *exc;
    
    void Connection0(ExceptionManager *e) {
        exc = e;
    }
    
    void Connection_Init(Connection *this) {
        Socket_Init(&this->server);
        Socket_SetReusableFlag(&this->server);
        Socket_SetCloexecFlag(&this->server, true);
        Socket_SetBlockingFlag(&this->server, true);
        Socket_ListenTCP(&this->server, 8080, SOMAXCONN);
    
        // Add the server socket to epoll
        Poll_Init(&this->poll, SOMAXCONN, (void *) &Connection_OnEvent, this);
        Poll_AddEvent(&this->poll, NULL, this->server.fd, EPOLLIN | EPOLLET | EPOLLHUP | EPOLLERR);
    
        this->activeConn = 0;
    }
    
    void Connection_Destroy(Connection *this) {
        Poll_Destroy(&this->poll);
        Socket_Destroy(&this->server);
    }
    
    void Connection_Process(Connection *this) {
        Poll_Process(&this->poll, -1);
    }
    
    void Connection_AcceptClient(Connection *this) {
        Client *client;
    
        SocketConnection conn = Socket_Accept(&this->server);
        SocketConnection_SetBlockingFlag(&conn, true);
    
        client = New(Client);
    
        client->req = NULL;
    
        client->conn = New(SocketConnection);
        client->conn->remote = conn.remote;
        client->conn->fd = conn.fd;
    
        this->activeConn++;
    
        Poll_AddEvent(&this->poll, client, conn.fd, EPOLLIN | EPOLLET | EPOLLHUP | EPOLLERR);
    }
    
    void Connection_DestroyClient(Connection *this, Client *client) {
        // Poll_DeleteEvent(&this->poll, client->conn->fd);
        SocketConnection_Close(client->conn);
    
        if (client->req != NULL) {
            Request_Destroy(client->req);
            Memory_Free(client->req);
        }
    
        if (client->conn != NULL) {
            Memory_Free(client->conn);
        }
    
        Memory_Free(client);
    
        this->activeConn--;
    }
    
    void Connection_OnEvent(Connection *this, int events, Client *client) {
        /* error or connection hung up */
        if (client != NULL && (events & (EPOLLHUP | EPOLLERR))) {
            String_Print(String("EPOLLHUP | EPOLLERR received\n"));
            Connection_DestroyClient(this, client);
            return;
        }
    
        /* incoming connection */
        if (client == NULL && (events & EPOLLIN)) {
            if (this->activeConn > SOMAXCONN - 1) { /* TODO */
                String_Print(String("Too many connections...\n"));
                return;
            }
    
            Connection_AcceptClient(this);
            return;
        }
    
        /* receiving data from client */
        if (client != NULL && (events & EPOLLIN)) {
            if (client->req == NULL) {
                client->req = New(Request);
                Request_Init(client->req, client->conn);
            }
    
            bool keepOpen = false;
    
            try (exc) {
                keepOpen = Request_Parse(client->req);
            } catch(&SocketConnection_PipeException, e) {
                printf("Caught PipeException on fd=%d\n", client->conn->fd); fflush(stdout);
            } catch(&SocketConnection_ConnectionResetException, e) {
                printf("Caught ConnectionResetException on fd=%d\n", client->conn->fd); fflush(stdout);
            } finally {
                if (!keepOpen) {
                    printf("Will close...\n"); fflush(stdout);
                    Connection_DestroyClient(this, client);
                }
            } tryEnd;
        }
    }
    
    2 回复  |  直到 14 年前
        1
  •  6
  •   mark4o    14 年前

    使用 sigaction() SIGPIPE SIG_IGN . 然后您将只得到返回代码-1 errno 设置为 EPIPE

    在Linux上,另一种选择是使用 send() MSG_NOSIGNAL 标志,而不是 write() SO_NOSIGPIPE 套接字选项,该选项禁止 对于该套接字上的所有写入。

    EPIPE公司 信号管 信号,如果它没有被抑制。因此,即使是连续两次写入,第一次也可能成功,下一次可能会失败。

    更新: fcntl64(5, F_SETFL, O_RDONLY|O_NONBLOCK) . 自 O_RDONLY 是0,可能您的代码只是将标志设置为 O_NONBLOCK . 它应该获取当前标志,然后将其设置为 OldFlags | O_NONBLOCK F_GETFD 而不是 F_GETFL 去拿旧旗子。

        2
  •  1
  •   Jonathan Leffler    14 年前