Лекции (1171139), страница 22
Текст из файла (страница 22)
Совместное использование с параметром AF_INETсвязывает сокет с протоколом UDPSOCK_RAW. Этот тип присваивается низкоуровневым (т. н. "сырым") сокетам. Их отличие от обычных сокетовсостоит в том, что с их помощью программа может взять на себя формирование некоторых заголовков,добавляемых к сообщению.Обратите внимание, что не все домены допускают задание произвольного типа сокета. Например, совместнос доменом Unix используется только тип SOCK_STREAM. С другой стороны, для Internet-домена можно задаватьлюбой из перечисленных типов. В этом случае для реализации SOCK_STREAM используется протокол TCP, дляреализации SOCK_DGRAM - протокол UDP, а тип SOCK_RAW используется для низкоуровневой работы спротоколами IP, ICMP и т.
д.Наконец, последний атрибут определяет протокол, используемый для передачи данных. Как мы только чтовидели, часто протокол однозначно определяется по домену и типу сокета. В этом случае в качестве третьегопараметра функции socket можно передать 0, что соответствует протоколу по умолчанию. Тем не менее, иногда(например, при работе с низкоуровневыми сокетами) требуется задать протокол явно.АдресаПрежде чем передавать данные через сокет, его необходимо связать с адресом в выбранном домене (этупроцедуру называют именованием сокета).
Иногда связывание осуществляется неявно (внутри функций connect иaccept), но выполнять его необходимо во всех случаях. Вид адреса зависит от выбранного вами домена. В Unixдомене это текстовая строка - имя файла, через который происходит обмен данными. В Internet-домене адресзадаётся комбинацией IP-адреса и 16-битного номера порта. IP-адрес определяет хост в сети, а порт - конкретныйсокет на этом хосте. Протоколы TCP и UDP используют различные пространства портов.Для явного связывания сокета с некоторым адресом используется функция bind. Её прототип имеет вид:int bind(int sockfd, struct sockaddr *addr, int addrlen);В качестве первого параметра передаётся дескриптор сокета, который мы хотим привязать к заданномуадресу.
Второй параметр, addr, содержит указатель на структуру с адресом, а третий - длину этой структуры.Посмотрим, что она собой представляет.struct sockaddr {unsigned shortchar};sa_family;sa_data[14];// Семейство адресов, AF_xxx// 14 байтов для хранения адресаПоле sa_family содержит идентификатор домена, тот же, что и первый параметр функции socket. Взависимости от значения этого поля по-разному интерпретируется содержимое массива sa_data. Разумеется,80работать с этим массивом напрямую не очень удобно, поэтому вы можете использовать вместо sockaddr одну изальтернативных структур вида sockaddr_XX (XX - суффикс, обозначающий домен: "un" - Unix, "in" - Internet и т.д.).
При передаче в функцию bind указатель на эту структуру приводится к указателю на sockaddr. Рассмотрим дляпримера структуру sockaddr_in.struct sockaddr_in {short intsin_family; // Семейство адресовunsigned short int sin_port;// Номер портаstruct in_addrsin_addr;// IP-адресunsigned charsin_zero[8]; // "Дополнение" до размера структурыsockaddr};Здесь поле sin_family соответствует полю sa_family в sockaddr, в sin_port записывается номер порта, а вsin_addr - IP-адрес хоста.
Поле sin_addr само является структурой, которая имеет вид:struct in_addr {unsigned long s_addr;};Зачем понадобилось заключать всего одно поле в структуру? Дело в том, что раньше in_addr представляласобой объединение (union), содержащее гораздо большее число полей. Сейчас, когда в ней осталось всего однополе, она продолжает использоваться для обратной совместимости.И ещё одно важное замечание. Существует два порядка хранения байтов в слове и двойном слове. Один изних называется порядком хоста (host byte order), другой - сетевым порядком (network byte order) хранения байтов.При указании IP-адреса и номера порта необходимо преобразовать число из порядка хоста в сетевой.
Для этогоиспользуются функции htons (Host TO Network Short) и htonl (Host TO Network Long). Обратное преобразованиевыполняют функции ntohs и ntohl.Установка соединения (клиент)(только TCP/IP)На стороне клиента для установления соединения используется функция connect, которая имеет следующийпрототип.int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);Здесь sockfd - сокет, который будет использоваться для обмена данными с сервером, serv_addr содержит указательна структуру с адресом сервера, а addrlen - длину этой структуры.
Обычно сокет не требуется предварительнопривязывать к локальному адресу, так как функция connect сделает это за вас, подобрав подходящий свободныйпорт. Вы можете принудительно назначить клиентскому сокету некоторый номер порта, используя bind передвызовом connect. Однако проще и надёжнее предоставить системе выбрать порт за вас.Обмен даннымиПосле того как соединение установлено, можно начинать обмен данными.
Для этого используютсяфункции send и recv. Функция send используется для отправки данных и имеет следующий прототип.int send(int sockfd, const void *msg, int len, int flags);Здесь sockfd - это, как всегда, дескриптор сокета, через который мы отправляем данные, msg - указатель на буфер сданными, len - длина буфера в байтах, а flags - набор битовых флагов, управляющих работой функции (если флагине используются, передайте функции 0). Вот некоторые из них (полный список можно найти в документации):MSG_OOB. Предписывает отправить данные как срочные (out of band data, OOB).
Концепция срочных данныхпозволяет иметь два параллельных канала данных в одном соединении. Иногда это бывает удобно. Например,Telnet использует срочные данные для передачи команд типа Ctrl+C. В настоящее время использовать их нерекомендуется из-за проблем с совместимостью (существует два разных стандарта их использования, описанные вRFC793 и RFC1122).
Безопаснее просто создать для срочных данных отдельное соединение.MSG_DONTROUTE. Запрещает маршрутизацию пакетов. Нижележащие транспортные слои могутпроигнорировать этот флаг.Функция send возвращает число байтов, которое на самом деле было отправлено (или -1 в случае ошибки). Эточисло может быть меньше указанного размера буфера.
Если вы хотите отправить весь буфер целиком, вампридётся написать свою функцию и вызывать в ней send, пока все данные не будут отправлены.Для чтения данных из сокета используется функция recv.int recv(int sockfd, void *buf, int len, int flags);В целом её использование аналогично send. Она точно так же принимает дескриптор сокета, указатель на буфер инабор флагов. Флаг MSG_OOB используется для приёма срочных данных, а MSG_PEEK позволяет "подсмотреть"данные, полученные от удалённого хоста, не удаляя их из системного буфера (это означает, что при следующемобращении к recv вы получите те же самые данные).
Полный список флагов можно найти в документации. Поаналогии с send функция recv возвращает количество прочитанных байтов, которое может быть меньше размерабуфера.81Обмен датаграммамиКак уже говорилось, датаграммы используются в программах довольно редко. В большинстве случаев надёжностьпередачи критична для приложения, и вместо изобретения собственного надёжного протокола поверх UDPпрограммисты предпочитают использовать TCP. Тем не менее, иногда датаграммы оказываются полезны.Например, их удобно использовать при транслировании звука или видео по сети в реальном времени, особеннопри широковещательном транслировании.Поскольку для обмена датаграммами не нужно устанавливать соединение, использовать их гораздо проще.
Создавсокет с помощью socket и bind, вы можете тут же использовать его для отправки или получения данных. Для этоговам понадобятся функции sendto и recvfrom.int sendto(int sockfd, const void *msg, int len, unsigned int flags,const struct sockaddr *to, int tolen);int recvfrom(int sockfd, void *buf, int len, unsigned int flags,struct sockaddr *from, int *fromlen);Функция sendto очень похожа на send. Два дополнительных параметра to и tolen используются для указания адресаполучателя. Для задания адреса используется структура sockaddr, как и в случае с функцией connect.
Функцияrecvfrom работает аналогично recv. Получив очередное сообщение, она записывает его адрес в структуру, накоторую ссылается from, а записанное количество байт - в переменную, адресуемую указателем fromlen. Как мызнаем, аналогичным образом работает функция accept.Некоторую путаницу вносят присоединённые датаграммные сокеты (connected datagram sockets). Дело в том, чтодля сокета с типом SOCK_DGRAM тоже можно вызвать функцию connect, а затем использовать send и recv дляобмена данными. Нужно понимать, что никакого соединения при этом не устанавливается.
Операционная системапросто запоминает адрес, который вы передали функции connect, а затем использует его при отправке данных.Обратите внимание, что присоединённый сокет может получать данные только от сокета, с которым он соединён.Закрытие сокетаЗакончив обмен данными, закройте сокет с помощью функции close (под Windows closesocket ).Это приведёт кразрыву соединения.closesocket(tcp_socket);close(tcp_socket);Особенности работы под WindowsДля инициализации подсистемы Сокетов нужно вызвать функцию WSAStartup.WSADATA wsaData;WSAStartup(MAKEWORD(2,2), &wsaData);В конце программы нужно вызвать WSACleanup();Структура приложения-клиента TCPWSAStartupsocketconnectsendrecvclosesocketWSACleanupСтруктура приложения-клиента UDPWSAStartupsocketsendtorecvftomclosesocketWSACleanupУстановка соединения (сервер).Установка соединения на стороне сервера состоит из четырёх этапов, ни один изкоторых не может быть опущен.
Сначала сокет создаётся и привязывается к локальному адресу.Если компьютер имеет несколько сетевых интерфейсов с различными IP-адресами, вы можетепринимать соединения только с одного из них, передав его адрес функции bind. Если же выготовы соединяться с клиентами через любой интерфейс, задайте в качестве адреса константуINADDR_ANY. Что касается номера порта, вы можете задать конкретный номер или 0 (в этомслучае система сама выберет произвольный неиспользуемый в данный момент номер порта).На следующем шаге создаётся очередь запросов на соединение.
При этом сокет переводится врежим ожидания запросов со стороны клиентов. Всё это выполняет функция listen.int listen(int sockfd, int backlog);82Первый параметр - дескриптор сокета, а второй задаёт размер очереди запросов. Каждый раз,когда очередной клиент пытается соединиться с сервером, его запрос ставится в очередь, таккак сервер может быть занят обработкой других запросов. Если очередь заполнена, всепоследующие запросы будут игнорироваться.