Отправка int по TCP (программирование на C)

У меня есть сервер и клиентская программа (оба работают на одной машине). Клиент может отправить структуру на сервер с такими элементами, как «ID», «размер» и т. д. Затем я хотел бы, чтобы сервер отправил идентификатор-член (только целое число) обратно клиенту в качестве ACK для проверки , но я просто не могу понять это, несмотря на то, что могу без проблем отправить структуру.

Вот код из server.c:

/* having just recieved the struct */

int ACK_ID = struct_buffer->message_ID;
result = send(CLIENT_socket, &ACK_ID, sizeof(int), 0);

if (result == -1) {
    close(SERVER_socket);
    printf("\n\t[ERROR] Failed to send ACK.\n");
    exit(EXIT_FAILURE);
}

Вот код из client.c:

// Recieve ACK from server
int ACK_ID;
com_result = read(CLIENT_socket, &ACK_ID, sizeof(int), 0);

if ((com_result == -1) || (ACK_ID != metablocks[index].message_ID)) {
    printf("\n\t[ERROR] Failed to send metadata. ACK: %i\n", ACK_ID);
}

Когда я пытаюсь запустить это, я получаю следующий вывод из client.c:

[ОШИБКА] Не удалось отправить метаданные. ПОДТВЕРЖДЕНИЕ: 14

И, конечно же, сервер сообщает мне, что ему не удалось отправить ACK. Значение целого числа ID, которое я пытаюсь отправить, должно быть 1, но оно получено как 14. Что я здесь делаю неправильно?

Обновление
Итак, я просто попробовал то, что предложил г-н Шоули, и получил следующее сообщение об ошибке:

Частичное чтение: неопределенная ошибка: 0

Сначала попробовал именно то, что он написал, но потом заметил, что код сравнивает com_result с sizeof(int). Поэтому я предположил, что это опечатка, и попытался заменить com_result на переменную ACK_ID в сравнении. Тот же результат.

Обновление 2
Только что добавил perror() на сервер при сбое и получил следующее сообщение об ошибке:

Неверный файловый дескриптор

Я использую для этой операции тот же сокет, что и при получении структуры. Вот расширенный пример кода из server.c:

// Recieve connection
CLIENT_socket = accept(SERVER_socket, (struct sockaddr *)&CLIENT_address, &CLIENT_address_length);
if (CLIENT_socket == -1) {
    close(SERVER_socket);
    printf("\n\t[ERROR] Failed to accept client connection.\n");
    exit(EXIT_FAILURE);
}

printf("\n\tClient connected!\n");

int data_size;

// Read meta data from connection
data_size = sizeof(struct msg_meta);
result = read(CLIENT_socket, &meta_buffer_char, data_size, 0);
meta_buffer = (struct msg_meta *) meta_buffer_char;

if (result == -1) {
    close(SERVER_socket);
    printf("\n\t[ERROR] Failed to read from connection.\n");
    perror("\n\tRead");
    exit(EXIT_FAILURE);
} else if (result > 0) {
    printf("\n\tMessage recieved.\n");
    printf("\n");
}

// Send ACK back to client
int ACK_ID = meta_buffer->message_ID;
result = send(CLIENT_socket, &ACK_ID, sizeof(int), 0);

if (result == -1) {
    printf("\n\t[ERROR] Failed to send ACK.");
    perror("\n\tSend");
    printf("\n");
    close(SERVER_socket);
    exit(EXIT_FAILURE);
}

// Close sockets
close(SERVER_socket);
close(CLIENT_socket);

person o01    schedule 15.11.2009    source источник
comment
Кроме того: используйте htonl и ntohl при отправке и получении (соответственно) int, чтобы убедиться, что он имеет правильный порядок байтов.   -  person outis    schedule 15.11.2009
comment
Не могу сказать точно, пока не напечатаешь сетевые ошибки, если они есть. Вы также можете убедиться, что исходная структура, отправленная на сервер, действительно содержит ожидаемые значения.   -  person Duck    schedule 15.11.2009
comment
@outis: нет, посмотрите на вопрос того же человека > stackoverflow.com/questions/1734819/. Он знает, что находится в состоянии греха в отношении портативности. Нет ничего плохого в том, чтобы определить проводной протокол с целыми числами с прямым порядком байтов, ему просто придется изменить его (или начать перестановку байтов), если ему когда-нибудь понадобятся клиенты или серверы с прямым порядком байтов.   -  person Steve Jessop    schedule 15.11.2009
comment
Структура проверяется на сервере. Я знаю, что отправляю клиенту правильные данные.   -  person o01    schedule 15.11.2009
comment
Г-н Шоули предполагает, что ваша отправка прошла успешно, но в вашем вопросе говорится, что сервер сообщает мне, что ему не удалось отправить ACK. Попробуйте также perror на стороне сервера. (Кстати, частичное чтение довольно странно, если сервер не может отправить ACK!)   -  person Andomar    schedule 15.11.2009
comment
Идея Капитолия :) Только что попробовал .. обновление поста   -  person o01    schedule 15.11.2009
comment
@Andomar Andomar - это не частичное чтение, сервер отправляет FIN.   -  person Duck    schedule 15.11.2009
comment
Звучит как FIN. Закрытие сокета на стороне сервера приведет к получению нулевых байтов на стороне клиента. Я знал, что была причина, по которой я так давно обернул весь свой TCP-код в библиотеку;)   -  person D.Shawley    schedule 15.11.2009
comment
1) Почему ваше чтение принимает четыре аргумента? Я привык использовать только три. 2) 0 является допустимым возвращаемым значением из read(), вы должны проверить 0.   -  person Prof. Falken    schedule 16.11.2009
comment
@Duck: Верно! @Eirik Lillebo: позвоните perror в первую очередь. Если вы вызовете его после close или printf, он сообщит, что printf выполнено успешно.   -  person Andomar    schedule 16.11.2009
comment
Я пробовал это. Тот же результат... В отчаянии.   -  person o01    schedule 17.11.2009
comment
@Eirik - Невозможно сказать по тому, что мы видели до сих пор. Я бы посоветовал публиковать как можно меньше рабочего кода, демонстрирующего проблему. На данный момент это довольно запутанно, поэтому при необходимости задайте новый вопрос.   -  person Duck    schedule 18.11.2009


Ответы (5)


Re: обновление 2. Убедитесь, что вы (а) используете правильный файловый дескриптор и (б) не закрывали () или не отключали () часть записи сокета.

person Duck    schedule 15.11.2009
comment
Не совсем уверен, в чем проблема, но я предполагаю, что я сделал что-то неправильно при закрытии сокетов. Сейчас переписали, работает. - person o01; 18.11.2009

Вам нужно проверить ошибку, которая генерируется при отправке. Вы должны включить библиотеку ошибок #include <cerrno>, а затем проверить значение глобального errno. Существует множество макросов, которые объявляют, какой тип ошибки произошел. Это даст вам больше информации при отладке, почему именно не удалось отправить ACK.

Вот список возможных возвращаемых значений ошибок (значения ошибок — это макросы, определенные в cerrno), взятые из здесь:

     [EACCES]           The SO_BROADCAST option is not set on the socket and a broadcast address is given as
                        the destination.

     [EAGAIN]           The socket is marked non-blocking and the requested operation would block.

     [EBADF]            An invalid descriptor is specified.

     [ECONNRESET]       A connection is forcibly closed by a peer.

     [EFAULT]           An invalid user space address is specified for a parameter.

     [EHOSTUNREACH]     The destination address specifies an unreachable host.

     [EINTR]            A signal interrupts the system call before any data is transmitted.

     [EMSGSIZE]         The socket requires that message be sent atomically, and the size of the message to
                        be sent makes this impossible.  IOV_MAX.

     [ENETDOWN]         The local network interface used to reach the destination is down.

     [ENETUNREACH]      No route to the network is present.

     [ENOBUFS]          The system is unable to allocate an internal buffer.  The operation may succeed when
                        buffers become available.

     [ENOBUFS]          The output queue for a network interface is full.  This generally indicates that the
                        interface has stopped sending, but may be caused by transient congestion.

     [ENOTSOCK]         The argument socket is not a socket.

     [EOPNOTSUPP]       socket does not support (some of) the option(s) specified in flags.

     [EPIPE]            The socket is shut down for writing or the socket is connection-mode and is no
                        longer connected.  In the latter case, and if the socket is of type SOCK_STREAM, the
                        SIGPIPE signal is generated to the calling thread.

     The sendmsg() and sendto() system calls will fail if:

     [EAFNOSUPPORT]     Addresses in the specified address family cannot be used with this socket.

     [EDESTADDRREQ]     The socket is not connection-mode and does not have its peer address set, and no
                        destination address is specified.

     [EISCONN]          A destination address was specified and the socket is already connected.

     [ENOENT]           A component of the pathname does not name an existing file or the path name is an
                        empty string.

     [ENOMEM]           Insufficient memory is available to fulfill the request.

     [ENOTCONN]         The socket is connection-mode, but is not connected.

     [ENOTDIR]          A component of the path prefix of the pathname in the socket address is not a direc-tory. directory.
                        tory.

     The send() system call will fail if:

     [EDESTADDRREQ]     The socket is not connection-mode and no peer address is set.

     [ENOTCONN]         The socket is not connected or otherwise has not had the peer pre-specified.

     The sendmsg() system call will fail if:

     [EINVAL]           The sum of the iov_len values overflows an ssize_t.

     [EMSGSIZE]         The socket requires that message be sent atomically, and the size of the message to
                        be sent makes this impossible, or the msg_iovlen member of the msghdr structure
                        pointed to by message is less than or equal to  or is greater than IOV_MAX.
person Daniel Bingham    schedule 15.11.2009
comment
Нет, что я могу заметить. Но отсутствие флага ошибки довольно странно. Я так же невежественен, как и вы, в этот момент. Я буду продолжать смотреть на это ... - person Daniel Bingham; 15.11.2009
comment
Я имею в виду, помимо ранее упомянутой необходимости конвертировать ваши целые числа в сетевой порядок байтов и обратно. - person Daniel Bingham; 15.11.2009
comment
Не могли бы вы опубликовать минимальный исходный код, который демонстрирует эту проблему? - person Prof. Falken; 16.11.2009
comment
(Я имею в виду полную минимальную тестовую программу.) - person Prof. Falken; 16.11.2009

Я предполагаю, что read не работает, что приводит к com_result == -1. В этом случае значение ACK_ID — это неопределенный мусор стека. Попробуйте это вместо этого:

com_result = read(CLIENT_socket, &ACK_ID, sizeof(int), 0);
if (com_result < 0) {
    perror("read");
} else if (com_result != sizeof(int)) {
    /* handle partial read condition */
} else if (ACK_ID != metablocks[index].message_ID) {
    printf("\n\t[ERROR] Failed to receive metadata. ACK: %i\n", ACK_ID);
}

Есть ряд причин, по которым read() может дать сбой или вернуть частичный результат - в конце концов, это TCP. Perror вызовет strerror(errno) и отобразит предоставленное вами сообщение с добавленной к нему строкой ошибки. Когда системный вызов, такой как read() или send(), возвращает -1, он устанавливает errno в более описательное значение, которое вы можете отобразить с помощью perror() или strerror().

Обновление – частичное чтение

Что касается проблемы частичного чтения, вы обычно решаете ее либо (1) игнорируя ее, либо (2) выполняя чтение в цикле, пока не получите все ожидаемые байты. Что-то типа:

int status = 0;
char *byte_ptr = (char*)&ACK_ID;
ssize_t bytes_left = sizeof(ACK_ID);
while (bytes_left > 0) {
   ssize_t rc = read(CLIENT_socket, byte_ptr, bytes_left);
   if (rc < 0) {
       if (errno == EINTR) {
           continue; /* interrupted system call */
       }
       perror("read");
       status = -1;
       break;
   } else if (rc == 0) {
       /* EOF */
       break;
   }
   bytes_left -= rc;
   byte_ptr += rc;
}
if (status == 0) {
    if (bytes_left == 0) {
        /* safely use the value stored in ACK_ID */
    } else {
        /* handle premature socket closure */
    }
}

Обычно это оборачивают в общую библиотечную функцию, чтобы упростить жизнь. Я бы рекомендовал прочитать W. Сетевое программирование UNIX Ричарда Стивена, том 1, если вы еще этого не сделали. Это именно то, что он делает в своей библиотечной функции readn().

person D.Shawley    schedule 15.11.2009
comment
Частичное чтение не установит errno, использование perror довольно бесполезно - person Hasturkun; 15.11.2009

Вы можете проверить, почему send не работает. Например, используйте perror:

if (result == -1) {
    perror("server error while sending ack");
    ....

Убедитесь, что между сбойными send и perror не вызываются никакие другие функции, так как это приведет к сбросу errno.

person Andomar    schedule 15.11.2009

Во-первых, попробуйте использовать htonl перед отправкой int и использовать ntohl после его получения. Затем вы должны ВСЕГДА отправлять и получать в циклах, поэтому вы отправляете и получаете ВСЮ длину ваших данных.

person Spidey    schedule 15.11.2009