c - 使用 C 中的套接字的简单回显程序在第一次运行后回显不正确的消息

标签 c sockets echo

我正在尝试使用 C 中的套接字学习网络通信的基础知识。我的客户端程序接收来自用户的消息,在服务器端和返回端回显它,然后打印出接收到的消息。当我第一次启动它们时,它们都按预期工作。但是,如果我退出客户端,然后在保持服务器程序运行的情况下再次启动它,我的回显消息就会减少一个。

我以为是因为最后一条消息被管道卡住了什么的,在四处寻找之后,我看到有人建议使用 shutdown() 来清除管道,但是那似乎没有用。我还尝试将缓冲区清零,只要我认为它们可能会挥之不去,但这似乎也无济于事。

服务器.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;
}

客户端.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;
}

最佳答案

我对您的服务器代码做了一些清理,这似乎有效。

对于我的测试,客户端代码未更改。但是,正如其他人所建议的那样,您应该检查 sendrecv 的错误代码。另外,请注意,如果您 ctrl-c 服务器,客户端将卡在 fgets 中,因此它不会检测到服务器中止,直到您在提示后按回车键.没什么大不了的,但我想我会提一下。

我还添加了一个fork,这样您就可以让多个 客户端同时与同一个服务器实例通信。

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

我将您的回显代码移到了新函数 docomm 中。与您的代码的一个小区别是,来自 recvsendany 错误会跳出循环并关闭连接。来自 客户端的所有连接都保证recv 调用开始。

在您的代码中,您不会总是跳出循环,而是关闭连接并再次调用listening或者 sendrecv 都会发生这种情况。如果它发生在错误的上,这可能是您遇到的问题的根源,因为您可以在之前执行发送 recv 最初是客户端。


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

更新:

Just from a glance: 1) Can I ask why you created a fork flag if you never change the value of it? Should it be changed somewhere?

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

Just tracing through the program mentally, it seems like the forking section will never be executed. Correct me where I'm wrong: after initially setting the socket to listen, the while loop will enter, and listening() will be called. Execution will halt in listening() until a connection is accepted.

是的,这是真的。

Control will return to main, where docomm() gets called. Control stays in docomm() until the connection breaks, at which point it returns to main and continue gets called, skipping the fork stuff and starting the process over again. So does the fork stuff ever get executed?

您所描述的是 forkflg 为零时的行为。

fork 被调用如果 forkflg 被设置。请注意,在这种情况下,docommchild 而不是 parent 中调用(因为 fork 返回 0 ).因此,父级不会在子级回显时被阻塞。

因此,parent 立即返回并可以自由执行 waitpid 循环以获取任何旧的 child 并重新启动主/外循环。

waitpid 循环仅在新连接进入时发生,因此多个 child 可能已经终止并保持僵尸状态,直到执行 waitpid 循环 [which将收获任何/多个待处理的 child ]。

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

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

关于c - 使用 C 中的套接字的简单回显程序在第一次运行后回显不正确的消息,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53687746/

相关文章:

ios - GCDAsyncSocket 丢失数据

echo - 软件回声消除器如何工作?

c - 将目录添加到 execvp() 的 PATH

c - EOF 练习 1-6 K&R C 编程语言

java - 如何使用套接字将文件从php客户端发送到java服务器

c - C中的套接字编程

php - 如何在 php 中不换行地回显?

linux - Bash脚本调用另一个Bash脚本时出现 echo 问题

c - 用 fork() 打印斐波那契数列

c - epoll - 轮询多个文件描述符(即套接字)