TCP套接字,服务器无法响应客户端,接受:系统调用中断

ton*_*ger 1 c sockets signals tcp interrupt

我正在尝试在Solaris上运行的C语言中实现TCP服务器和客户端。我是套接字的新手,并以Beej指南为例。

对于初学者来说,我希望客户端以的形式向服务器发送消息word1 word2。收到后,我希望服务器word2从消息中提取并将其发送回客户端。

初始客户端->服务器消息发送工作正常。但是服务器->客户端响应不起作用。有几种故障症状:

  1. 服务器似乎甚至没有尝试对send()客户端执行任何操作。
  2. 接收到客户端的消息后,服务器将打印:accept: Interrupted system call,然后返回到while(1)循环的顶部并保持在那里,直到我按Ctrl-C为止。
  3. 客户端的调用recv()返回0个字节。

我在这里找到一个旧线程,最后一个帖子说:

子进程终止时,accept被子进程中断,它将信号发送回父进程(SIGCHLD,如果我还记得写的话)。您可以忽略SIGCHLD,也可以编写accept()以便更好地处理中断(errno设置为EINTR)

但是,我不明白这一点。为什么子进程在尝试该send()部分之前就终止了?“更好地处理中断”是什么意思?

搜索更多内容后,我在堆栈溢出上发现了这个问题:如何处理EINTR(中断的系统调用)

我尝试将代码添加到接受的答案中,替换write()send(),但是我仍然看到相同的行为。

服务器代码:

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

const char* nodename = "localhost";
const char* FILESERV1_LISTEN_PORT = "41063";
const unsigned int MAXDATASIZE = 100;

void sigchld_handler(int s)
{
// from http://beej.us/guide/bgnet/examples/server.c
    while(waitpid(-1, NULL, WNOHANG) > 0);
}

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
// from http://beej.us/guide/bgnet/examples/server.c
    if (sa->sa_family == AF_INET) {
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(void)
{
    // from http://beej.us/guide/bgnet/examples/server.c
    int rv;
    const unsigned int BACKLOG = 3; // how many pending connections queue will hold
    int tcp_sockfd, new_tcp_sockfd;  // listen on tcp_sockfd, new connection on new_tcp_sockfd
    struct addrinfo fs_hints, *fileservinfo, *p_fsinfo;
    struct sockaddr_storage client_addr; // connector's address information
    socklen_t sin_size;
    struct sigaction sa;
    char yes='1'; // char for Solaris
    char s[INET6_ADDRSTRLEN];
    int tcp_numbytes, tcp_numbytes_written, size;
    char tcp_recv_buf[MAXDATASIZE];
    char *word1, *word2;

    memset(&fs_hints, 0, sizeof fs_hints);
    fs_hints.ai_family = AF_INET; // force IPv4
    fs_hints.ai_socktype = SOCK_STREAM;
    //fs_hints.ai_flags = AI_PASSIVE; // use my IP

    if ((rv = getaddrinfo(nodename, FILESERV1_LISTEN_PORT, &fs_hints, &fileservinfo)) != 0) {
        fprintf(stderr, "file_server1: getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }

    // loop through all the results and bind to the first we can
    for(p_fsinfo = fileservinfo; p_fsinfo != NULL; p_fsinfo = p_fsinfo->ai_next) {
        if ((tcp_sockfd = socket(p_fsinfo->ai_family, p_fsinfo->ai_socktype,
                p_fsinfo->ai_protocol)) == -1) {
            perror("file_server1: socket");
            continue;
        }

        if (setsockopt(tcp_sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,
                sizeof(int)) == -1) {
            perror("file_server1: setsockopt");
            exit(1);
        }

        if (bind(tcp_sockfd, p_fsinfo->ai_addr, p_fsinfo->ai_addrlen) == -1) {
            close(tcp_sockfd);
            perror("file_server1: bind");
            continue;
        }

        break;
    }

    if (p_fsinfo == NULL)  {
        fprintf(stderr, "file_server1: failed to bind\n");
        return 2;
    }

    freeaddrinfo(fileservinfo); // all done with this structure

    if (listen(tcp_sockfd, BACKLOG) == -1) {
        perror("file_server1: listen");
        exit(1);
    }

    sa.sa_handler = sigchld_handler; // reap all dead processes
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    if (sigaction(SIGCHLD, &sa, NULL) == -1) {
        perror("file_server1: sigaction");
        exit(1);
    }

    printf("DEBUG: file_server1: waiting for connections...\n");

    //signal(SIGCHLD, SIG_IGN);  /* now I don't have to wait()! */

    while(1) {  // main accept() loop
        printf("DEBUG: Top of while loop\n");
        sin_size = sizeof client_addr;
        new_tcp_sockfd = accept(tcp_sockfd, (struct sockaddr *)&client_addr, &sin_size);
        if (new_tcp_sockfd == -1) {
            perror("file_server1: accept");
            continue;
        }

        inet_ntop(client_addr.ss_family, get_in_addr((struct sockaddr *)&client_addr), s, sizeof s);
        printf("DEBUG: file_server1: got connection from %s\n", s);

        if (!fork()) { // this is the child process
            printf("DEBUG: inside if\n");
            close(tcp_sockfd); // child doesn't need the listener
            if ((tcp_numbytes = recv(new_tcp_sockfd, tcp_recv_buf, MAXDATASIZE-1, 0)) == -1) {
                perror("file_server1: recv");
                exit(1);
            }
            tcp_recv_buf[tcp_numbytes] = '\0';
            printf("DEBUG: file_server1: received: %s\n", tcp_recv_buf);

            sscanf(tcp_recv_buf, "%s %s", word1, word2);
            printf("DEBUG: file_server received word1: %s and word2: %s.\n", word1, word2);

            size = strlen(word2);
            while (size > 0) {
                printf("DEBUG: top of inner while size\n");
                tcp_numbytes_written = send(new_tcp_sockfd, word2, strlen(word2), 0);
                if (tcp_numbytes_written == -1) {
                    if (errno == EINTR) {
                        printf("DEBUG: continuing after EINTR\n");
                        continue;
                    }
                    else {
                        printf("DEBUG: -1 on send(), not EINTR\n");
                        perror("DEBUG: file_server1: send");
                        return -1;
                    }
                }
                word2 += tcp_numbytes_written;
                size -= tcp_numbytes_written;
                printf("DEBUG: bottom of inner while size\n");
            }
            printf("DEBUG: file_server has sent %s to client.\n", word2);
            close(new_tcp_sockfd);
            //exit(0);
        }
        printf("DEBUG: outside of if\n");
        close(new_tcp_sockfd);  // parent doesn't need this
        printf("DEBUG: Bottom of while loop\n");
    }

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

客户代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

const char* nodename = "localhost";
const char* FILESERV1_LISTEN_PORT = "41063";
const unsigned int MAXDATASIZE = 100; // max number of bytes we can get at once

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
    // http://beej.us/guide/bgnet/examples/client.c
    if (sa->sa_family == AF_INET) {
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(void)
{
    // from http://beej.us/guide/bgnet/examples/client.c
    int tcp_sockfd, tcp_numbytes;
    char buf[MAXDATASIZE];
    struct addrinfo fs_hints, *fileservinfo, *p_fsinfo;
    struct sockaddr_storage my_addr; // for storing local dynamic port number
    unsigned short int client_tcp_port;
    char client_ipaddr_str[INET_ADDRSTRLEN];
    socklen_t addrlen;
    int rv;
    int getsock_check;
    char fs_ipaddr_str[INET6_ADDRSTRLEN];

    char *msg_to_send = "abcdef hijklm"; // word1 and word2

    memset(&fs_hints, 0, sizeof fs_hints);
    fs_hints.ai_family = AF_INET; // IPv4
    fs_hints.ai_socktype = SOCK_STREAM;

    if ((rv = getaddrinfo(nodename, FILESERV1_LISTEN_PORT, &fs_hints, &fileservinfo)) != 0) {
        fprintf(stderr, "client1: getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }

    // loop through all the results and connect to the first we can
    for(p_fsinfo = fileservinfo; p_fsinfo != NULL; p_fsinfo = p_fsinfo->ai_next) {
        if ((tcp_sockfd = socket(p_fsinfo->ai_family, p_fsinfo->ai_socktype, p_fsinfo->ai_protocol)) == -1) {
            perror("client1: socket");
            continue;
        }

        if (connect(tcp_sockfd, p_fsinfo->ai_addr, p_fsinfo->ai_addrlen) == -1) {
            close(tcp_sockfd);
            perror("client1: connect");
            continue;
        }

        break;
    }
    printf("DEBUG: client1: socket and connect successful\n");
    if (p_fsinfo == NULL) {
        fprintf(stderr, "client1: failed to connect\n");
        return 2;
    }

    addrlen = sizeof my_addr;
    if ((getsock_check=getsockname(tcp_sockfd, (struct sockaddr *)&my_addr, (socklen_t *)&addrlen)) == -1) { 
        perror("client1: getsockname");
        exit(1);
    }
    printf("DEBUG: client1: getsockname successful\n");
    client_tcp_port = ntohs(((struct sockaddr_in *)&my_addr)->sin_port);
    inet_ntop(AF_INET, &(((struct sockaddr_in *)&my_addr)->sin_addr), client_ipaddr_str, INET_ADDRSTRLEN);
    printf("DEBUG: client1 has dynamic TCP port number %hu and IP address %s.\n", client_tcp_port, client_ipaddr_str);

    inet_ntop(p_fsinfo->ai_family, get_in_addr((struct sockaddr *)p_fsinfo->ai_addr), fs_ipaddr_str, sizeof fs_ipaddr_str);
    printf("DEBUG: client1: connecting to %s\n", fs_ipaddr_str);
    freeaddrinfo(fileservinfo); // all done with this structure

    if (send(tcp_sockfd, msg_to_send, strlen(msg_to_send), 0) == -1) {
        perror("client1: send");
        exit(1);
    }

    printf("DEBUG: The request from client1 has been sent to the file_server1\n");

    if ((tcp_numbytes = recv(tcp_sockfd, buf, MAXDATASIZE-1, 0)) == -1) {
        perror("client1: recv");
        exit(1);
    }

    buf[tcp_numbytes] = '\0';

    printf("DEBUG: client1: received %d bytes, content '%s'\n", tcp_numbytes, buf);
    close(tcp_sockfd);


    return 0;
}
Run Code Online (Sandbox Code Playgroud)

服务器的输出:

DEBUG: file_server1: waiting for connections...
DEBUG: Top of while loop
DEBUG: file_server1: got connection from 127.0.0.1
DEBUG: outside of if
DEBUG: Bottom of while loop
DEBUG: Top of while loop
DEBUG: inside if
DEBUG: file_server1: received: abcdef hijklm
file_server1: accept: Interrupted system call
DEBUG: Top of while loop
Run Code Online (Sandbox Code Playgroud)

客户的输出:

DEBUG: client1: socket and connect successful
DEBUG: client1: getsockname successful
DEBUG: client1 has dynamic TCP port number 51196 and IP address 127.0.0.1.
DEBUG: client1: connecting to 127.0.0.1
DEBUG: The request from client1 has been sent to the file_server1
DEBUG: client1: received 0 bytes, content ''
Run Code Online (Sandbox Code Playgroud)

最后,我最终希望该服务器与可能的第二个或第三个客户端(目前,不考虑同时服务的客户端)处理相同的双向交易。listen()在这种情况下,我是否要保持打开状态返回的sockfd ?

nos*_*nos 5

使用EINTR的accept()调用“失败”是正常的。您的子进程已结束,当前系统调用被中断。即使您SA_RESTART在sigaction标志中进行设置,这种情况也会在少数系统调用中发生。您只要继续循环,尝试接受新客户,就可以了。

您的子进程由于崩溃而结束。

char *word1, *word2;
...

sscanf(tcp_recv_buf, "%s %s", word1, word2);
Run Code Online (Sandbox Code Playgroud)

在这里,您尝试写入word1word2,它们是未初始化的指针。