Отправка клиента TCP / IP

Ниже приведен мой код C / S и результат только для теста. У меня с этим проблема.
ясно, что я просто дважды отправляю через клиента. но есть 3 сообщения, которые только что получает сервер. а второй кажется NULL.

#define MP_MAXLINE 4096    
/* client */
int main(int ac, char *av[])
{
    char buf[MP_MAXLINE];
    int clifd;
    struct sockaddr_in cliaddr;

    if ((clifd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
        perror("socket error");

    memset(&cliaddr, 0, sizeof(struct sockaddr_in));
    cliaddr.sin_family = AF_INET;
    cliaddr.sin_port = htons(atoi(av[2]));
    inet_pton(AF_INET, av[1], &cliaddr.sin_addr);

    if (connect(clifd, (struct sockaddr *)&cliaddr, sizeof(cliaddr)))
        perror("can not connect to server");

    printf("enter data to send: ");
    while(fgets(buf, MP_MAXLINE, stdin) != NULL){
        write(clifd, buf, sizeof(buf));
        printf("enter data to send: ");
    }
}

/* server */
static void cli_service(int clifd){
    int lclifd, rcv_cnt = 0;
    char data[MP_MAXLINE];

    for(;;){
        if (read(clifd, data, sizeof(data)) == 0)
            exit(1);
        rcv_cnt++;
        printf("cnt: %d, data: %s", rcv_cnt, data);
    }
}

int main(int ac, char *av[])
{
    ...

    for(;;){
        clifd = accept(svrfd, (struct sockaddr *)&cliaddr, &clisocklen);

        if ((clipid = fork()) < 0){
            printf("fork error\n");
            continue;
        }
        else if (clipid == 0){
            close(svrfd);
            cli_service(clifd);
        }
        close(clifd);
        continue;
    }
}

вывод клиента здесь:
введите данные для отправки: 1111
введите данные для отправки: 2222
введите данные для отправки:

вывод сервера здесь:
cnt: 1, данные: 1111
cnt: 2, данные: cnt: 3, данные: 2222

🤔 А знаете ли вы, что...
C стандартизирован ISO и ANSI, что обеспечивает переносимость кода между различными компиляторами и платформами.


523
1

Ответ:

Решено

Что происходит, так это то, что вы собираетесь обнаружить, что TCP является протоколом ориентированный на поток, что означает, что он гарантирует, что отправляемые вами байты будут доставлены правильно и по порядку, но сопоставление байтов с отдельными вызовами write() / read() произвольно - то есть количество байтов, которые записываются в массив получателя при каждом вызове read(), определенно нет гарантированно будет таким же, как количество байтов, которые были ранее переданы вызову write() программой-отправителем.

Таким образом, похоже, что в вашем клиенте каждый вызов write() отправляет 4096 байт, первые несколько из которых будут содержать текстовую строку с завершающим нулем, которую ввел пользователь, а остальная часть будет неинициализированными данными (т.е. это может быть что угодно потому что вы никогда не писали в эту часть массива).

Затем на вашем сервере вы вызываете read(), который читает некоторое количество байтов, но вы не знаете, сколько байтов он считал, потому что все, что вам нужно сделать, это проверить, вернул ли read() 0 или нет. Таким образом, вы не знаете, поместила ли read () 4096 байтов данных в ваш массив data или какое-то меньшее количество байтов; и если это было немного меньшее число, тогда ваш принимающий код теперь не синхронизирован с вашим отправляющим кодом, поскольку последующие вызовы read(), вероятно, будут записывать неинициализированные данные в начало вашего массива data, а следующая строка пользователя где-то в середине массива.

Это, конечно, бесполезное поведение. Что вам нужно сделать, чтобы исправить это, так это обратить пристальное внимание на значение, возвращаемое read(); и если он больше нуля, но меньше sizeof(data), вам нужно как-то с этим справиться. Один из способов справиться с этим - вести подсчет того, сколько действительных байтов в настоящее время находится в вашем массиве data, и всегда read(clifd, data+numValidBytes, sizeof(data)-numValidBytes) до (numValidBytes == sizeof(data)) ... и только затем анализировать данные и устанавливать numValidBytes обратно в ноль. Другой, более простой вариант - вызвать recv() вместо read() и установить MSG_WAITALL в последнем параметре - в этом случае recv() обещает не возвращаться, пока не прочитает все байты sizeof(data), и тогда вам не придется иметь дело с частично читает себя.