2011. Машбук (1114722), страница 50
Текст из файла (страница 50)
Если тип сокета – виртуальный канал, тосокет должен устанавливать соединение, если же тип сокета – датаграмма, то, какправило, это сокет без установления соединения, хотя последнее не является требованием.Для установления соединения служит следующая функция:#include <sys/types.h>#include <sys/socket.h>int connect (int sockfd, struct sockaddr *serv_addr,int addrlen);Здесь первый аргумент – дескриптор сокета, второй аргумент – указатель наструктуру, содержащую адрес сокета, с которым производится соединение, в формате,который мы обсуждали выше, и третий аргумент содержит реальную длину этойструктуры.
Функция возвращает 0 в случае успеха и –1 в случае неудачи, при этом кодошибки можно посмотреть в переменной errno.Заметим, что в рамках модели «клиент-сервер» клиенту, вообще говоря, не важно,какой адрес будет назначен его сокету, так как никакой процесс не будет пытатьсянепосредственно установить соединение с сокетом клиента. Поэтому клиент может невызывать предварительно функцию bind(), в этом случае при вызове connect()система автоматически выберет приемлемые значения для локального адреса клиента.Однако сказанное справедливо только для взаимодействия в рамках домена AF_INET, вдомене AF_UNIX клиентское приложение само должно позаботиться о связывании сокета.1863.3.3.2 Сервер: прослушивание сокета и подтверждение соединения.Следующие два вызова используются сервером только в том случае, еслииспользуются сокеты с предварительным установлением соединения.#include <sys/types.h>#include <sys/socket.h>int listen (int sockfd, int backlog);Этот вызов используется процессом-сервером для того, чтобы сообщить системе отом, что он готов к обработке запросов на соединение, поступающих на данный сокет.
Дотех пор, пока процесс – владелец сокета не вызовет listen(), все запросы насоединение с данным сокетом будут возвращать ошибку. Первый аргумент функции –дескриптор сокета. Второй аргумент, backlog, содержит максимальный размер очередизапросов на соединение. ОС буферизует приходящие запросы на соединение, выстраиваяих в очередь до тех пор, пока процесс не сможет их обработать.
В случае если очередьзапросов на соединение переполняется, поведение ОС зависит от того, какой протоколиспользуется для соединения. Если конкретный протокол соединения не поддерживаетвозможность перепосылки (retransmission) данных, то соответствующий вызовconnect() вернет ошибку ECONNREFUSED. Если же перепосылка поддерживается (как,например, при использовании TCP), ОС просто выбрасывает пакет, содержащий запрос насоединение, как если бы она его не получала вовсе. При этом пакет будет присылатьсяповторно до тех пор, пока очередь запросов не уменьшится и попытка соединения неувенчается успехом, либо пока не произойдет тайм-аут, определенный для протокола. Впоследнем случае вызов connect() завершится с ошибкой ETIMEDOUT.
Это позволитклиенту отличить, был ли процесс-сервер слишком занят, либо он не функционировал. Вбольшинстве систем максимальный допустимый размер очереди равен 5.Конкретное соединение устанавливается при помощи вызова accept():#include <sys/types.h>#include <sys/socket.h>int accept (int sockfd, struct sockaddr *addr,int *addrlen);Этот вызов применяется сервером для удовлетворения поступившего клиентскогозапроса на соединение с сокетом, который сервер к тому моменту уже прослушивает (т.е.предварительно была вызвана функция listen()). Вызов accept() извлекает первыйзапрос из очереди запросов, ожидающих соединения, и устанавливает с ним соединение.Если к моменту вызова accept() очередь запросов на соединение пуста, процесс,вызвавший accept(), блокируется до поступления запросов.Когда запрос поступает и соединение устанавливается, accept() создает новыйсокет, который будет использоваться для работы с данным соединением, и возвращаетдескриптор этого нового сокета, соединенного с сокетом клиентского процесса.
При этомпервоначальный сокет продолжает оставаться в состоянии прослушивания. Через новыйсокет осуществляется обмен данными, в то время как старый сокет продолжаетобрабатывать другие поступающие запросы на соединение (напомним, что именнопервоначально созданный сокет связан с адресом, известным клиентам, поэтому всеклиенты могут слать запросы только на соединение с этим сокетом). Это позволяетпроцессу-серверу поддерживать несколько соединений одновременно.
Обычно этореализуется путем порождения для каждого установленного соединения отдельногопроцесса-потомка, который занимается собственно обменом данными только с этимконкретным клиентом, в то время как процесс-родитель продолжает прослушиватьпервоначальный сокет и порождать новые соединения.Во втором параметре передается указатель на структуру, в которой возвращаетсяадрес клиентского сокета, с которым установлено соединение, а в третьем параметревозвращается реальная длина этой структуры. Благодаря этому сервер всегда знает, куда187ему в случае надобности следует послать ответное сообщение.
Если адрес клиента нас неинтересует, в качестве второго аргумента можно передать NULL.3.3.4 Прием и передача данныхСобственно для приема и передачи данных через сокет используются три парыфункций.#include <sys/types.h>#include <sys/socket.h>int send(int sockfd, const void *msg, int len,unsigned int flags);int recv(int sockfd, void *buf, int len,unsigned int flags);Эти функции используются для обмена только через сокет с предварительноустановленным соединением. Аргументы функции send(): sockfd – дескрипторсокета, через который передаются данные, msg и len - сообщение и его длина. Еслисообщение слишком длинное для того протокола, который используется при соединении,оно не передается и вызов возвращает ошибку EMSGSIZE.
Если же сокет окажетсяпереполнен, т.е. в его буфере не хватит места, чтобы поместить туда сообщение,выполнение процесса блокируется до появления возможности поместить сообщение.Функция send() возвращает количество переданных байт в случае успеха и -1 в случаенеудачи. Код ошибки при этом устанавливается в errno. Аргументы функции recv()аналогичны: sockfd – дескриптор сокета, buf и len – указатель на буфер для приемаданных и его первоначальная длина. В случае успеха функция возвращает количествосчитанных байт, в случае неудачи -17.Последний аргумент обеих функций – flags – может содержать комбинациюспециальных опций. Нас будут интересовать две из них:MSG_OOB – этот флаг сообщает ОС, что процесс хочет осуществитьприем/передачу экстренных сообщенийMSG_PEEK – данный флаг может устанавливаться при вызове recv().
При этомпроцесс получает возможность прочитать порцию данных, не удаляя ее из сокета, такимобразом, что последующий вызов recv() вновь вернет те же самые данные.Другая пара функций, которые могут использоваться при работе с сокетами спредварительно установленным соединением – это обычные read() и write(), вкачестве дескриптора которым передается дескриптор сокета.И, наконец, пара функций, которая может быть использована как с сокетами сустановлением соединения, так и с сокетами без установления соединения:#include <sys/types.h>#include <sys/socket.h>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);Первые 4 аргумента у них такие же, как и у рассмотренных выше.
В последнихдвух в функцию sendto() должны быть переданы указатель на структуру, содержащуюадрес получателя, и ее размер,а функция recvfrom() в них возвращаетсоответственно указатель на структуру с адресом отправителя и ее реальный размер.7Отметим, что, как уже говорилось, при использовании сокетов с установлением виртуальногосоединения границы сообщений не сохраняются, поэтому приложение, принимающее сообщения, можетпринимать данные совсем не теми же порциями, какими они были посланы. Вся работа по интерпретациисообщений возлагается на приложение.188Отметим, что перед вызовом recvfrom() параметр fromlen должен быть установленравным первоначальному размеру структуры from.
Здесь, как и в функции accept, еслинас не интересует адрес отправителя, в качестве from можно передать NULL.3.3.5 Завершение работы с сокетомЕсли процесс закончил прием либо передачу данных, ему следует закрытьсоединение. Это можно сделать с помощью функции shutdown():# include <sys/types.h># include <sys/socket.h>int shutdown (int sockfd, int mode);Помимо дескриптора сокета, ей передается целое число, которое определяет режимзакрытия соединения. Если mode=0, то сокет закрывается для чтения, при этом вседальнейшие попытки чтения будут возвращать EOF. Если mode=1, то сокет закрываетсядля записи, и при осуществлении в дальнейшем попытки передать данные будет выданкода неудачного завершения (-1).
Если mode=2, то сокет закрывается и для чтения, и длязаписи.Аналогично файловому дескриптору, дескриптор сокета освобождается системнымвызовом close(). При этом, разумеется, даже если до этого не был вызванshutdown(), соединение будет закрыто. Таким образом, в принципе, если по окончанииработы с сокетом мы собираемся закрыть соединение и по чтению, и по записи, можнобыло бы сразу вызвать close() для дескриптора данного сокета, опустив вызовshutdown(). Однако, есть небольшое различие с тем случаем, когда предварительнобыл вызван shutdown(). Если используемый для соединения протокол гарантируетдоставку данных (т.е.
тип сокета – виртуальный канал), то вызов close() будетблокирован до тех пор, пока система будет пытаться доставить все данные, находящиеся«в пути» (если таковые имеются), в то время как вызов shutdown() извещает систему отом, что эти данные уже не нужны и можно не предпринимать попыток их доставить, исоединение закрывается немедленно. Таким образом, вызов shutdown() важен в первуюочередь для закрытия соединения сокета с использованием виртуального канала.3.3.6 Резюме: общая схема работы с сокетамиМеханизм сокетов включает в свой состав достаточно разнообразные средства,позволяющие организовывать взаимодействие различной топологии.