1

我用 C 语言编写了一个简单的应用程序,它派生一个孩子充当网络服务器,而许多孩子充当网络客户端。客户端连接到服务器,并请求数据。这是代码:

#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 函数中);根据手册页,如果连接成功,连接应该返回 0,错误时返回 -1。我注意到,在我的程序中,connect() 返回 0 尽管它没有与服务器建立连接(从服务器接收到 SYN ACK)。后来,在程序中,同一个子进程发出了recv系统调用,产生了errno值为104(ECONNRESET)的错误。我一直在观看wireshark 捕获,并没有注意到服务器发送的任何TCP 数据包中的RESET 标志。

有人知道出了什么问题吗?我已经在 linux,2.6.38-8-generic 内核上测试了这段代码。

4

2 回答 2

1

我以前见过这个。当连接到 localhost 时,connect() 看起来会成功,即使它实际上并没有成功,并且直到第一次 I/O 操作才发现。但是,因为您忽略了第一个 I/O 操作 send() 中的错误,所以您会在第二个操作 recv() 中得到它,您可以在其中进行检查。检查 send() 太。

上次我看到这是二十年前,我忘记了我是如何绕过它的。您可以尝试在 connect() 之后通过 getsockopt() 查看 SO_ERROR。

于 2012-11-03T21:37:06.717 回答
1

您可能需要在 main() 中插入延迟,以确保在客户端尝试连接之前启动服务器。

睡眠(1);// 应该在 2 个分叉之间进行

可以随时重置连接。

对于listen() 积压工作,您的fork() 速率可能太快了。从 10 增加到 SOMAXCONN。在每个尝试连接的客户端之间插入一个 nanosleep() 亚秒睡眠,也许 10 毫秒就可以了。当您超过此值时,客户端将看到 ECONNRESET。

不知道为什么在客户端的 exit() 之前有 sleep(1) 。在线查看 setsocketopt() 逗留设置,了解我认为您正在尝试实现的内容并立即退出。一些链接: https ://lists.mindrot.org/pipermail/openssh-unix-dev/2002-September/015275.html (包含SO_LINGER使用的C代码) 用0值调用setSoLinger和不启用有什么区别soLinger 呢? (面向Java但与相同机制相关)

无需在服务器上 sleep(5)。你可以马上听。这 5 秒的延迟会将 listen() 积压设置为 10,以便在 connect() 期间查看 ECONNRESET(),因为服务器端的传入连接超载。

作为一项改进,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 回答