1

Linux上で実行されているCでシングルスレッドの非同期サーバーを作成しました。ソケットは非ブロッキングであり、ポーリングに関しては、epollを使用しています。ベンチマークは、サーバーが正常に動作し、Valgrindによると、メモリリークやその他の問題がないことを示しています。

唯一の問題は、write()コマンドが中断された場合(クライアントが接続を閉じたため)、サーバーでEPIPEが発生することです。パラメータ-bを指定してベンチマークユーティリティ「siege」を実行することにより、人為的に割り込みを実行しています。それはすべてが完全に機能する連続した多くの要求を実行します。ここで、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()を削除しました)

ご覧のとおり、クライアントは新しい接続を確立し、その結果、受け入れられます。次に、EPOLLキューに追加されます。epoll_wait()は、クライアントがデータを送信したことを通知します(EPOLLIN)。要求が解析され、応答が作成されます。ヘッダーの送信は正常に機能しますが、本文に関しては、write()の結果がEPIPEになります。どのクライアントからの着信接続もブロックするため、「包囲」のバグではありません。

#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;
    }
}
4

2 に答える 2

6

sigaction()のアクションを に設定するために使用SIGPIPESIG_IGNます。次に、シグナルなしで、にerrno設定された-1 の戻りコードを取得します。EPIPE

Linux では、別の方法として、 の代わりにフラグを使用send()します。これにより、他に影響を与えることなく、その書き込みの信号を抑制することができます。BSD システムでの代替手段はsocket オプションで、そのソケットへのすべての書き込みを抑制します。MSG_NOSIGNALwrite()SO_NOSIGPIPESIGPIPE

カーネル TCP 実装はいつでも、ピアから TCP RST を受信する可能性があります。その時点以降のソケットへの次の書き込みはEPIPEエラーになり、SIGPIPEそれが抑制されていない場合はシグナルになります。そのため、2 回連続して書き込みを行った場合でも、最初の書き込みは成功し、次の書き込みは EPIPE と SIGPIPE で失敗する可能性があります。

更新:投稿されたコードの一部ではありませんが、strace の出力で、あなたが呼び出していることがわかりますfcntl64(5, F_SETFL, O_RDONLY|O_NONBLOCK)。は 0 であるためO_RDONLY、おそらくコードはフラグを に設定するだけO_NONBLOCKです。OldFlags | O_NONBLOCKノンブロッキング モードを設定するには、現在のフラグを取得してから設定する必要があります。ソケットを読み取り専用モードに設定すると、書き込み時に問題が発生する可能性があります。または、古いフラグを取得するF_GETFD代わりに誤って使用した可能性があります。F_GETFL

于 2010-03-28T20:38:40.343 に答える
1

記述子の 1 つから SIGPIPE を取得した後に epoll 構造を調整するかどうかは、コードからはわかりません (おそらく、適切な場所を調べていないためです)。ファイル記述子から SIGPIPE (または EPIPE) を取得すると、その後同じファイル記述子を使用すると、繰り返しになります。ファイル記述子を閉じてから、サーバーの内部構造を適切に調整する必要があります。

于 2010-03-28T22:27:40.320 に答える