Сломанная труба для C-Socket. Как только поддерживать работоспособность сервера?

В простой программе, где я пытаюсь отправить данные командной строки от клиента к серверу, я продолжаю получать «сломанную трубу» для стороны сервера. Я отправляю строку на сервер, и сервер возвращает строку клиенту в нижнем регистре.

Сервер:

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include<string.h>
#include <ctype.h>
#include <unistd.h>

int main()
{

    char str[100];
    int listen_fd, comm_fd;

    struct sockaddr_in servaddr;

    listen_fd = socket(AF_INET, SOCK_STREAM, 0);

    bzero( &servaddr, sizeof(servaddr));

    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htons(INADDR_ANY);
    servaddr.sin_port = htons(37892);

    bind(listen_fd, (struct sockaddr *) &servaddr, sizeof(servaddr));

    listen(listen_fd, 10);

    comm_fd = accept(listen_fd, (struct sockaddr*) NULL, NULL);

    while(1){
                bzero( str, 100);
                read(comm_fd,str,100);
                for(int i = 0; i < strlen(str); i++){

                        str[i] = tolower(str[i]);

                }
                printf("Echoing back - %s",str);
                write(comm_fd, str, strlen(str)+1);
        }
}

Клиент

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include<string.h>
#include<ctype.h>
#include <unistd.h>

int main(int argc,char **argv)
{
    int sockfd,n;
    char sendline[100];
    char recvline[100];
    struct sockaddr_in servaddr;

    sockfd=socket(AF_INET,SOCK_STREAM,0);
    bzero(&servaddr,sizeof servaddr);

    servaddr.sin_family=AF_INET;
    servaddr.sin_port=htons(37892);

    inet_pton(AF_INET,"127.0.0.1",&(servaddr.sin_addr));

    connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));


    if(argc==1) printf("\nNo arguments");

    if (1){
        {
                bzero( sendline, 100);
                bzero( recvline, 100);
                strcpy(sendline, argv[1]);
                write(sockfd,sendline,strlen(sendline)+1);
                read(sockfd,recvline,100);
                printf("%s",recvline);
         }
     }
}

Проблема, которую я обнаружил, заключалась в том, что, когда сторона клиента завершает отправку строки, ввод командной строки не работает как fgets(), когда цикл будет ждать ввода другого пользователя. Если я изменю if(1) на стороне клиента на while(1), он, очевидно, запустит бесконечный цикл, поскольку новые входные данные не добавляются. Дилемма заключается в том, как я смогу поддерживать работоспособность сервера, чтобы непрерывно возвращать строку клиенту при обработке отдельных запросов из командной строки на стороне клиента?


person Joe    schedule 21.02.2018    source источник
comment
Вы не обращаете внимания на то, что возвращают read() и write(), что всегда является серьезной проблемой. Вы также не проверяете ошибки socket() или connect() (или bind() или accept()) - то же самое. Вы копируете argv[1] в sendline, что на самом деле не нужно, но если вы копируете, вы должны проверить, достаточно ли места. Хотя read() в клиенте «безопасен», вам нужно знать, сколько байтов было получено, чтобы вы могли распечатать правильное число; вы могли прочитать 100 из 120 байтов, и строка не будет завершаться нулем. Вы должны быть очень и очень осторожны!   -  person Jonathan Leffler    schedule 21.02.2018
comment
Чтобы ответить на вопрос заголовка, вам придется обрабатывать ошибки SIGPIPE - или игнорировать их и обращать внимание на возвращаемые сообщения об ошибках от write(). Затем вы можете закрыть соединение и вернуться к следующему. Вы также не закрываете файловые дескрипторы, поэтому рано или поздно они у вас закончатся.   -  person Jonathan Leffler    schedule 21.02.2018
comment
Возможный дубликат Как предотвратить SIGPIPE (или обработать их должным образом)   -  person Jeremy Friesner    schedule 21.02.2018


Ответы (2)


У вашей программы две проблемы:

1) read() работает не так, как вы думаете:

Обычно read() читает определенное количество байтов из некоторого файла или потока (например, сокета).

Поскольку read() не различает разные типы байтов (например, буквы, маркер конца строки или даже байт NUL), read() не будет работать как fgets() (чтение по строкам).

read() также разрешено «разбивать» данные: если вы выполните write(..."Hello\n"...) на клиенте, сервер может получить "Hel" при первом вызове read() и в следующий раз, когда он получит "lo\n".

И, конечно, read() может объединять данные: вызов write(..."Hello\n"...) и write(..."World\n"...) на клиенте, и один единственный read() вызов может получить "Hello\nWorld\n".

И, конечно, оба эффекта могут появиться одновременно, и вам придется трижды вызвать read(), получив "Hel", "lo\nWo" и "rld\n".

TTY (= консоль (клавиатура) и последовательные порты) имеют специальную функцию (которая может быть отключена), которая заставляет вызов read() вести себя как fgets(). Однако такая функция есть только TTY!

В случае сокетов read() всегда будет ждать получения хотя бы одного байта и возвращать (положительное) количество полученных байтов, пока соединение активно. Как только read() вернет ноль или отрицательное значение, соединение будет разорвано.

Вы должны использовать цикл while, который обрабатывает данные до тех пор, пока соединение не будет разорвано.

Вам нужно будет проверить данные, полученные read(), если они содержат байт NUL, чтобы определить «конец» данных - если «ваши» данные оканчиваются байтом NUL.

2) Как только клиент разрывает соединение, дескриптор, возвращаемый accept(), становится бесполезным.

Вы должны закрыть этот дескриптор, чтобы сэкономить память и файловые дескрипторы (существует ограничение на количество файловых дескрипторов, которые вы можете открыть одновременно).

Затем вам нужно снова вызвать accept(), чтобы дождаться, пока клиент установит новое соединение.

person Martin Rosenau    schedule 21.02.2018

  1. Ваш клиент отправляет один запрос и читает один ответ.
  2. Затем он выходит, не закрывая сокет.
  3. Ваш сервер работает в цикле чтения запросов и отправки ответов.
  4. Ваш сервер игнорирует конец потока.
  5. Этот код практически не проверяется на ошибки.
person user207421    schedule 21.02.2018