Skip to content

Latest commit

 

History

History
137 lines (75 loc) · 7.68 KB

File metadata and controls

137 lines (75 loc) · 7.68 KB

Чтение и запись: recv() и send()

Эти две системные функции предназначены для общения (обмена данными) через сокеты. Если вам нужны несоединенные сокеты датаграм, то нужно использовать: sendto() и recvfrom().

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

send()

Сигнатура:

int send(int sockfd, const void* msg, int len, int flags);

Параметры:

  1. int sockfd - дескриптор сокета, которому отправляем.

  2. const void* msg - указатель на данные, которые вы хотите отправить.

  3. int len - длина этих данных в байтах.

  4. int flags - дополнительные флаги для работы функции, можно оставить 0. (смотрите manpages про функцию send(), чтобы узнать больше про доступные флаги).

Функция возвращает количество отправленных байтов. Это количество может быть меньше, чем вы намеривались отправить!

Пример кода:

char* msg = "intfloabool был здесь!";
int len = sizeof(msg);
int bytes_sent = send(sockfd, msg, len, 0)

Если количество отправленных байтов int bytes_sent меньше чем int len, то в вашей ответственности заняться переотправкой оставшихся байтов.

Если пакет данных размером меньше 1 тысячи байт, то скорее всего он будет отправлен целиком

В случае ошибки возвращается -1 и выставляется errno, и можно вывести ее с помощью вызова perror().

recv()

Сигнатура:

int recv(int sockfd, void* buf, int len, int flags);

Параметры:

  1. int sockfd - дескриптор сокета, из которого получаем данные.

  2. void* buf - буфер, куда данные записываются.

  3. int len - максимальная длина буфера.

  4. int flags - дополнительные флаги для работы функции, можно оставить 0. (смотрите manpages про функцию recv(), чтобы узнать больше про доступные флаги).

recv() возвращает количество прочитанных байтов или -1 в случае ошибки (соответственно, выставляя errrno).

Еще recv() может вернуть 0, что означает - удаленная сторона закрыла с вами соединение.


sendto() и recvfrom() - общение датаграммами.

sendto()

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

Сигнатура функции:

int sendto(int sockfd, const void* msg, int len, unsigned int flags, const struct sockaddr* to, socklen_t tolen);

Как можно увидеть, параметры очень похожи на функцию send(), но имеют два дополнительных:

  • struct sockaddr* to - содержит адрес назначения и порт.
  • socklen_t tolen - длина адреса в байтах. Можно передать sizeof(struct sockaddr_storage)

Также как и send(), функция sendto() возвращает количество отправленных байтов (что также может быть меньше чем len !), и -1 в случае ошибки.

recvfrom()

Сигнатура функции:

int recvfrom(int sockfd, void* buf, int len, unsigned int flags, struct sockaddr* from, int* fromlen);

Она также очень похожа на recv(), но имеет два дополнительных параметра:

  • struct sockaddr* from - заполненный адрес и порт устройства, с которого получаем данные.

  • int* fromlen - указатель на локальный int, должен иметь значение sizeof(*from) или sizeof(struct sockaddr_storage)

По окончанию работы функции, fromlen будет содержать длину адреса from.

recvfrom() возвращает количество полученных байтов или -1 при возникновению ошибки (и errrno с указанием ошибки).

Мы используем struct sockaddr_storage во всех вычислениях адреса, вместо struct sockaddr_in и struct sockaddr_in6, чтобы не зависеть от конкретной версии IP протокола - IPv4 и IPv6. struct sockaddr_storage достаточно вместительный для обеих версий протокола.

Запомните, если вы используйте connect() для сокета датаграмм, вы можете продолжать использовать recv() и send() для своих транзакций. Сокет сам по себе остается UDP, а внутреннее устройство доставки пакетов автоматически подстроится под этот протокол.

close() и shutdown()

В конце работы нужно закрывать файловый дескриптор сокета, через системный вызов close():

int close(int socketfd);

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

Для большего контроля, можно использовать функцию shutdown():

int shutdown(int sockfd, int how);
  • int how указывает поведение:

    • 0 - останавливает дальнейшее получение данных.
    • 1 - останавливает дальнейшую отправку данных.
    • 2 - останавливает всё, подобно close().

shutdown() как и close() возвращает 0 при успехе и -1 в случае ошибки (и записывает errno).

shutdown() не закрывает дескриптор сокета, а меняет его поведение. Для закрытия все-равно нужно использовать close() в конце использования.