Что такое сокеты в программировании

Что такое сокеты в программировании

Сокеты (англ. socket — разъём) — название программного интерфейса для обеспечения обмена данными между процессами. Процессы при таком обмене могут исполняться как на одной ЭВМ, так и на различных ЭВМ, связанных между собой сетью. Сокет — абстрактный объект, представляющий конечную точку соединения.

Принципы сокетов¶

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

Каждый сокет имеет свой адрес. ОС семейства UNIX могут поддерживать много типов адресов, но обязательными являются INET-адрес и UNIX-адрес. Если привязать сокет к UNIX-адресу, то будет создан специальный файл (файл сокета) по заданному пути, через который смогут сообщаться любые локальные процессы путём чтения/записи из него (см. Доменный сокет Unix). Сокеты типа INET доступны из сети и требуют выделения номера порта.

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

Основные функции¶

Общие
Socket Создать новый сокет и вернуть файловый дескриптор
Send Отправить данные по сети
Receive Получить данные из сети
Close Закрыть соединение
Серверные
Bind Связать сокет с IP-адресом и портом
Listen Объявить о желании принимать соединения. Слушает порт и ждет когда будет установлено соединение
Accept Принять запрос на установку соединения
Клиентские
Connect Установить соединение

socket()¶

Создаёт конечную точку соединения и возвращает файловый дескриптор. Принимает три аргумента:

domain указывающий семейство протоколов создаваемого сокета

  • AF_INET для сетевого протокола IPv4
  • AF_INET6 для IPv6
  • AF_UNIX для локальных сокетов (используя файл)

type

  • SOCK_STREAM (надёжная потокоориентированная служба (сервис) или потоковый сокет)
  • SOCK_DGRAM (служба датаграмм или датаграммный сокет)
  • SOCK_RAW (Сырой сокет — сырой протокол поверх сетевого уровня).

protocol

Протоколы обозначаются символьными константами с префиксом IPPROTO_* (например, IPPROTO_TCP или IPPROTO_UDP). Допускается значение protocol=0 (протокол не указан), в этом случае используется значение по умолчанию для данного вида соединений.

Функция возвращает −1 в случае ошибки. Иначе, она возвращает целое число, представляющее присвоенный дескриптор.

Пример на Python

Связывает сокет с конкретным адресом. Когда сокет создается при помощи socket(), он ассоциируется с некоторым семейством адресов, но не с конкретным адресом. До того как сокет сможет принять входящие соединения, он должен быть связан с адресом. bind() принимает три аргумента:

  1. sockfd — дескриптор, представляющий сокет при привязке
  2. serv_addr — указатель на структуру sockaddr, представляющую адрес, к которому привязываем.
  3. addrlen — поле socklen_t, представляющее длину структуры sockaddr.

Возвращает 0 при успехе и −1 при возникновении ошибки.

Пример на Python

Автоматическое получение имени хоста.

listen()¶

Подготавливает привязываемый сокет к принятию входящих соединений. Данная функция применима только к типам сокетов SOCK_STREAM и SOCK_SEQPACKET. Принимает два аргумента:

  1. sockfd — корректный дескриптор сокета.
  2. backlog — целое число, означающее число установленных соединений, которые могут быть обработаны в любой момент времени. Операционная система обычно ставит его равным максимальному значению.

После принятия соединения оно выводится из очереди. В случае успеха возвращается 0, в случае возникновения ошибки возвращается −1.

Пример на Python

accept()¶

Используется для принятия запроса на установление соединения от удаленного хоста. Принимает следующие аргументы:

  1. sockfd — дескриптор слушающего сокета на принятие соединения.
  2. cliaddr — указатель на структуру sockaddr, для принятия информации об адресе клиента.
  3. addrlen — указатель на socklen_t, определяющее размер структуры, содержащей клиентский адрес и переданной в accept(). Когда accept() возвращает некоторое значение, socklen_t указывает сколько байт структуры cliaddr использовано в данный момент.

Функция возвращает дескриптор сокета, связанный с принятым соединением, или −1 в случае возникновения ошибки.

Пример на Python

connect()¶

Устанавливает соединение с сервером.

Некоторые типы сокетов работают без установления соединения, это в основном касается UDP-сокетов. Для них соединение приобретает особое значение: цель по умолчанию для посылки и получения данных присваивается переданному адресу, позволяя использовать такие функции как send() и recv() на сокетах без установления соединения.

Загруженный сервер может отвергнуть попытку соединения, поэтому в некоторых видах программ необходимо предусмотреть повторные попытки соединения.

Возвращает целое число, представляющее код ошибки: 0 означает успешное выполнение, а −1 свидетельствует об ошибке.

Пример на Python

Передача данных¶

Для передачи данных можно пользоваться стандартными функциями чтения/записи файлов read и write, но есть специальные функции для передачи данных через сокеты:

Нужно обратить внимание, что при использовании протокола TCP (сокеты типа SOCK_STREAM) есть вероятность получить меньше данных, чем было передано, так как ещё не все данные были переданы, поэтому нужно либо дождаться, когда функция recv возвратит 0 байт, либо выставить флаг MSG_WAITALL для функции recv, что заставит её дождаться окончания передачи. Для остальных типов сокетов флаг MSG_WAITALL ничего не меняет (например, в UDP весь пакет = целое сообщение).

send, sendto — отправка данных.

Приветствую Вас, уважаемый читатель! Предоставляю Вашему суровому вниманию свою вторую статью, "Приёмы сетевого программирования: Сокетный движок". Надеюсь, чтение этой статьи будет для Вас не только полезным, но и приятным.

Для людей, щепетильных в вопросах оригинальности и уникальности, вроде "хочу всегда только свежее!", следует отметить с самого начала, что тема, поднимаемая мной в этой статье — это далеко не новшество в сетевом программировании. Те или иные модели сокетных движков применяются в реализации прокси-серверов, сканеров сетей и в других программах, немноготредово реализующих обработку большого количества сокетов.

Для понимания технических деталей этой статьи, потребуются:

1. Базовый опыт в программировании на классическом ANSI C;
2. Базовые знания стандрата posix.1b, в частности, системных вызовов BSD Sockets и файловых API UNIX и/или знания WinSock2 API под Windows (в идеале, и то, и другое);
3. Небольшой опыт в программировании сетевых задач или в анализе кода сетевых программ.

Надеюсь, пользователи UNIX не брезгуют программой man, а пользователи Windows всегда смогут найти руководство по WinSock2 в хелпах тех IDE, в которых они привыкли работать. Кроме того, в Интернете более чем достаточно учебников и по [1], по [2] и по [3].

Сокетный движок: что это такое?

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

Этот метод сугубо противоположен вызову отдельного процесса или потока выполнения для обработки каждого конкретного сокета. И именно в этом заключается основной его плюс (многократное понижение требований к ресурсам) и минус (многократное повышение требований к структурированию алгоритма программы).

В качестве примера реализации самого простого сокетного движка я могу предложить
исходный текст программы Grinder v1.1
(49Kb), конкретно в файлах и . Здесь Вы можете взять бинарник этой программы под
WinSock (40 Kb).

Блокирующие вызовы сокетных API

Прежде чем заняться моделированием сокетного движка, следует чётко уяснить разницу между блокирующими и неблокирующими вызовами сокетных API. Возьмём самый простой пример, в котором мы коннектимся к удалённому http-серверу и получаем список поддерживаемых им команд (пример для WinSock2):

Читайте также:  Можно ли ставить конденсатор большего вольтажа

#include
.
SOCKET s;
int rc;
struct sockaddr_in dst;
char buf[8192];
.
memset(buf,0,8129);
memset(&dst,0,sizeof(struct sockaddr_in));
dst.sin_addr.s_addr = inet_addr("XXX.XXX.XXX.XXX");
dst.sin_port = htons(80);
dst.sin_family = AF_INET;
s = socket(AF_INET,SOCK_STREAM,0);
rc = connect(s,(struct sockaddr*)&dst,sizeof(dst));
if(rc == SOCKET_ERROR) <
printf("Error occured while connecting!
");
return 1;
>
rc = send(s,"OPTIONS / HTTP/1.1x0Dx0Ax0Dx0A",22,0);
if(rc

Сценарий задачи описан линейно: мы просим у ядра сокет и коннектимся по структуре dst. В случае успеха, мы отправляем данные на сервер и, опять же, в случае успеха, принимаем данные с сервера.

Вызовы connect() и recv() в данном примере являются блокирующими: они блокируют ход выполнения вызывающей их функции на время своего исполнения, которое может длиться неопределённое время. Вызов send() блокирующим не является: и в UNIX, и в Windows сокетный дескриптор представляет собой FIFO-структуру и send() лишь добавляет в очередь сокета необходимое количество данных, сразу же возвращая управление.

Программы, построенные на так называемых "тредовых движках", вызывают отдельный поток выполнения для обработки каждого сокета и такого рода издержки слабо влияют на динамичность программы в целом. Однако, этот приём, мягко скажем, не очень уместен в том случае, когда нам необходимо обрабатывать большое количество сокетов (до нескольких тысяч): не открывать же для каждого сокета отдельный поток! :))) Даже несколько десятков потоков — это солидная и неоправданная нагрузка на ядро системы. Которую, впрочем, можно избежать.

Неблокирующие вызовы сокетных API

Когда мы открываем сокет в UNIX, ядро устанавливает ему дефолтный атрибут доступа: O_RDWR, "чтение/запись". С помощью файлого API fcntl() мы можем установить для открытого сокета неблокирующий режим работы, сохранив бит O_RDWR:

.
s = socket(AF_INET,SOCK_STREAM,0);
if(s
Следует отметить, что константа O_NONBLOCK в разных UNIX’ах может называться по-разному. Мне встречались такие: O_NDELAY, FNONBIO. Если Ваша система не знает O_NONBLOCK, загляните к себе в /usr/include/sys/fcntl.h.

Таким образом, любая операция чтения или записи по дескриптору ‘s’ не будет блокировать ход выполнения вызывающей функции и будет моментально возвращать управление вызывающей функции. Для того, чтобы закрыть сокет, нам необходимо вернуть дескриптору ‘s’ его первоначальное состояние:

WinSock (и первый, и второй), насколько это возможно в рамках ОС Windows, копирует названия и синтаксис API BSD Sockets. Принципиальное различие заключается в том, что API BSD Sockets "воспринимают" сокеты как файловые дескрипторы, к которым можно применять файловые API UNIX. В WinSock для сокетов выделен специальный тип данных, который обрабатывается особыми API. Открываем сокет и переводим его в неблокирующий режим:

on = 1;
WSADATA wsaData;
.
if (WSAStartup(MAKEWORD(2,1),&wsaData) != 0) <
printf("Bad WinSock!
");
return 1;
>
.
s = socket(AF_INET,SOCK_STREAM,0);
if(s == SOCKET_ERROR) <
printf("Error allocating a socket!
");
return 1;
>
ioctlsocket(s,FIONBIO,&on);
.

Вводим сокет в блокирующий режим и закрываем его:

off = 0;
.
ioctlsocket(s,FIONBIO,&off);
closesocket(s);
WSACleanup();
.

Чтение из неблокирующего сокета и запись в него

Как уже было отмечено ранее, вызовы connect() и recv() в неблокирующем режиме возвращают управление моментально, в не зависимости от результата выполнения и состояния сокета. Для того, чтобы алгоритм программы имел возможность адекватно и динамично реагировать на состояния сокетов, необходим инструмент контроля этих состояний.

В стандарте posix.1 для UNIX, такой инструмент реализован в виде файлового API select() и макросов FD_*. WinSock2 полностью копирует вызов select() и макросы FD_*, за исключением того, что select() в WinSock2 фактически игнорирует значение первого аргумента, сообщающего функции select() количество сокетов, которые нужно обработать. Для обратной совместимости, всё же, имеет смысл всегда сообщать select()’у значение FD_SETSIZE для сохранения обратной совместимости, а компилятор уже подставит вместо FD_SETSIZE нужное значение.

Рассмотрим следующий пример (действительный и для Windows, и для UNIX): мы имеем некую структуру сокетных дескрипторов int *s в количестве snum, уже открытых и обрабатываемых некой программой. Нам необходимо отобрать среди всех этих сокетов те, из которых мы имеем возможность произвести чтение в данный момент времени. И произвести эту запись.

int i, rc;
#ifdef POSIX
int * s; // указатель на пачку сокетов для posix
#else
SOCKET * s; // указатель на пачку сокетов для Windows
#endif
int snum; // количество сокетов
fd_set fdset; // дескриптор сокетов
struct timeval tv = <0,0>; // 0 секунд, 0 микросекунд
unsigned char * data_to_recv; // буфер
.
FD_ZERO(&fdset); // обнуляем дескриптор сокетов
for(i=0;i

Рассмотрим синтаксис вызова select() более подробно (взято из cygwin, /usr/include/sys/select.h):

int select __P ((int __n, fd_set *__readfds, fd_set *__writefds, fd_set *__exceptfds, struct timeval *__timeout));

int __n: количество сокетов, для обработки;
fd_set *__readfds: указатель на дескриптор, в который надобно выставить сокеты, готовые к чтению;
fd_set *__writefds: указатель на дескриптор, в который выставляются сокеты, годные к чтению;
fd_set *__exceptfds: указатель на дескриптор, в который выставляются "порванные", ошибочные сокеты;
struct timeval *__timeout: указатель на структуру timeval, где содержится количество времени, на которое select() задерживает возвращение управления вызывающей функции.

Моделирование простого сокетного движка

Теоретически, внутри одного потока выполнения можно обрабатывать сколь угодно большое количество сокетов и реализовывать сколь угодно сложные и разветвлённые алгоритмы. Благо, все сокетные и файловые API, работающие в неблокирующем режиме, возвращают управление очень быстро.

Сокетный движок реализуется в виде цикла, прерываемого либо по нормальному завершению работы программы, либо при возникновении каких-то заранее предусмотренных обстоятельств вроде прерывания выполнения программы пользователем или фатальной системной ошибки.

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

Рассмотрим моделирование сокетного движка под UNIX и под Windows на примере написанной мной программы. Это консольный сканер IP-диапазона на халявные анонимные SOCKSv5-прокси, Tiny Anonymous SOCKSv5 scanner. Пакет исходных текстов программы, в котором Вы найдёте обещанные мною в предыдущей статье подробные комментарии, Вы можете взять
тут (10.5 Кб). Бинарник, скомпилированный под cygwin,
в этом архиве, а бинарник под Windows
тут.

Итак, основной тип данных, с которым работает сокетный движок, это структура

typedef struct <
#ifdef SSCAN_WS2
SOCKET s; // сокет в Windows
#else
int s; // сокет в posix
#endif
u32 job; // джоб, который сокет должен выполнить
u32 timestamp; // отпечаток времени с последней успешно выполненной операции
u32 addr; // адрес, с которым сокет, в данный момент, осуществляет соединение
> s_st;

В теле программы объявляется глобальный указатель

s_st *s; // socket engine structure

Сокетный движок обращается к структурам s_st, разыменовывая данный указатель.

Джобов, обрабатываемых сокетным движком, в этой программе немного:

Читайте также:  Как вытащить контакты с разбитого телефона андроид

1) J_CLOSED, сокет закрыт.
2) J_CONNECTING, сокету дана команда connect().
3) J_CONNECTED, connect() успешно завершён, можно записывать в сокет.
4) J_SENT, сообщение в сокет отправлено.
5) J_IDLE, обработка джоба завершена.

Алгоритм в программе линейный:

while(!enough) <
1) последовательная обработка сокетов, связанных, в настоящий момент времени, с определённым джобом; переключение джоба в случае успешного прохождения всех необходимых операций, в противном случае s[i].job устанавливается в J_CLOSED;
2) проверка таймаутов сокетов, джобы которых находятся в состоянии J_CONNECTING и J_SENT;
3) проверка ошибок в tcp-соединении;
4) запись файла-отпечатка прогресса работы сканера;
5) поиск ответа на вопрос: "а не пора ли закругляться?" &#128578;
6) задержка движка на время, определённое пользователем.
>

При реализации сокетного движка и под UNIX, и под Windows, крайне важно перехватывать и контролировать сигналы SIGINT (UNIX+Windows), SIGPIPE и SIGQUIT в UNIX, SIGBREAK и SIGTERM в Windows.

Я игнорирую сигнал SIGPIPE в UNIX, который отправляется в мой процесс в том случае, если я пытаюсь произвести какую-то, с точки зрения ядра, недопустимую операцию с сокетом. Если этот сигнал не перехватывать и не игнорировать, программа будет завершаться с ошибкой "Broken Pipe". Я лично не вижу никакого смысла обрабатывать эту ситуацию в рамках моей программы.

Сигналы SIGINT и SIGQUIT в UNIX, сигналы SIGBREAK и SIGTERM в Windows отвечают за прерывание потока выполнения программы пользователем. Если программа завершается подобным образом, необходимо "опустить" в блокирующий режим и закрыть все сокеты, открытые программой. Не знаю, как для UNIX (который, вроде бы, закрывает все файловые дескрипторы, открытые потоком выполнения при завершении этого потока), но для Windows это критично!

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

Сокет´ (socket) — название программного интерфейса для обеспечения обмена данными между процессами. Процессы при таком обмене могут исполняться на одном компьютере или на различных компьютерах, связанных между собой сетью. Сокет — абстрактный объект, представляющий конечную точку соединения.

Интерфейс сокетов впервые появился в BSD Unix. Программный интерфейс сокетов описан в стандарте POSIX и в той или иной мере поддерживается всеми современными операционными системами.

5.1. Использование классических блокирующих сокетов

5.1.1. Алгоритм работы сервера и клиента

С установлением соединения. Порядок использования блокирующих сокетов с установлением соединения:

На стороне сервера:

1. Инициализировать библиотеку Winsock ( WSAStartup , только для платформы Windows).

2. Создать сокет ( socket ).

3. Связать сокет ( bind ).

4. Перевести его в слушающий режим ( listen ).

5. Принять запрос на соединение ( accept ).

6. Получить ( recv ) или отправить ( send ) данные.

7. Отключить сокет ( shutdown ).

8. Закрыть сокет ( close , для Windows — closesocket ).

9. Закрыть библиотеку Winsock ( WSACleanup , только для платформы Windows).

На стороне клиента:

1. Инициализировать библиотеку Winsock ( WSAStartup , только для платформы Windows)

2. Создать сокет ( socket ).

3. Послать запрос на соединение ( connect ).

4. Отправить ( send ) или получить ( recv ) данные.

5. Отключить сокет ( shutdown ).

6. Закрыть сокет ( close , для Windows — closesocket ).

7. Закрыть библиотеку Winsock ( WSACleanup , только для платформы Windows).

Без установления соединения. Порядок использования блокирующих сокетов без установления соединения (одинаково для сервера и клиента):

1. Инициализировать библиотеку Winsock ( WSAStartup , только для платформы Windows).

Ю.В. Земсков. Вычислительные сети. Версия 0.20. — Санкт-Петербургский гос. университет гражданской авиации , 2012

2. Создать сокет ( socket ).

3. Связать сокет ( bind ).

4. Получить ( recvfrom ) или отправить ( sendto ) данные.

5. Закрыть сокет ( close , для Windows — closesocket ).

6. Закрыть библиотеку Winsock ( WSACleanup , только для платформы Windows).

5.1.2. Функции API сокетов

Для использования сокетов в системе Windows необходимо подключать заголовочный файл , в Linux — . Если приложение разрабатывается в системе Microsoft Visual C++, то к проекту надо подключить библиотеку ws2_32.lib . Для этого надо выполнить команду главного меню í Project í Settings Link Category: s General

и дописать имя библиотечного файла в поле « Object/library modules » (рис. 5.1 ).

Рис. 5.1. Подключение библиотеки ws2_32.lib в Microsoft Visual C++

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

Создание серверного или клиентского сокета. Дескриптор (описатель) сокета — это переменная типа SOCKET . Сокет создаётся с помощью функции

SOCKET socket(int domain, int type, int protocol);

Возвращаемое значение: дескриптор сокета в случае успеха; −1 (Linux) или INVALID_SOCKET (Windows) — ошибка. Входной параметр domain — константа, указывающая, какой домен нужен сокету. Обычно используются домены AF_INET (т. е. Internet) и AF_LOCAL (применяется для межпроцессного взаимодействия (IPC) на одной и той же машине).

Ю.В. Земсков. Вычислительные сети. Версия 0.20. — Санкт-Петербургский гос. университет гражданской авиации , 2012

С помощью параметра type задаётся тип создаваемого сокета. Чаще встречаются следующие значения:

∙ SOCK_STREAM — обеспечивают надёжный дуплексный протокол на основе установления логического соединения. Если говорится о семействе протоколов TCP/IP, то это TCP;

∙ SOCK_DGRAM — обеспечивают ненадежный сервис доставки датаграмм. В рамках TCP/IP это протокол UDP;

∙ SOCK_RAW — предоставляют доступ к некоторым датаграммам на уровне протокола IP. Они используются в особых случаях, например для просмотра всех ICMP-сообщений.

Параметр protocol показывает, какой протокол следует использовать с данным сокетом. В контексте TCP/IP он обычно неявно определяется типом сокета, поэтому в качестве значения задают 0 . Иногда, например, в случае rawсокетов, имеется несколько возможных протоколов, тогда данный параметр необходимо задавать явно.

Система не освобождает ресурсы, выделенные при вызове socket() , пока не произойдет вызова closesocket() . Каждый вызов socket() должен иметь соответствующий вызов closesocket() во всех возможных путях исполнения. Результатом выполнения системного вызова closesocket() является только обращение к интерфейсу для закрытия сокета, а не закрытие самого сокета. Другими словаи, это всего лишь команда для операционной системы закрыть сокет, а само освобождение ресурсов будет выполнено системой позже, в соответствии с алгоритмами её работы.

Подключение клиента к серверу. Соединение с удалённым сокетом устанавливается с помощью функции

int connect(SOCKET s, struct sockaddr *peer, int peer_len);

Возвращаемое значение: 0 — нормально; −1 (Linux) или не 0 (Windows) — ошибка. Параметр s — дескриптор сокета, который получен в результате вызова функции socket() . Параметр peer указывает на структуру, в которой хранится адрес удаленного хоста и некоторая дополнительная информация. Для домена AF_INET это структура типа sockaddr_in . Параметр peer_len содержит размер структуры (в байтах), на которую указывает peer .

Читайте также:  Как включить кастомное рекавери

После установления соединения можно передавать данные.

Передача и приём данных. В системе Linux передача и приём информации выполняется с помощью функций read() и write() , которым передаётся дескриптор сокета. В системе Windows используются функции recv() (приём) и send() (передача данных):

int recv(SOCKET s, void *buf,

Ю.В. Земсков. Вычислительные сети. Версия 0.20. — Санкт-Петербургский гос. университет гражданской авиации , 2012

size_t len, int flags); int send(SOCKET s, const void *buf,

size_t len, int flags);

Возвращаемое значение: число принятых или переданных байтов в случае успеха; −1 в случае ошибки.

Параметры buf и len — адрес буфера для приёма или отправки информации и его длина. Значение параметра flags зависит от системы, но и Linux,

и Windows поддерживают следующие флаги:

∙ MSG_OOB — следует послать или принять срочные данные;

∙ MSG_PEEK — используется для просмотра поступивших данных без их удаления из приёмного буфера. После возврата из системного вызова данные еще могут быть получены при последующем вызове read()

∙ MSG_DONTROUTE — сообщает ядру, что не надо выполнять обычный алгоритм маршрутизации. Как правило, используется программами маршрутизации или для диагностических целей.

При работе с UDP нужны еще системные вызовы recvfrom() и sendto() . Они очень похожи на recv() и send() , но позволяют при отправке датаграммы задать адрес назначения, а при приёме — получить адрес источника:

int recvfrom(SOCKET s, void *buf, size_t len, int flags, struct sockaddr *from, int *fromlen);

int sendto(SOCKET s, const void *buf, size_t len, int flags, const struct sockaddr *to, int tolen);

Возвращаемое значение: число принятых или переданных байтов в случае успеха; −1 при ошибке.

Первые четыре параметра: s , buf , len и flags — такие же, как в вызовах recv() и send() . Параметр from в вызове recvfrom() указывает на структуру, в которую ядро помещает адрес источника пришедшей датаграммы. Длина этого адреса хранится в целом числе, на которое указывает параметр fromlen . Обратите внимание, что fromlen — это указатель на целое. Аналогично параметр to в вызове sendto() указывает на адрес структуры, содержащей адреса назначения датаграммы, а параметр tolen — длина этого адреса. Заметьте, что to — это целое, а не указатель.

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

Ю.В. Земсков. Вычислительные сети. Версия 0.20. — Санкт-Петербургский гос. университет гражданской авиации , 2012

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

Связывание серверного сокета. На стороне сервера необходимо привязать адрес интерфейса и номер порта к прослушивающему сокету. Для этого предназначен вызов bind() :

int bind(SOCKET s, const struct sockaddr *name, int namelen);

Возвращаемое значение: 0 — нормально, −1 (Linux) или SOCKET_ERROR (Windows) — ошибка. Параметр s — дескриптор слушающего сокета. С помощью параметров name и namelen передаются порт и сетевой интерфейс, которые нужно прослушивать. Обычно в качестве адреса задается константа INADDR_ANY . Это означает, что будет принято соединение, запрашиваемое по любому интерфейсу. Если хосту с несколькими сетевыми адресами нужно принимать соединения только по одному интерфейсу, то следует указать IP-адрес этого интерфейса. Параметр namelen — длина структуры sockaddr_in .

Перевод серверного сокета в слушающим режим. После привязки локального адреса к сокету нужно перевести сокет в режим прослушивания входящих соединений с помощью системного вызова listen() . Его единственная задача — пометить сокет как прослушивающий. Когда хосту поступает запрос на установление соединения, ядро ищет в списке прослушивающих сокетов тот, для которого адрес назначения и номер порта соответствуют указанным в запросе.

int listen(SOCKET s, int backlog);

Возвращаемое значение: 0 — нормально, −1 (Linux) или SOCKET_ERROR (Windows) — ошибка. Параметр s — дескриптор сокета, который нужно перевести в режим прослушивания. Параметр backlog — максимальное число ожидающих, но еще не принятых соединений. Следует отметить, что это не максимальное число одновременных соединений с данным портом, а лишь максимальное число частично установленных соединений, ожидающих в очереди, пока приложение их примет (описание системного вызова accept() дано ниже). Традиционно значение параметра backlog() — не более пяти соединений, но в современных реализациях, которые должны поддерживать приложения с высокой нагрузкой, например, Web-сервера, оно может быть намного больше. Поэтому, чтобы выяснить его истинное значение, необходимо изучить документацию по конкретной системе. Если задать значение, большее максимально допустимого, то система уменьшит его, не сообщив об ошибке.

Слушающие соединения (listening connections) — это пассивные серверные сокеты, ожидающие клиента. Как только клиент делает новый запрос, сервер

Ю.В. Земсков. Вычислительные сети. Версия 0.20. — Санкт-Петербургский гос. университет гражданской авиации , 2012

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

Приём сервером запроса от клиента на установку соединения. И последний вызов, который будет здесь рассмотрен, — это accept() . Он служит для приёма соединения, ожидающего во входной очереди. После того как соединение принято, его можно использовать для передачи данных, например, с помощью вызовов recv() и send() . В случае успеха accept() возвращает дескриптор нового сокета, по которому и будет происходить обмен данными. Номер локального порта для этого сокета такой же, как и для прослушивающего сокета. Адрес интерфейса, на который поступил запрос о соединении, называется локальным. Адрес и номер порта клиента считаются удаленными.

Обратите внимание, что оба сокета имеют один и тот же номер локального порта. Это нормально, поскольку TCP-соединение полностью определяется четырьмя параметрами: локальным адресом, локальным портом, удалённым адресом и удалённым портом. Поскольку удалённые адрес и порт для этих двух сокетов различны, то ядро может отличить их друг от друга.

int accept(SOCKET s, struct sockaddr *addr, int *addrlen);

Функция accept() блокируется до тех пор, пока от клиента не поступит запрос соединения, после чего она возвращает новый сокет. Возвращаемое значение: дескриптор этого нового сокета в случае успеха; −1 (Linux) или INVALID_SOCKET (Windows) — ошибка.

Параметр s — дескриптор слушающего сокета. Вызов accept() возвращает адрес подключившегося к серверу клиента в структуре sockaddr_in , на которую указывает параметр addr . Целому числу, на которое указывает параметр addrlen , ядро присваивает значение, равное длине этой структуры. Часто нет необходимости знать адрес клиента, тогда в качестве addr и addrlen передаётся NULL .

5.1.3. Пример использования блокирующих TCP-сокетов в однопоточном сервере и клиенте

Рассмотрим пример сервера и клиента Winsock (листинги 2 и 3 ), написанные на языке C (консольные приложения). Здесь клиент соединяется с сервером и посылает ему сообщение, состоящее из пяти символов. Сервер принимает сообщение и выводит его на экран. Сервер является однопоточным приложением, т. е. может одновременно работать только с одним клиентом.

Ссылка на основную публикацию
Что значит спящий режим компьютера
В операционной системе Windows есть несколько режимов выключения компьютера – это обыкновенный режим, (который полностью выключает PC), режим гибернации и...
Чем открыть файл с расширением dat
После установки каких-нибудь программ, получения почты при помощи почтовых клиентов, на компьютере создаются .dat файлы. Чаще всего они почти не...
Чем отличается frontend от backend
Переводы , 13 апреля 2017 в 19:58 Мая Устинова Вы наверняка уже слышали эти модные в сфере программирования слова «фронтенд»...
Что значит сторнировать документ
Сто́рно (итал. storno — перевод на другой счёт, отвод; от stornare — поворачивать обратно) — в общем смысле возврат к...
Adblock detector