Сокеты

Сокеты, дескрипторы сокетов, порты и соединения

Технология сокетов лежит в основе современного сетевого программирования. Основные операционные среды (Unix или Windows) базируются в настоящее время на идеологии сокетов (socket). Эта технология была разработана в университете г. Беркли (США) для системы Unix, поэтому их иногда называют сокетами Беркли (berkeley sockets).

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

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

Протоколы TCP/IP ориентируются на то, что приложениями для общения друг с другом используются хорошо известные порты. При этом клиентские приложе­ния предполагают, что соответствующее серверное приложение прослушивает свой хорошо известный порт, ассоциированный с этим приложением. Например, для протокола HTTP(HyperTextTransferProtocol— протокол передачи гипертек­ста) используется TCP-порт с номером 80. По умолчанию веб-браузер будет пы­таться установить соединение с ТСР-портом 80 хоста назначения, если в URL-ад­ресе не указан другой номер порта (например, 8000 или 8080).

Порт(port) идентифицирует точку соединения в локальном стеке (например, порт с номером 80, как правило, используется веб-сервером). Сокет (socket) иденти­фицирует пару, состоящую из IP-адреса и номера порта (например, 192.168.1.20:80 обозначает порт веб-сервера на хосте 192.168.1.20). Пара сокетов (socketpair) иден­тифицирует все четыре компонента (адреса и номера портов отправителя и полу­чателя). Поскольку хорошо известные порты уникальны, иногда они используют­ся для обозначения определенного приложения на любом хосте, на котором может быть запушено это приложение. Однако употребление слова «сокет» подразуме­вает конкретное приложение на конкретном хосте. Соединение, или пара сокетов, означает соединение сокетов между двумя конкретными взаимодействующими системами. Протокол TCPпозволяет одновременно устанавливать несколько соединений для одного и того же локального порта, если у этих соединений от­личаются удаленные IP-адреса или номера портов.

Номера портов разделяются на три категории:

§  Номера от 0 до 1023 зарезервированы для хорошо известных портов. Они ассоциированы со службами на постоянной основе. Например, HTTP-сер­веры всегда принимают запросы к порту 80.

§  Порты с номерами от 1024 до 49 151 являются регистрируемыми. Они ис­пользуются для различных целей.

§  Порты с номерами от 49151 до 65535 представляют собой динамические и частные порты. С ними не должны ассоциироваться службы.

 

Модель клиент-сервер

В модели клиент-сервер имеется программа-сервер, которая обычно прослушивает определенный порт на наличие входящих подключений от удаленных клиентов, и программа-клиент, инициирующая подключение к серверу. При наличии во входной очереди соединений сервера входящего подключения устанавливается сеанс связи и выполняется обмен данными по протоколу TCP, после чего соединение закрывается. Сервер может обрабатывать запросы клиентов либо поочередно, либо параллельно/псевдопараллельно с использованием многопоточных технологий или мультиплексирования ввода вывода. Модель взаимодействия показана на рисунке 1.

Запуск как сервера, так и клиента, всегда начинается с функции socket(), используемой для начальной регистрации сокета определенного типа в системе. После вызова данной функции сервер должен связать сокет с локальным адресом протокола (IP-адресом и портом), вызвав функцию bind(). После чего сервер вызывает  функцию listen() для перевода сокета в состояние LISTEN, тем самым обозначив, что запросы на подключение к нему начинают обрабатываться ядром операционной системы. Далее сервер вызывает в цикле функцию accept() для выбора из очереди установленных соединений следующего соединения. После чего начинается непосредственно объем данными с клиентом, используя функции read()/write() или send()/recv(). После завершения соединения, оно закрывается функцией close().

Теперь опишем последовательность действий для клиента. После создания сокета, клиент вызывает функцию connect() для установления соединения с сервером, после чего происходит обмен данными и закрытие соединения функцией close().

Рис 1. Модель взаимодействия клиент-сервер.

 

Служебные структуры и функции, использующиеся в APIсокетов.

Универсальная структура адреса сокета

structsockaddr

{

    uint8_tsa_len;         /* длина структуры */

    sa_family_t sa_family;  /* AF_XXX */

    char  sa_data[14]; /* данные, специфичные для протокола*/

};

 

Данная структура обычно не используется непосредственно, но в функциях сокетах указатель на структуру адреса сокета, специфичную для протокола, должен приводиться к типу указателя на универсальную структуру sockaddr. Параметр sa_lenимеется не во всех реализациях APIсокетов.

 

Структура адреса сокета IPv4

structsockaddr_in{

    uint8_t     sin_len;    /* длина структуры */

    sa_family_tsin_family; /* AF_INET */

    in_port_t   sin_port;   /* номерпорта

       сетевойпорядокбайтов*/

    struct in_addr sin_addr; /* 32-разрядныйадресIPv4

    сетевой порядок байтов */

    char sin_zero[8];        /* не используется */

};

Элемент sin_lenимеется не во всех реализациях. Элемент структуры  sin_family задает семейство протоколов и всегда равен AF_INETдля IPv4.

Второй и третий элементы – номер порта и IP-адрес в сетевом порядке байтов. Функции преобразования порядка байтов узла в сетевой порядок байтов будут описаны ниже в данном разделе.

 

Структура адреса сокета IPv6

structin6_addr{

      uint8_t s6_addr[16]; /*128-разрядныйадресIPv6 */

};

 

struct sockaddr_in6 {

       uint8_t         sin6_len;         /* длина структуры */

      sa_family_t     sin6_family;      /* AF_INET6 */

      in_port_t       sin6_port;        /* номерпорта

                     сетевой порядок байтов */

      uint32_t        sin6_flowinfo;    /* приоритет и метка потока

               сетевойпорядокбайтов*/

       struct  in6_addr sin6_addr;       /* IPv6-адрес

                                            сетевойпорядокбайтов*/

       uint32_t         sin6_scope_id;   /* Наборинтерфейсов*/

};

Поле длины также имеется не во всех реализациях, если поле имеется, должна быть определена константа SIN6_LEN. Элемент sin6_flowinfoразделен на два поля:  20 младших бит – метка потока, следующие 12 бит зарезервированы.

Элемент sin6_scope_idопределяет контекст, в котором действует конкретный адрес. Чаще всего это бывает индекс интерфейса для локальных адресов.

 

Новая универсальная структура адреса сокета

Новая универсальная структура адреса сокета была определена как часть APIсокетов IPv6 с целью преодоления некоторых недостатков существующей структуры sockaddr. В отличие от структуры sockaddr, новая структура достаточно велика для хранения адреса сокета любого типа, поддерживаемого системой.

 structsockaddr_storage{

       uint8_tss_len;            /* длина структуры */

              sa_family_tss_family;     /* семейство адресов */

 

              /* Зависящие от реализации элементы, обеспечивающие:

а) выравнивание, достаточное для выполнения требований повыравниванию всех типов адресов сокетов, поддерживаемых системой

б) достаточный объем для хранения адреса сокета любого типа, поддерживаемого системой */

};

         Рекомендации по реализации данной структуры содержатся в RFC2553.

 

Функции преобразования порядка байтов

Как известно, порядок байт в целых числах, представление которых занимает более одного байта, может быть для различных компьютеров неодинаковым. Есть вычислительные системы, в которых старший байт числа имеет меньший адрес, чем младший байт (big-endian byte order), а есть вычислительные системы, в которых старший байт числа имеет больший адрес, чем младший байт (little-endian byte order). При передаче целой числовой информации от машины, имеющей один порядок байт, к машине с другим порядком байт мы можем неправильно истолковать принятую информацию. Для того чтобы этого не произошло, было введено понятие сетевого порядка байт, т.е. порядка байт, в котором должна представляться целая числовая информация в процессе передачи ее по сети (на самом деле – это big-endian byte order). Целые числовые данные из представления, принятого на компьютере-отправителе, переводятся пользовательским процессом в сетевой порядок байт, в таком виде путешествуют по сети и переводятся в нужный порядок байт на машине-получателе процессом, которому они предназначены. Для перевода целых чисел из машинного представления в сетевое и обратно используется четыре функции: htons(), htonl(), ntohs(), ntohl().

unsigned long  htonl(unsigned long hostlong);

unsigned short htons(unsigned short hostshort);

unsigned long  ntohl(unsigned long int netlong);

unsigned short ntohs(unsigned short int netshort);

Описание функций:

Функция htonl осуществляет перевод целого 32-битного числа из порядка байт, принятого на компьютере, в сетевой порядок байт.

Функция htons осуществляет перевод целого 16-битного числа из порядка байт, принятого на компьютере, в сетевой порядок байт.

Функция ntohl осуществляет перевод целого 32-битного числа из сетевого порядка байт в порядок байт, принятый на компьютере.

Функция ntohs осуществляет перевод целого 16-битного числа из сетевого порядка байт в порядок байт, принятый на компьютере.

 

ФункциипреобразованияIP-адресовIPv4

char *inet_ntoa(struct in_addr *addrptr);

int inet_aton(const char *strptr, struct in_addr *addrptr);

unsigned long inet_addr(const char *srcptr);

Функция inet_atonпереводит символьный IP-адрес, расположенный по указателю strptr, в числовое представление в сетевом порядке байт и заносит его в структуру, расположенную по адресу addrptr. Функция возвращает значение 1, если в строке записан правильный IP-адрес, и значение 0 в противном случае.

Функция inet_ntoaприменяется для обратного преобразования. Числовое представление адреса в сетевом порядке байт должно быть занесено в структуру типа structin_addr, адрес которой addrptrпередается функции как аргумент. Функция возвращает указатель на строку, содержащую символьное представление адреса. Эта строка располагается в статическом буфере, при последующих вызовах ее новое содержимое заменяет старое содержимое.

Функция inet_addrпереводит символьный IP-адрес, расположенный по указателю strptr, в числовое представление в сетевом порядке байт и возвращает значение 32-разрядного целого числа, соответствующего данному адресу.

 

Функции преобразования IP-адресов inet_ptonи inet_ntop

Эти функции появились с IPv6 и работают как с адресами IPv4, так и с адресами IPv6. Символы pи nобозначают, соответственно, формат представления и числовой формат.

int inet_pton(int af, const char *src, void *dst);

char *inet_ntop(int af, const void *src, char *dst, size_t size);

Значением аргумента afможет быть либо AF_INETдля IPv4, либо AF_INET6 для IPv6.

Первая функция преобразует строку, на которую указывает src, сохраняя двоичный результат по адресу dst.

Вторая функция выполняет обратное преобразование из формата представления (src) в строковый формат (dst). Аргумент size– размер принимающей строки для предотвращения переполнения буфера. Если размер слишком мал для хранения результирующего формата адреса, функция возвращает нулевой указатель.

     

Создание сокета. Функция socket()

Чтобы обеспечить сетевой ввод-вывод, процесс должен начать работу с вызова функции socket, задав тип желаемого протокола.

#include <sys/socket.h>

int socket(int family, int type, int protocol);

Параметр familyиспользуется для выбора семейства протоколов. Разные системы имеют различный набор семейств протоколов (например, в Windows, начиная с 2003 Server, имеется семейство протоколов Bluetooth, задающееся константой AF_BTM), но обычно во всех ОС поддерживаются следующие типы:

AF_INET– семейство протоколов IPv4;

AF_INET6 – семейство протоколов IPv6;

Параметр typeзадает тип сокета (способ обмена информацией), обычно используются следующие значения:

SOCK_STREAM – для связи с установлением соединения (например, по протоколу TCP).

SOCK_DGRAM – для обмена без установления соединения (например, по протоколу UDP).

Параметр protocolспецифицирует конкретный транспортный протокол для выбранного семейства протоколов и способа обмена информацией. Он имеет значение только в том случае, когда таких протоколов существует несколько. Обычно семейство протоколов и тип обмена информацией определяют протокол однозначно, поэтому чаще всего этот параметр имеет значение 0. Возможные варианты данного параметра:

IPPROTO_TCP– транспортный протокол TCP;

IPPROTO_UDP– транспортный протокол UDP;

В различных ОС могут встречаться дополнительные значения данного параметра.

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

 

Привязка сокета к адресу. Функция bind()

Функция bindсвязывает сокет с локальным адресом протокола. В случае протоколов Интернета адрес протокола – это комбинация адреса IPv4 или IPv6 с 16-разрядным номером порта TCPили UDP.

int bind(int sockd, struct sockaddr *myaddr, int addrlen);

Параметр sockfd– дескриптор сокета.

Параметр myaddr– указатель на структуру адреса сокета. Если IP-адрес в структуре сокета представляет собой универсальный адрес (например, 0.0.0.0 для IPv4), ядро системы самостоятельно выберет IP-адрес. Для IPv4 универсальный адрес задается константой INADDR_ANY. Для IPv6 используется внешняя константа in6addr_any.

Пример задания универсального адреса для IPv4:

struct sockaddr_in servaddr;

servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

Пример задания универсального адреса для IPv6:

struct sockaddr_in6 servaddr;

servaddr.sin6_addr = in6addr_any;

При задании номера порта в 0, ОС также самостоятельно выбирает порт для сервера, но обычно эта возможность никогда не используется, т.к. практически всегда используются заранее известные номера портов.

Третий аргумент addrlen– размер структуры адреса сокета в байтах.

Функция возвращает 0 в случае успешного выполнения, -1 при ошибке. Общей ошибкой выполнения функции bindявляется EADDRINUSE, указывающая на то, что адрес уже используется.

 

Функция listen

Функция listenвызывается только сервером и преобразует неприсоединенный сокет в пассивный сокет, запросы на подключение к которому начинают приниматься системой. В терминах диаграммы перехода между состояниями TCPвызов функции listenпереводит сокет из состояния CLOSEDв состояние LISTEN.

int listen(int sockfd, int backlog);

Функция обычно вызывается после функций socketи bindи перед вызовом функции accept. Аргумент backlogзадает максимальную длину очереди входящих соединений. В качестве этого аргумента можно передать константу SOMAXCONN для обозначения максимального количества соединений, поддерживаемых системой.

Этот параметр на разных операционных системах и даже на разных версиях одной и той же системы может иметь различный смысл. Где-то это суммарная длина обеих очередей, где-то он относится к очереди не полностью установленных соединений (например, Linux до версии ядра 2.2) где-то – к очереди полностью установленных соединений (например, Linux, начиная с версии ядра 2.2), где-то – вообще игнорируется. Пояснение принципа очередей полностью и не полностью установленных соединений – рисунок 2.

В случае успешного выполнения функция возвращает 0, в случае неудачи: -1.

Рис. 2.  Установка TCP-соединения.

 

Согласие на установку соединения. Функция accept

Функция acceptвызывается сервером для возвращения следующего установленного соединения из начала очереди полностью установленных соединений. Если эта очередь пуста, процесс переходит в состояние ожидания, иначе возвращается дескриптор для первого присоединенного сокета в этой очереди, одновременно удаляя его из очереди. Системный вызов также позволяет серверу узнать полный адрес клиента, установившего соединение

int accept(int sockd, struct sockaddr *cliaddr,

            socklen_t*addrlen);

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

Параметр cliaddrслужит для получения адреса клиента, установившего логическое соединение, и должен содержать указатель на структуру, в которую будет занесен этот адрес.

Параметр addrlenсодержит указатель на целую переменную, которая после возвращения из вызова будет содержать фактическую длину адреса клиента. Перед вызовом эта переменная должна содержать максимально допустимое значение такой длины. Если параметр cliaddrимеет значение NULL, то и параметр addrlenможет иметь значение NULL.

При успешном выполнении функция возвращает дескриптор сокета для соединения с клиентом, при ошибке возвращается -1.

 

Подключение к серверу. Функция connect

Функция connectиспользуется клиентом TCPдля установления соединения с сервером TCP.

int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen); 

Аргумент sockfd– дескриптор сокета, возвращенный функцией socket. Второй и третий аргументы – указатель на структуру адреса сокета и ее размер. Структура адреса сокета содержит IP-адрес и номер порта сервера.

В случае успеха функция возвращает 0, в случае ошибки возвращается -1.

 

Обмен данными. Функции sendи recv.

Обычно при использовании потоковых сокетов для обмена данными используются функции sendи recv.

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

int recv(int socket, void *buf, size_t len, int flags);

Параметр msg– указатель на передаваемое сообщение.

Параметр len– длина сообщения.

Параметр  buf– указатель на буфер, в который записывается принимаемое сообщение.

Параметр flagsпереопределяет параметры сообщения по умолчанию. Обычно в большинстве систем реализованы как минимум следующие флаги:

MSG_OOB - Посылает внепотоковые данные, если сокет поддерживает эту возможность (например, сокеты типа SOCK_STREAM); нижележащий протокол также должен поддерживать внепотоковые данные.

MSG_DONTROUTE - Не использовать маршрутизацию при отправке пакета, а посылать его только на хосты в локальной сети. Обычно это используется в диагностических программах и программах маршрутизации. Этот флаг определен только для маршрутизируемых семейств протоколов; пакетные сокеты не используют маршрутизацию.

MSG_PEEK– данные копируются в пользовательский буфер, но не удаляются из буфера приема.

Также могут присутствовать и другие флаги, в зависимости от операционной системы.

Функции возвращают количество переданных/полученных байт. Если соединение закрыто с использованием функций закрытия сокета, функции возвращаю 0. В случае ошибки возвращается -1.

В UNIX-подобных ОС для операций передачи/приема данных также могут использоваться функции файлового ввода-вывода read() и write(), т.к. в данных ОС дескрипторы сокетов являются файловыми дескрипторами.

 

Закрытие сокета. Функции closeи closesocket

В UNIX-подобных система для закрытия сокета и завершения TCP-соединения применяется обычная функция закрытия файлового дескриптора close:

int close(int sockfd);

В Windowsдля этого используется функция closesocket:

int closesocket(int sd);

Функция закрытия сокета помечает дескриптор сокета как закрытый, и управление немедленно возвращается процессу. Но TCPможет попытаться отправить сокету данные, которые уже установлены в очередь, после их отправки ядро осуществит нормальную последовательность завершения TCP-соединения.

 

Примеры простых программ TCPэхо-клиента и эхо-сервера, работающих в Windowsи в UNIX-подобных системах

Ниже приведены примеры простых программ эхо-клиента и эхо-сервера, работающих в UNIX-подобных и Windows-подобных ОС. Для простоты в программах не проверяются возвращаемые значения, но в реальных программах проверки должны присутствовать.

TCPecho-сервер

#include<stdio.h>

#ifdef_WIN32

#include<winsock2.h>

#else

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#endif

#definePORT 10000

intmain(int argc, char* argv[])

{    

#ifdef_WIN32

      WSADATA wsadata;

#endif

      int listensock, clnsock;

      struct sockaddr_in s_in = {0};

      struct sockaddr_in client_addr;

#ifdef_WIN32

      WSAStartup(MAKEWORD(1,1), &wsadata); //инициализациябиблиотекиWinSock

#endif

      listensock = socket(AF_INET, SOCK_STREAM, 0);

      s_in.sin_family = AF_INET;

      s_in.sin_port = htons(PORT);

      s_in.sin_addr.s_addr = htonl(INADDR_ANY);

      bind(listensock, (struct sockaddr*)&s_in, sizeof(s_in));

           listen(listensock, SOMAXCONN);

           for(;;)

      {

            char sym;

            size_t cli_len = sizeof(client_addr);

            clnsock = accept(listensock, (struct sockaddr*)&client_addr,

&cli_len);

            printf("client %s:%i connected\n",            

                  inet_ntoa(client_addr.sin_addr), htons(client_addr.sin_port));

            while(recv(clnsock, &sym, sizeof(char), 0) > 0)

            {

                  putchar(sym);

            }

      }

      return 0;

}

 

TCPecho-клиент

#include<stdio.h>

#ifdef_WIN32

#include<winsock2.h>

#else

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#define closesocket(s) close(s)

#endif

#definePORT 10000

intmain(int argc, char* argv[])

{    

#ifdef_WIN32

      WSADATA wsadata;

#endif

      int sock;

      char sym;

      struct sockaddr_in srv_addr = {0};

     #ifdef_WIN32

      WSAStartup(MAKEWORD(1,1), &wsadata);

#endif

      sock = socket(AF_INET, SOCK_STREAM, 0);

      srv_addr.sin_family = AF_INET;

      srv_addr.sin_port = htons(PORT);

      srv_addr.sin_addr.s_addr = inet_addr(argv[1]);

      connect(sock, (struct sockaddr*)&srv_addr, sizeof(srv_addr));

      do

      {

            sym = getc(stdin);

       

      } while(send(sock, &sym, sizeof(char), 0) > 0);

      closesocket(sock);

      return 0;

}

Датаграммные сокеты

Сокеты типа SOCK_DGRAM– датаграммные сокеты, использующие ненадежную передачу. Датаграммные сокеты используют протокол UDP, не требующий установления соединения, не гарантирующий надежной передачи данных, также данные могут поступать не в том порядке, в котором они были отправлены. Все вопросы обеспечения надежности передачи и распознавания правильной последовательности передаваемых данных в данном случае должны обеспечиваться программистом, использующим такие сокеты.

На рис. 3 показана схема соединения с использованием датаграммных сокетов. Можно заметить, что в данном случае не требуется установления соединения, поэтому сервер не вызывает функции acceptи listen, а вместо функций sendи recvприменяются функции sendtoи recvfrom.

Рис 3. Взаимодействие с использованием UDP.

Функции sendto и recvfrom

int sendto(int sockfd, char *buff, int nbytes, int flags,

struct sockaddr *to, int addrlen);

int recvfrom(int sockfd, char *buff, int nbytes, int flags,

struct sockaddr *from, int *addrlen);

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

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

Параметр buffпредставляет собой адрес области памяти, начиная с которого будет браться информация для передачи или размещаться принятая информация.

Параметр nbytesдля системного вызова sendtoопределяет количество байт, которое должно быть передано, начиная с адреса памяти buff. Параметр nbytesдля системного вызова recvfromопределяет максимальное количество байт, которое может быть размещено в приемном буфере, начиная с адреса buff.

Параметр toдля системного вызова sendtoопределяет ссылку на структуру, содержащую адрес сокета получателя информации, которая должна быть заполнена перед вызовом. Если параметр fromдля системного вызова recvfromне равен NULL, то для случая установления связи через пакеты данных он определяет ссылку на структуру, в которую будет занесен адрес сокета отправителя информации после завершения вызова. В этом случае перед вызовом эту структуру необходимо обнулить.

Параметр addrlenдля системного вызова sendtoдолжен содержать фактическую длину структуры, адрес которой передается в качестве параметра to. Для системного вызова recvfromпараметр addrlenявляется ссылкой на переменную, в которую будет занесена фактическая длина структуры адреса сокета отправителя, если это определено параметром from. Заметим, что перед вызовом этот параметр должен указывать на переменную, содержащую максимально допустимое значение такой длины. Если параметр fromимеет значение NULL, то и параметр addrlenможет иметь значение NULL.

Параметр flagsопределяет режимы использования системных вызовов (перечислены выше в описании функций sendи recv).

В случае успешного завершения функции возвращают количество реально отосланных или принятых байт. При возникновении какой-либо ошибки возвращается -1.

Вложения:
Скачать этот файл (sockets.ppt)Сокет[Презентация]297 kB