Билеты (Graur) (1114774), страница 25
Текст из файла (страница 25)
Поэтому для успешноговыполнения bind() необходимо, чтобы такого файла не существовало к данномумоменту. Это следует учитывать, если мы «зашиваем» в программу определенноеимя и намерены запускать нашу программу несколько раз на одной и той жемашине – в этом случае для успешной работы bind() необходимо удалять файл сэтим именем перед связыванием. Кроме того, в процессе создания файла,естественно, проверяются права доступа пользователя, от имени которогопроизводится вызов, ко всем директориям, фигурирующим в полном путевомимени файла, что тоже необходимо учитывать при задании имени. Если правадоступа к одной из директорий недостаточны, вызов bind() завершитсянеуспешно.В случае успешного связывания bind() возвращает 0, в случае ошибки – -1.Предварительное установление соединения.Сокеты с установлением соединения.
Запрос на соединение.Различают сокеты с предварительным установлением соединения, когда доначала передачи данных устанавливаются адреса сокетов отправителя и получателяданных – такие сокеты соединяются друг с другом и остаются соединенными доокончания обмена данными; и сокеты без установления соединения, когдасоединение до начала передачи данных не устанавливается, а адреса сокетовотправителя и получателя передаются с каждым сообщением. Если тип сокета –виртуальный канал, то сокет должен устанавливать соединение, если же типсокета – датаграмма, то, как правило, это сокет без установления соединения, хотяпоследнее не является требованием.
Для установления соединения служитследующая функция:#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 клиентское приложение само должнопозаботиться о связывании сокета.Сервер: прослушивание сокета и подтверждение соединения.Следующие два вызова используются сервером только в том случае, еслииспользуются сокеты с предварительным установлением соединения.#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() создает новыйсокет, который будет использоваться для работы с данным соединением, ивозвращает дескриптор этого нового сокета, соединенного с сокетом клиентскогопроцесса.
При этом первоначальный сокет продолжает оставаться в состояниипрослушивания. Через новый сокет осуществляется обмен данными, в то время какстарый сокет продолжает обрабатывать другие поступающие запросы насоединение (напомним, что именно первоначально созданный сокет связан садресом, известным клиентам, поэтому все клиенты могут слать запросы только насоединение с этим сокетом). Это позволяет процессу-серверу поддерживатьнесколько соединений одновременно. Обычно это реализуется путем порождениядля каждого установленного соединения отдельного процесса-потомка, которыйзанимается собственно обменом данными только с этим конкретным клиентом, вто время как процесс-родитель продолжает прослушивать первоначальный сокет ипорождать новые соединения (см.
Error! Reference source not found.).Во втором параметре передается указатель на структуру, в которой возвращаетсяадрес клиентского сокета, с которым установлено соединение, а в третьемпараметре возвращается реальная длина этой структуры. Благодаря этому сервервсегда знает, куда ему в случае надобности следует послать ответное сообщение.Если адрес клиента нас не интересует, в качестве второго аргумента можнопередать NULL.Прием и передача данных.Собственно для приема и передачи данных через сокет используются три парыфункций.#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 intflags);Эти функции используются для обмена только через сокет с предварительноустановленным соединением.
Аргументы функции send(): sockfd –дескриптор сокета, через который передаются данные, msg и len - сообщение иего длина. Если сообщение слишком длинное для того протокола, которыйиспользуется при соединении, оно не передается и вызов возвращает ошибкуEMSGSIZE. Если же сокет окажется переполнен, т.е.
в его буфере не хватит места,чтобы поместить туда сообщение, выполнение процесса блокируется до появлениявозможности поместить сообщение. Функция send() возвращает количествопереданных байт в случае успеха и -1 в случае неудачи. Код ошибки при этомустанавливается в errno. Аргументы функции recv() аналогичны: sockfd –дескриптор сокета, buf и len – указатель на буфер для приема данных и егопервоначальная длина.
В случае успеха функция возвращает количество считанныхбайт, в случае неудачи -16.Последний аргумент обеих функций – 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, inttolen);int recvfrom(int sockfd, void *buf, int len, unsignedint flags, struct sockaddr *from, int *fromlen);Первые 4 аргумента у них такие же, как и у рассмотренных выше.
В последнихдвух в функцию sendto() должны быть переданы указатель на структуру,содержащую адрес получателя, и ее размер, а функция recvfrom() в нихвозвращает соответственно указатель на структуру с адресом отправителя и еереальный размер. Отметим, что перед вызовом recvfrom() параметр fromlenдолжен быть установлен равным первоначальному размеру структуры from. Здесь,как и в функции accept, если нас не интересует адрес отправителя, в качествеfrom можно передать NULL.Завершение работы с сокетом.Если процесс закончил прием либо передачу данных, ему следует закрытьсоединение.
Это можно сделать с помощью функции shutdown():# include <sys/types.h># include <sys/socket.h>int shutdown (int sockfd, int mode);6Отметим, что, как уже говорилось, при использовании сокетов с установлениемвиртуального соединения границы сообщений не сохраняются, поэтому приложение, принимающеесообщения, может принимать данные совсем не теми же порциями, какими они были посланы. Вся работапо интерпретации сообщений возлагается на приложение.Помимо дескриптора сокета, ей передается целое число, которое определяет режимзакрытия соединения. Если mode=0, то сокет закрывается для чтения, при этом вседальнейшие попытки чтения будут возвращать EOF. Если mode=1, то сокетзакрывается для записи, и при осуществлении в дальнейшем попытки передатьданные будет выдан кода неудачного завершения (-1). Если mode=2, то сокетзакрывается и для чтения, и для записи.Аналогично файловому дескриптору, дескриптор сокета освобождается системнымвызовом close().