Простая эхо-программа, использующая сокеты на C, отображающая некорректное сообщение после первого запуска - PullRequest
0 голосов
/ 09 декабря 2018

Я пытаюсь изучить основы сетевого взаимодействия с использованием сокетов на языке C. Моя клиентская программа принимает сообщение от пользователя, отображает его на стороне сервера и обратно и распечатывает полученное сообщение.Когда я запускаю их обоих в первый раз, они работают точно так, как ожидалось.Однако если я выйду из клиентской части, а затем снова включу ее, продолжая работу серверной программы, мои отраженные сообщения будут отключены на единицу.

Я предположил, что это произошло потому, что последнее сообщение попадает в канал иличто-то, и после осмотра я увидел, что кто-то предложил использовать 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 голосов
/ 09 декабря 2018

Я выполнил некоторую очистку кода вашего сервера, и, похоже, это работает.

Для моего тестирования код клиента не изменился.Но, как другие предложили, вы должны проверить коды ошибок от send и recv.Также обратите внимание, что если вы ctrl-c сервер, клиент будет зависать в fgets, поэтому он не обнаружит прерывание работы сервера, пока вы не нажмете return после запроса.Ничего страшного, но я подумал, что упомяну это.

Я также добавил fork, чтобы вы могли иметь несколько клиентов, говорящих с одним экземпляром сервера одновременно.

Я проверил это с двумя клиентами [в двух xterm окнах], разговаривающими с одним экземпляром сервера.

Я переместил ваш эхо-код в новую функцию docomm.Небольшое отличие от вашего кода в том, что любая ошибка из recv или send прерывается и закрывает соединение.Все подключения от новых клиентов гарантированы для начала с recv звонком.

В вашем коде вы бы не всегда разрывалицикла, но закройте соединение и снова наберите listening.Это произойдет для или send или recv.Если это произошло на неверном , это может быть источником проблемы, с которой вы столкнулись, потому что вы могли сделать send до a 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;
}

ОБНОВЛЕНИЕ:

Всего с одного взгляда: 1) Могу я спросить, почему вы создали флаг форка, если вы никогда не меняете его значение?Должно ли оно быть где-то изменено?

Я использовал forkflg, чтобы вы могли установить его на ноль (например, int forkflg = 0;) для последовательной работы.Или вы можете добавить некоторый код и разобрать argv в поисках опции (например, -f), чтобы установить / очистить его [для целей тестирования / отладки].Для производственного кода вы бы хотели, чтобы был установлен forkflg и могли бы убрать флаг, и всегда делайте случай с вилкой [корректируя код для соответствия].

Просто мысленно отслеживая программу,похоже, что раздел разветвления никогда не будет выполнен.Поправьте меня, где я ошибаюсь: после первоначальной установки сокета на прослушивание, включится цикл while и будет вызвана listen ().Выполнение будет приостановлено в listen () до тех пор, пока соединение не будет принято.

Да, это правда.

Элемент управления вернется в main, где вызывается docomm ().Элемент управления остается в docomm () до тех пор, пока не будет разорвано соединение, после чего он вернется к основному и будет вызван метод continue, пропуская содержимое форка и снова начав процесс.Так выполняется ли разветвление?

Что вы описываете, это поведение, если forkflg равно нулю.

fork называется , если forkflg установлено.Обратите внимание, что в этом случае docomm вызывается в child и не родительском (потому что fork вернул 0).Таким образом, родитель будет не заблокирован, пока ребенок выполняет эхо.

Таким образом, родитель немедленно возвращается и может свободно делатьцикл waitpid, чтобы пожинать всех старых детей и перезапускать основной / внешний цикл.

Цикл waitpid происходит только при установлении нового соединения, поэтому несколько детей уже могут прерваться и останутся в зомбисостояние до тех пор, пока не будет выполнен цикл waitpid [который будет пожинать все / несколько ожидающих потомков].

Более чистый способ пожинать потомки может быть установить обработчик сигнала для SIGCHLD и пусть это сделает цикл waitpid.Это сразу же пожнет все потраченные дети, без необходимости ждать, пока не установится новое соединение.

Или, с помощью обработчика сигнала, добавить цикл waitpid к listening [внутри токовой петли], потому что, если поступит сигнал SIGCHLD, accept немедленно вернется с errno, установленным в EINTR

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...