1

私は単純なアプリケーションを C で作成しました。このアプリケーションは、1 つの子をネットワーク サーバーとして機能させ、多くの子をネットワーク クライアントとして機能させます。クライアントはサーバーに接続し、データを要求します。コードは次のとおりです。

#include <stdio.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/queue.h>
#include <strings.h>
#include <sys/wait.h>


#define LISTENSOCKET 1519
#define MAXCLIENT 200
#define MAXLINE 80


void client_do_something(int c) 
{
    printf("Process %d, server gave %d\n", getpid(), c);
}


void client_body()
{
    struct sockaddr_in clientaddr;
    struct sockaddr_in localaddr;
    int sockfd;
    int nread;
    int s;
    char serveraddr[20] = "127.0.0.1";
    char laddr[20];
    char command[4] = "GET";
    int c;
    command[3] = '\0';
    socklen_t len;

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    clientaddr.sin_family = AF_INET;
    clientaddr.sin_port = htons(LISTENSOCKET);
    inet_pton(AF_INET, serveraddr, &clientaddr.sin_addr);
    printf("Client %d started\n", getpid());
    if ((s=connect(sockfd, (struct sockaddr *) &clientaddr, sizeof(clientaddr))) != 0) {
        perror("conn err:");
        printf("Connect error: pid %d %d\n", getpid(), errno);
        close(sockfd);
        sleep(1);
        exit(1);
    }
    getsockname(sockfd, (struct sockaddr *) &localaddr, &len);
    inet_ntop(AF_INET,  &localaddr.sin_addr, laddr, sizeof(laddr));
    printf("Client %d passed connect (%d), %s:%d \n", getpid(), s, laddr,     ntohs(localaddr.sin_port));
    while(1) {
        send(sockfd, command, 4, 0);
        if ( (nread = recv(sockfd, &c, 4, 0)) < 0 ) {
            if (errno == ENOTCONN) {
                sleep(1);
                continue;
            }
            perror("client recv err:");
            printf("Client %d received error %d ", getpid(), errno);
            exit(1);
        } else if (nread == 0) {
            printf("Pid %d received FIN\n", getpid());
            close(sockfd);
            exit(0);
        }
        client_do_something(c);
    }
}


int start_server() {
    int i, nread, maxi, val, listenfd, connfd, sockfd, maxfd, nready, client[MAXCLIENT];
    socklen_t len;
    char *c;
    struct sockaddr_in servaddr, clientaddr, localaddr;
    fd_set rset, allset;
    char addr[MAXLINE];
    char laddr[INET_ADDRSTRLEN];
    struct timeval timeout;
    printf("Started\n");
    printf("Server PID=%d", getpid());
    val = 0;

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(LISTENSOCKET);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        perror("socket failed\n");


    if(bind(listenfd, (struct sockaddr *) &servaddr, (socklen_t) sizeof(servaddr)) == -1) 
        perror("listen failed\n");
    getsockname(listenfd, (struct sockaddr *) &localaddr, &len);
    inet_ntop(AF_INET,  &localaddr.sin_addr, laddr, INET_ADDRSTRLEN);
    printf("Server %d passed connect , %s:%d \n", getpid(),  laddr, ntohs(localaddr.sin_port));
    if(listen(listenfd, 10) == -1) 
        perror("listen failed\n");

    maxi = -1;
    for(i=0; i < MAXCLIENT; i++)
    client[i] = -1;
    maxfd = listenfd;
    FD_ZERO(&allset);
    FD_SET(listenfd, &allset);

    bzero(&timeout, sizeof(struct timeval));
    timeout.tv_sec = 15;
    printf("Server process, after listen, sleep 5s before accept\n");
/* Here I purpousely sleep because I want client to initiate connection before accept */
    sleep(5);
    printf("Server porcess, slept for 5s\n");
    while(1) {
    rset = allset;
    nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
    if (FD_ISSET(listenfd, &rset)) {
        len = sizeof(clientaddr);
        bzero(&clientaddr, sizeof(clientaddr));
        if((connfd = accept(listenfd, (struct sockaddr *) &clientaddr, &len)) < 0) {
        perror("accept failed");
        exit(-1);
        }
        for(i=0; i < MAXCLIENT; i++) {
        if(client[i] < 0) {
            client[i] = connfd;
            break;
        }
        } 
        FD_SET(connfd, &allset);
        if(connfd >= maxfd)
        maxfd = connfd;
        if(i > maxi)
        maxi = i;
        if(--nready <= 0) 
        continue;
    }
    for(i=0; i <= maxi; i++) {
        if ( (sockfd = client[i]) < 0)
        continue;
        if(FD_ISSET(sockfd, &rset)) {
        if( (nread = read(sockfd, addr, MAXLINE)) < 0) {
            if(errno == EINTR) 
            nread = 0;
            else {
            printf("Izlazim");
            return -1;
            }
        } else if (nread == 0) {
            close(sockfd);
            FD_CLR(sockfd, &allset);
            client[i] = -1;
                    continue;
        }

        if (strncmp(addr, "GET", 3) == 0) {
                    if(val < 100) {
                        i = send(sockfd, (int *) &val, (size_t) sizeof(val), 0);
                        val++;
                        if (i == -1)
                            printf("nread=%d, errno=%d\n",nread, errno); 
                    } else {
                        FD_CLR(sockfd, &allset);
                        client[i] = -1;
                        close(sockfd);
                    }
            } else {
            c = addr;
                    printf("Poslao je addr=%s\n", addr);
            }
            if(--nready <= 0) 
            break;
        }
    }
    }
}


int main(int argc, char *argv[]) {
    printf("Pid=%d",getpid());
    int i, child_num, status;
    pid_t p, pp;
    child_num = 100;
    printf("%d childs, pid=%d \n", child_num, getpid());
    p = fork();
    if(p == 0) {
        start_server();
        exit(0);
    } else if (p < 0) {
        exit(1);
    }
    pp = p;
    for(i = 0; i < child_num; i++) {
        p = fork();
        if (p < 0) {
            exit(1);
        } else if (p == 0){
            client_body();
            exit(0);
        }
    }
    for(i = 0; i < child_num; i++) {
        wait(&status);
    }
    kill(pp, SIGTERM);
    return 0;
}

問題は connect() システム コール (client_body 関数内) にあります。man ページによると、connect は、接続が成功した場合は 0 を返し、エラーの場合は -1 を返す必要があります。私のプログラムでは、サーバーとの接続を確立していないにもかかわらず、connect() が 0 を返したことに気付きました (サーバーから SYN ACK を受信しました)。その後、プログラム内で、同じ子プロセスが recv システム コールを発行し、errno 値 104 (ECONNRESET) のエラーが発生しました。Wireshark のキャプチャをずっと見ていましたが、サーバーから送信された TCP パケットに RESET フラグがあることに気づきませんでした。

誰が何が悪いのか分かりますか?Linux、2.6.38-8-generic カーネルでこのコードをテストしました。

4

2 に答える 2

1

クライアントが接続を試行する前にサーバーが確実に開始されるように、main() に遅延を挿入する必要がある場合があります。

睡眠 (1); // 2 つのフォークの間で行う必要があります

接続はいつでもリセットできます。

あなたの fork() レートはおそらく listen() バックログに対して速すぎます。10 から SOMAXCONN に増やします。接続を試みる各クライアント間に nanosleep() サブセカンド スリープを挿入します。おそらく 10ms で済みます。これを超えると、クライアントには ECONNRESET が表示されます。

クライアントからの exit() の前に sleep(1) がある理由がわかりません。あなたが達成しようとしていると私が思うものについて、オンラインで setsocketopt() 残留設定を調べて、すぐに終了してください。いくつかのリンク: https://lists.mindrot.org/pipermail/openssh-unix-dev/2002-September/015275.html (SO_LINGER 使用の C コードを含む )まったくソリンガー? (Java 指向ですが、同じメカニズムに関連しています)

サーバー上で sleep(5) する必要はありません。すぐに聞くことができます。この 5 秒の遅延は、サーバー側が着信接続で過負荷になるため、connect() 中に ECONNRESET() を確認するために listen() バックログが 10 に設定されます。

改善として、recv() のほとんどのエラーは、接続を終了する理由です。EWOULDBLOCK/EAGAIN および EINTR を除く。つまり、 ENOTCONN はソケットの使用を終了するエラーです (UDP の場合、fd を開いたままにしておく場合がありますが、TCP を使用していない場合は、ソケットが回復することはありません)。この2つのケースだけです

if(recv(fd, ...) < 0) {
  if(errno != EWOULDBLOCK && errno != EINTR) {
    // print out
    close(fd);
    exit(1);
  }
}

FWIW 私はあなたの質問に直接答えていないことを知っていますが、コードに基づいたあなたの主張は信じていません. getpid() が %05d 形式の行の先頭にあるようにすべてのログ出力を再編成する場合は、プログラムを実行し、結果のログ ファイルを、発生した順序の証拠として貼り付けます。

于 2012-11-03T20:25:56.450 に答える
1

これは前に見たことがあります。localhost に接続すると、実際には成功しなかったにもかかわらず、connect() が成功したように見える場合があり、最初の I/O 操作まではわかりません。ただし、最初の I/O 操作である send() のエラーを無視しているため、チェックする 2 番目の I/O 操作である recv() でエラーを取得しています。send() も確認してください。

これを最後に見たのは 20 年前のことで、どうやって回避したか忘れてしまいました。connect() の後に getsockopt() を介して SO_ERROR を調べることができます。

于 2012-11-03T21:37:06.717 に答える