Что recv() записывает в буфер, если полученные данные меньше длины буфера

Я пытаюсь написать сервер на C, используя сокеты, которые будут получать команды от нескольких клиентов. Я пытаюсь понять: если клиент отправляет команду, состоящую, скажем, из 4 символов, а функции recv() приказано получить 5 байтов данных, что произойдет?

Вот мой код:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <time.h>
#include <dirent.h>

void *connection_handler(void *);
int sendall(int s, char *buf, int len, int flags);
int recvall(int s, char *buf, int len, int flags);

int main (int argc , char *argv[])
{
char check = 'n', *message;
int socket_d, port, socket_n, *sock_n, c;
struct sockaddr_in server_addr, client_addr;

socket_d = socket(AF_INET , SOCK_STREAM , 0);
if (socket_d == -1)
{
    printf("Could not create socket. Exiting...\n");
    return 1;
}

printf("Welcome to Random server v0.0.1.\n");

printf("Please input port (default - 3425): ");
scanf("%i", &port);
if (port >= 65536 || port < 0) port = 3425;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
server_addr.sin_port = htons(port);

if( bind(socket_d,(struct sockaddr *)&server_addr , sizeof(server_addr)) < 0)
{
    printf("Binding socket failed.\n");
    return 1;
}
printf("Binding socket done.\n");

listen(socket_d, 3);

printf("Waiting for connections...\n");
c = sizeof(struct sockaddr_in);

while( (socket_n = accept(socket_d, (struct sockaddr *)&client_addr, (socklen_t*)&c)) )
    {
        puts("Connection accepted");
        message = "Hello Client, I have received your connection. And now I will assign a handler for you\n";
        send(socket_n , message , strlen(message), 0);

        pthread_t sniffer_thread;
        sock_n = malloc(1);
        *sock_n = socket_n;

        if( pthread_create( &sniffer_thread , NULL ,  connection_handler , (void*) sock_n) < 0)
        {
            perror("could not create thread");
            return 1;
        }

        //Now join the thread , so that we dont terminate before the thread
        //pthread_join( sniffer_thread , NULL);
        puts("Handler assigned");
    }

if (socket_n<0)
    {
        printf("Accept failed.\n");
        return 1;
    }
return 0;
}
void *connection_handler(void *socket_d)
{
printf("Thread created\n");
int sock = *(int*)socket_d;
int read_size;
char *message , client_message[200];

while (1)
{
    message = malloc(5);
    read_size = recv(sock , message , 5 , 0);
    strcpy(client_message, message);
    //send(sock , message , strlen(message), 0);
    free(message);

    if (strcmp(client_message, "list") == 0)
    {
        DIR * dir;
        struct dirent * de;
        strcpy(client_message, "");
        if ((dir = opendir(".")) == NULL ) strcpy(client_message, "Unable to open the directory.\n");
        else
        {
            while (de = readdir(dir)) {strcat(client_message, de->d_name); strcat(client_message, "\n"); }
        }
        closedir(dir);
        send(sock, client_message, strlen(client_message), 0);
        continue;
    }
    if (strcmp(client_message, "date") == 0)
    {
        time_t rtime;
        rtime = time (NULL);
        strcpy(client_message, ctime(&rtime));
        send(sock, client_message, strlen(client_message), 0);
        continue;
    }
    if (strcmp(client_message, "mkdir") == 0)
    {
        int result_code = mkdir("./new dir");
        if (result_code == 0) strcpy(client_message, "OK");
        else strcpy(client_message, "ERROR");
        send(sock, client_message, strlen(client_message), 0);
        continue;
    }
    if (strcmp(client_message, "exit") == 0)
    {
        strcpy(client_message, "Bye");
        send(sock, client_message, strlen(client_message), 0);
        break;
    }
    send(sock, client_message, strlen(client_message), 0);


    if(read_size == 0)
    {
        printf("Client disconnected\n");
        fflush(stdout);
        break;
    }
    else if(read_size == -1)
    {
        printf("Recv failed\n");
    }
}

printf("Thread will now be deleted.\n");
free(socket_d);

return 0;
}

На данный момент кажется, что он запишет эти 4 символа в буфер И что-то еще, чтобы сделать его длиной пять байтов. Я прав? Что именно происходит? Как мне сделать так, чтобы сервер мог обрабатывать команды любой длины?


person FalconD    schedule 04.02.2014    source источник
comment
Пятый байт не изменился — он по-прежнему имеет значение, которое было до вызова recv().   -  person    schedule 04.02.2014


Ответы (2)


На данный момент кажется, что он запишет эти 4 символа в буфер И что-то еще, чтобы сделать его длиной пять байтов. Я прав?

Нет. recv() будет считывать максимальное количество запрошенных байтов, но может читать меньше. Со связанной справочной страницы:

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

Наблюдаемое что-то еще связано с тем, что заполняемый буфер recv() унифицирован:

message = malloc(5); /* Uninitialized. */
read_size = recv(sock , message , 5 , 0);

Это означает, что если считаны только 4 байта, пятый байт в буфере будет содержать случайное значение.

В опубликованном коде неправильно используется strcpy(), что зависит от наличия нулевого завершающего символа: используйте возвращаемое значение recv(), чтобы точно знать, сколько байтов было прочитано, а также в случае полного сбоя (в этом случае возвращаемое значение равно -1) . Например:

read_size = recv(sock , message , 5 , 0);
if (read_size != -1)
{
    if (read_size == 4 && memcmp(message, "list", 4) == 0)
    {
    }
}
else
{
    /* Report failure. */
}

Как мне сделать так, чтобы сервер мог обрабатывать команды любой длины?

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

person hmjd    schedule 04.02.2014
comment
Если я правильно понял, использование strncpy(client_message, message, read_size) вместо strcpy() должно помочь, верно? - person FalconD; 04.02.2014
comment
Что делать, если для нулевого символа недостаточно места? Из strncpy(): Если счетчик достигнут до всей строки src был скопирован, результирующий массив символов не завершается нулем. - person hmjd; 04.02.2014
comment
@user3201961 user3201961: помните, strncpy() не гарантирует, что выходная строка завершается нулем. Крайне важно помнить об этом, потому что самая распространенная ошибка при использовании strncpy() состоит в том, чтобы забыть убедиться, что строка завершается нулем. Другая проблема с strncpy() заключается в том, что если у вас есть буфер размером 64 КиБ в качестве цели, strncpy() будет записывать 64 КиБ при каждом копировании, даже если исходная строка имеет длину 3 байта плюс нулевой байт. Если вы этого не знали, внимательно прочитайте спецификацию strncpy(). - person Jonathan Leffler; 04.02.2014

Если клиент отправляет команду, состоящую, скажем, из 4 символов, а функции recv() приказано получить 5 байт данных, что произойдет?

Буфер будет содержать 4 байта данных, а информация о длине, возвращаемая recv(), скажет вам, что есть 4 байта действительных данных и, следовательно, в пятом байте нет ничего надежного (но, скорее всего, это то, что было до вызова recv()). сделал). Поскольку ваш код не отправляет нулевое значение в конце строки с завершающим нулем, принимающий код не получает нулевое значение, поэтому вы не можете надежно использовать strcpy() для полученных данных. Используйте memmove() или memcpy() или, возможно, strncpy(), а затем нулевое завершение.

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

person Jonathan Leffler    schedule 04.02.2014