Вопрос

Я немного запутался в программировании сокетов на C.

Вы создаете сокет, привязываете его к интерфейсу и IP-адресу и заставляете его прослушивать.Я нашел пару веб-ресурсов по этому вопросу и прекрасно все понял.В частности, я нашел статью Сетевое программирование в системах Unix быть очень информативным.

Что меня смущает, так это время поступления данных в сокет.

Как узнать, когда прибудут пакеты и насколько они велики? Вам придется выполнять всю тяжелую работу самостоятельно?

Мое основное предположение здесь заключается в том, что пакеты могут иметь переменную длину, поэтому, как только двоичные данные начнут появляться в сокете, как вы начнете создавать из них пакеты?

Это было полезно?

Решение

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

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

Вот типичная функция C для чтения заданного количества байтов из сокета:

/* buffer points to memory block that is bigger than the number of bytes to be read */
/* socket is open socket that is connected to a sender */
/* bytesToRead is the number of bytes expected from the sender */
/* bytesRead is a pointer to a integer variable that will hold the number of bytes */
/*           actually received from the sender. */
/* The function returns either the number of bytes read, */
/*                             0 if the socket was closed by the sender, and */
/*                            -1 if an error occurred while reading from the socket */
int readBytes(int socket, char *buffer, int bytesToRead, int *bytesRead)
{
    *bytesRead = 0;
    while(*bytesRead < bytesToRead)
    {
        int ret = read(socket, buffer + *bytesRead, bytesToRead - *bytesRead);
        if(ret <= 0)
        {
           /* either connection was closed or an error occurred */
           return ret;
        }
        else
        {
           *bytesRead += ret;
        }
    }
    return *bytesRead;
}

Другие советы

Итак, ответ на ваш вопрос во многом зависит от того, используете ли вы UDP или TCP в качестве транспорта.

Для UDP жизнь становится намного проще: вы можете вызвать Recv/recvfrom/recvmsg с нужным вам размером пакета (вероятно, вы все равно будете отправлять пакеты фиксированной длины из источника) и сделать предположение, что если данные доступны , он там кратен размерам пакетов.(И.Е.Вы вызываете Recv*, указав размер отправляющего пакета, и все готово.)

С TCP жизнь становится немного интереснее — для целей данного объяснения я предполагаю, что вы уже знаете, как использовать функции socket(),bind(), Listen() и Accept() — последний способ получения файловый дескриптор (FD) вашего нового соединения.

Существует два способа выполнения ввода-вывода для сокета: блокировка, при которой вы вызываете read(fd, buf, N), а операция чтения находится там и ждет, пока вы не прочитаете N байтов в buf, или неблокировка. в котором вам нужно проверить (с помощью select() или poll()), доступен ли чтение FD, и ЗАТЕМ выполните read().

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

Если ваше приложение использует «пакеты» (упакованные или распакованные структуры данных, которые вы передаете), вы должны иметь возможность вызывать read() с правильным аргументом размера и считывать всю структуру данных из сокета за раз.Единственное предостережение, с которым вам придется иметь дело, — это не забывать правильно упорядочивать байты любых данных, которые вы отправляете, в случае, если исходная и целевая системы имеют разный порядок байтов.Это относится как к UDP, так и к TCP.

Что касается программирования сокетов *NIX, я настоятельно рекомендую W.Ричард Стивенс «Сетевое программирование для Unix, Vol.1» (UNPv1) и «Расширенное программирование в среде Unix» (APUE).Первый — это том, посвященный сетевому программированию, независимо от транспорта, а второй — хорошая всесторонняя книга по программированию, применимая к программированию на базе *NIX.Также найдите «TCP/IP Illustrated», тома 1 и 2.

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

Сокеты работают на более высоком уровне, чем необработанные пакеты — это похоже на файл, из которого можно читать/записывать.Кроме того, когда вы пытаетесь прочитать данные из сокета, операционная система заблокирует (приостановит) ваш процесс до тех пор, пока у нее не появятся данные для выполнения запроса.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top