首页 文章

在第一次运行后使用C中的套接字回显错误消息的简单回显程序

提问于
浏览
1

我正在尝试使用C中的套接字来学习网络通信的基本知识 . 我的客户端程序接收来自用户的消息,回显服务器端和后端,并打印出收到的消息 . 当我第一次点燃它们时,它们都完全按照预期工作 . 但是,如果我退出客户端,然后在保持服务器程序运行的同时再次启动它,我的回显消息将被一个人关闭 .

我以为这是因为最后一条消息被管道或其他东西 grab 了,在探索之后,我看到有人建议使用 shutdown() 来清除管道,但这似乎也没有帮助 .

server.c

#include <stdio.h>
#include <errno.h>
#include <sys/socket.h>
#include <resolv.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>

#define PORT            12403
#define BUFFER_MAX      1024
#define BACKLOG_MAX     1024

int clientSocket;
int serverSocket;

void listening()
{
    while (1)
    {
        struct sockaddr_in clientAddress;
        socklen_t addressLength = sizeof(clientAddress);

        /*---accept a connection (creating a data pipe)---*/
        clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddress, &addressLength);
        if (clientSocket > -1)
        {
            printf("%s:%d connected\n", inet_ntoa(clientAddress.sin_addr), ntohs(clientAddress.sin_port));
            break;
        }
    }
}

int main(int Count, char *Strings[])
{   
    struct sockaddr_in socketInfo;
    char buffer[BUFFER_MAX];

    //Create socket
    if ((serverSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        perror("Error creating socket");
        exit(errno);
    }

    //Setting the linger option to off and resuse address option to on for testing
    int option = 0;
    setsockopt(serverSocket, SOL_SOCKET, SO_LINGER, &option, sizeof(option));
    option = 1;
    setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));

    //Initialize socket information
    bzero(&socketInfo, sizeof(socketInfo));
    socketInfo.sin_family = AF_INET;
    socketInfo.sin_port = htons(PORT);
    socketInfo.sin_addr.s_addr = INADDR_ANY;

    //Assign a port number to the socket
    if (bind(serverSocket, (struct sockaddr*)&socketInfo, sizeof(socketInfo)) != 0)
    {
        perror("Error binding socket");
        exit(errno);
    }

    //Set socket to listen
    if (listen(serverSocket, BACKLOG_MAX) != 0)
    {
        perror("Error setting socket to listen");
        exit(errno);
    }

    listening();

    //Once first socket has been connected, begin echoing process
    int i = 0;
    while (1)
    {
        //Clear the buffer
        bzero(buffer, BUFFER_MAX);

        //Echo back anything sent
        //Close connection and begin listening process again if the client disconnects
        int sendCheck;
        int readCheck;
        readCheck = recv(clientSocket, buffer, BUFFER_MAX, 0);
        if (readCheck <= 0)
        {
            shutdown(clientSocket, SHUT_WR);
            close(clientSocket);
            sleep(1);
            listening();
        }
        sendCheck = send(clientSocket, buffer, BUFFER_MAX, 0);

        if (sendCheck <= 0)
        {
            shutdown(clientSocket, SHUT_WR);
            close(clientSocket);
            sleep(1);
            listening();
        }
        i++;
    }

    close(serverSocket);
    return 0;
}

client.c

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

#define PORT            12403
#define LOCALHOST       "127.0.0.1"
#define BUFFER_MAX      1024

int socketStatus = 0;

void sigpipeHandler()
{
    perror("Connection to server terminated\n");
    socketStatus = 0;
}

int main()
{
    int mySocket;
    struct sockaddr_in socketInfo;
    char buffer[BUFFER_MAX];

    int count = 0;

    //Create socket
    if ((mySocket = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        perror("Error creating socket");
        exit(errno);
    }

    //Get IP address of required host machine
    char* hostName = "<host name removed>";
    int portNumber = PORT;
    char* ipAddr = NULL;
    struct hostent* host = NULL;
    host = gethostbyname(hostName);
    ipAddr = inet_ntoa(*((struct in_addr*) host->h_addr_list[0]));

    //Initialize server information
    bzero(&socketInfo, sizeof(socketInfo));
    socketInfo.sin_family = AF_INET;
    socketInfo.sin_port = htons(portNumber);
    if (inet_aton(ipAddr, (struct in_addr *)&socketInfo.sin_addr.s_addr) == 0)
    {
        perror("Error assigning IP address");
        exit(errno);
    }

    //Set up sigpipe handler
    signal(SIGPIPE, sigpipeHandler);

    //Connect to server
    if (connect(mySocket, (struct sockaddr*)&socketInfo, sizeof(socketInfo)) != 0)
    {
        perror("Error connecting");
        exit(errno);
    }

    //Indicate that socket is OK
    socketStatus = 1;

    while(1)
    {
        if(!socketStatus) {shutdown(mySocket, SHUT_WR); break;}
        printf("Please enter a command.\n");
        char command[BUFFER_MAX];
        bzero(command, BUFFER_MAX);
        fgets(command, sizeof(command), stdin);

        send(mySocket, command, BUFFER_MAX, 0);

        //Get echoed message
        bzero(buffer, BUFFER_MAX);
        recv(mySocket, buffer, sizeof(buffer), 0);
        printf("Echo [%d]:%s\n", ++count, buffer);
    }

    //Close socket
    close(mySocket);
    return 0;
}

1 回答

  • 0

    我对你的服务器代码进行了一些清理,这似乎有效 .

    对于我的测试,客户端代码保持不变 . 但是,正如其他人所建议的那样,您应该检查 sendrecv 中的错误代码 . 另外,请注意,如果你_99927_服务器,客户端将挂在 fgets ,所以它赢了't detect the server abort until you hit return after the prompt. Not a big deal, but I thought I' d提到它 .

    我还添加了 fork ,因此您可以让多个客户端同时与同一服务器实例通信 .

    我用两个客户端[在两个 xterm 窗口]与单个服务器实例进行交谈测试了这个 .

    我将你的echo代码移动到一个新函数 docomm 中 . 与您的代码的一个小差别是, recvsend 中的任何错误都会突破循环并关闭连接 . 来自新客户端的所有连接都保证以 recv 调用开始 .

    在您的代码中,您不会总是突破循环,但关闭连接并再次调用 listening . 这将发生在 sendrecv . 如果它发生在错误的一个上,这可能是您遇到的问题的根源,因为您最初可以在 recv 之前对新客户端进行 send .


    #include <stdio.h>
    #include <errno.h>
    #include <sys/socket.h>
    #include <resolv.h>
    #include <arpa/inet.h>
    #include <errno.h>
    #include <stdlib.h>
    #include <strings.h>
    #include <unistd.h>
    #include <sys/wait.h>
    
    #define PORT            12403
    #define BUFFER_MAX      1024
    #define BACKLOG_MAX     1024
    
    int clientSocket;
    int serverSocket;
    int forkflg = 1;
    
    void listening()
    {
        while (1)
        {
            struct sockaddr_in clientAddress;
            socklen_t addressLength = sizeof(clientAddress);
    
            /*---accept a connection (creating a data pipe)---*/
            clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddress, &addressLength);
            if (clientSocket > -1)
            {
                printf("%s:%d connected\n", inet_ntoa(clientAddress.sin_addr), ntohs(clientAddress.sin_port));
                break;
            }
        }
    }
    
    void
    docomm(void)
    {
        char buffer[BUFFER_MAX];
    
        //Once first socket has been connected, begin echoing process
        int i = 0;
        while (1) {
            //Clear the buffer
            bzero(buffer, BUFFER_MAX);
    
            //Echo back anything sent
            //Close connection and begin listening process again if the client disconnects
            int sendCheck;
            int readCheck;
    
            readCheck = recv(clientSocket, buffer, BUFFER_MAX, 0);
            if (readCheck <= 0)
                break;
    
            sendCheck = send(clientSocket, buffer, BUFFER_MAX, 0);
            if (sendCheck <= 0)
                break;
    
            i++;
        }
    
        printf("close\n");
        shutdown(clientSocket, SHUT_WR);
        close(clientSocket);
    }
    
    int main(int Count, char *Strings[])
    {
        struct sockaddr_in socketInfo;
    
        //Create socket
        if ((serverSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        {
            perror("Error creating socket");
            exit(errno);
        }
    
        //Setting the linger option to off and resuse address option to on for testing
        int option = 0;
        setsockopt(serverSocket, SOL_SOCKET, SO_LINGER, &option, sizeof(option));
        option = 1;
        setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));
    
        //Initialize socket information
        bzero(&socketInfo, sizeof(socketInfo));
        socketInfo.sin_family = AF_INET;
        socketInfo.sin_port = htons(PORT);
        socketInfo.sin_addr.s_addr = INADDR_ANY;
    
        //Assign a port number to the socket
        if (bind(serverSocket, (struct sockaddr*)&socketInfo, sizeof(socketInfo)) != 0)
        {
            perror("Error binding socket");
            exit(errno);
        }
    
        //Set socket to listen
        if (listen(serverSocket, BACKLOG_MAX) != 0)
        {
            perror("Error setting socket to listen");
            exit(errno);
        }
    
        while (1) {
            listening();
    
            if (! forkflg) {
                docomm();
                continue;
            }
    
            pid_t pid = fork();
            if (pid == 0) {
                docomm();
                exit(0);
            }
    
            while (waitpid(0,NULL,WNOHANG) > 0);
        }
    
        close(serverSocket);
        return 0;
    }
    

    UPDATE:

    一目了然:1)如果你永远不改变它的 Value ,我可以问你为什么创建一个fork标志?它应该在某处改变吗?

    我使用了 forkflg ,因此您可以将其设置为零(例如 int forkflg = 0; )以按顺序运行 . 或者,您可以添加一些代码并解析 argv 寻找一个选项(例如 -f )来设置/清除它[用于测试/调试目的] . 对于 生产环境 代码,您需要设置 forkflg 并且可以删除该标志,并且始终执行fork情况[调整代码以匹配] .

    只是在精神上追踪程序,似乎永远不会执行分叉部分 . 在我错误的地方纠正我:在最初将套接字设置为listen之后,while循环将进入,并且将调用listen() . 执行将在listen()中暂停,直到接受连接 .

    是的,这是真的 .

    Control将返回main,调用docomm() . 控制保持在docomm()中,直到连接断开,此时它返回到main并继续调用,跳过fork东西并重新开始该过程 . 那些叉子的东西会被执行吗?

    你所描述的是 forkflg 为零时的行为 .

    如果设置 forkflg ,则调用 fork . 请注意,在这种情况下,在子节点中调用 docomm 而不是父节点(因为 fork 返回0) . 因此,当孩子进行回声时,父母不会被阻止 .

    因此,父进程立即返回并可以自由地执行 waitpid 循环以收集任何旧子进程并重新启动主/外循环 .

    waitpid 循环仅在新连接进入时发生,因此几个孩子可能已经终止并将保持僵尸状态,直到 waitpid 循环被执行[这将收获任何/多个未决子项] .

    收获子项的一种更简洁的方法可能是为 SIGCHLD 设置一个信号处理程序并让它执行 waitpid 循环 . 这将立即收获所有用过的孩子,而不必等待新的连接进入 .

    或者,使用信号处理程序,将 waitpid 循环添加到 listening [在当前循环内],因为如果 SIGCHLD 信号进入, accept 将立即返回, errno 设置为 EINTR

相关问题