Winsock что это такое
WinSock или Windows socket — это интерфейс программного программирования (API) созданный для реализации приложений в сети на основе протокола TCP/IP. То есть это просто группа функций . Для работы используется WSOCK32.DLL.
Программа WSOCK32.DLL TCP/IP
Практически это интерфейс к протоколу TCP/IP.
При взаимодействии клиент — сервер в сети каждого участника взаимодействия можно рассматривать как конечную точку. Сокет это вроде как и есть эта конечная точка. В приложении Вы определяете сокет. И хоть программирование сокетов в UNIX и Windows похоже, мы с Вами будем рассматривать именно Windows socket.
Незнаю интересно это или нет, но Windows socket разрабатывался на основе интерфейса Беркли для UNIX, но к ним добавлены функции поддержки событий Windows.
- WinSock 1.1 — поддержка только TCP/IP
- WinSock 2.0 — Поддерка дополнительного программного обеспечения
- Функции Беркли
- Информационные функции (получение информации о наименовании доменов, службах, протоколах Internet)
- Расширения Windows для функций Беркли
Все функции могут быть блокирующие и неблокирующие. Обычно блокирующие это функции Беркли. То есть при работе такой функции нельзя выполнять другие функции WinSock.
Давайте расмотрим как производится подключение к серверу. Сначала программа подключается к адресу IP с созданием сокета. Программа будет ждать подключения. Для подключения программа клиент тоже создает сокет и пытается подключиться к сокету сервера. Но сервер не спит . Только он увидит попытку подключения он создаст новый сокет. И этот новый сокет будет использоваться для взаимодействия с клиентом. А тот, к которому была попытка подключения будет ждать следующего. На этой основе производится взаимодействие сервера и многими программами.
- TCP (transport control protocol) — надежное соединение
- UDP (user datagram protocol) — ненадежное соединения
Начало работы с Winsock
Этот раздел представляет собой пошаговое руководство по началу работы с программированием сокетов Windows. Он предназначен для понимания основных функций Winsock и структур данных, а также того, как они работают вместе.
Клиентское и серверное приложения, которые мы используем в этом разделе для иллюстрации, являются очень простым клиентом и сервером. Более сложные примеры кода включены в примеры, включенные в пакет sdk для Microsoft Windows.
Первые несколько шагов одинаковы как для клиентских, так и для серверных приложений.
- Сведения о серверах и клиентах
- Создание базового приложения Winsock
- Инициализация Winsock
В следующих статьях описаны оставшиеся шаги по созданию клиентского приложения Winsock.
- Создание сокета для клиента
- Подключение к сокету
- Отправка и получение данных на клиенте
- Отключение клиента
В следующих статьях описаны оставшиеся шаги по созданию серверного приложения Winsock.
- Создание сокета для сервера
- Привязка сокета
- Прослушивание сокета
- Принятие подключения
- Получение и отправка данных на сервере
- Отключение сервера
Полный исходный код для этих базовых примеров.
- Запуск примера кода клиента и сервера Winsock
- Завершение кода клиента Winsock
- Полный код сервера Winsock
Расширенные примеры приложений Winsock
На сайте GitHub доступно несколько более сложных примеров приложений для клиента и сервера Winsock . Они перечислены здесь в порядке от более высокой до более низкой производительности и находятся в следующих каталогах:
- iocp Эта папка содержит три примера программ, использующих порты завершения ввода-вывода. Программы включают: сервер Winsock , iocpserver который использует функцию WSAAccept ; сервер Winsock , iocpserverex который использует функцию AcceptEx ; и простой многопоточный клиент Winsock , iocpclient используемый для тестирования любого из этих серверов. Серверные программы поддерживают подключение нескольких клиентов по протоколу TCP/IP и отправку буферов данных произвольного размера, которые сервер затем передает клиенту. Для удобства была разработана простая клиентская программа для iocpclient подключения и непрерывной отправки данных на сервер, чтобы подчеркнуть их с помощью нескольких потоков. Серверы Winsock, использующие порты завершения ввода-вывода, обеспечивают максимальную производительность.
- Перекрываются Эта папка содержит пример серверной программы, которая использует перекрывающиеся ввод-вывод. Пример программы использует функцию AcceptEx и перекрывающиеся операции ввода-вывода для эффективной обработки нескольких асинхронных запросов на подключение от клиентов. Сервер использует функцию AcceptEx для мультиплексирования различных клиентских подключений в однопотоковом приложении Win32. Использование перекрывающихся операций ввода-вывода обеспечивает большую масштабируемость.
- WSAPoll Эта папка содержит базовый пример программы, демонстрирующий использование функции WSAPoll . Объединенная клиентская и серверная программы не блокируются и используют функцию WSAPoll , чтобы определить, когда можно отправлять или получать без блокировки. Этот пример предназначен для иллюстрации и не является высокопроизводительный сервер.
- простой Эта папка содержит три основных примера программ, демонстрирующих использование нескольких потоков сервером. К программам относятся: простой сервер TCP/UDP; simples сервер только TCP, simples_ioctl , который использует функцию select в консольном приложении Win32 для поддержки нескольких клиентских запросов; и клиентская программа simplec TCP/UDP , , для тестирования серверов. Серверы демонстрируют использование нескольких потоков для обработки нескольких клиентских запросов. Этот метод имеет проблемы с масштабируемостью, так как для каждого запроса клиента создается отдельный поток.
- accept Эта папка содержит базовый пример серверной и клиентской программы. Сервер демонстрирует использование неблокирующего принятия с помощью функции select или асинхронного принятия с помощью функции WSAsyncSelect . Этот пример предназначен для иллюстрации и не является высокопроизводительный сервер.
Обратная связь
Были ли сведения на этой странице полезными?
Winsock что это такое
В последней статье мы написали простейшее Winsock-приложение, получающее данные с удалённого Web-сервера. Исходный код программы прост и прозрачен, и с виду никаких «подводных» камней в этой программе нет. Однако, это далеко не так. Ведь в предыдущих статьях нашей целью было понять только принцип работы и организации простейшего Winsock приложения, работающего с TCP сокетами.
Теперь же рассмотрим пройденный материал более подробно, так как мы уже немного «подросли» и можем перейти к решению более серьёзных проблем. Итак, вот некоторые особенности пройденного материала, на которые я не делал упор в начале. Мне даже приходили письма от некоторых читателей, которые указывали мне на эти «упущения». Спасибо за ваши отзывы! Значит, материал читают!
1. В примере программы из статьи 2, есть такой фрагмент кода:
// .
// Ждём ответа
int len = recv (s, (char *) &buff, MAX_PACKET_SIZE, 0);
// .
На первый взгляд вроде бы всё нормально. Но это не так! В вызове функции recv присутствует параметр MAX_PACKET_SIZE, который определяет длину буфера приёма данных. То есть, Winsock протоколы (TCP/UDP) могут «отправить» пакет размером и 65535 байт. Однако, реальный размер IP пакета, может быть меньше передаваемых данных для конкретного типа сети ( величина MTU — размер наибольшего допустимого кадра в локальной сети или глобальном канале) и поэтому данные фрагментируются (кто хочет — смотрит описание TCP/IP протокола, мы не будем пока углубляться в описание «железного» уровня сети). При этом, фрагментированные данные могут идти с задержками. Какое отношение имеет данная особенность к нашей программе? Всё очень просто. Если мы запросим у удалённого Web-сервера не такой маленький кусочек HTML-кода, как в нашем примере, а немного побольше, то мы получим только первую порцию данных от сервера. Остальная часть данных будет, скорее всего, утеряна. Выход из данного положения очень прост. Он основан на знании принципа работы HTTP и TCP/IP протоколов, плюс некоторых особенностей Winsock. В нашем случае Web-сервер, передав последнюю порцию данных, закроет соединение. А функция recv в таком случае, после получения последнего фрагмента данных, возвращает ноль. Вывод напрашивается сам: мы должны читать входящие данные до тех пор, пока функция recv не вернет ноль.
Вот новый вариант кода:
При такой организации считывания данных, потерь не будет.
Полный исходный код программы можно взять здесь: ws3_1.zip
2. При рассмотрении функций send/recv мы упустили интерпретацию поля flags, заполняя его нулём, что значило отсутствие каких-либо флагов при вызове send/recv. Рассмотрим, какие значения может принимать поле flags.
Функция send.
MSG_DONTROUTE — указывает на то, что в отправляемое сообщение, не включатся информация о маршрутизации. Однако Winsock service provider может игнорировать этот флаг при доставке сообщения. Используется для отладки. Адрес назначения — локальный. То есть данные могут быть доставлены только на машины, соединенные напрямую.
MSG_OOB — Сообщение является OOB данными. (Out Of Band) То есть, такое сообщение передаётся вне потока. Это значит, что при отправке сообщения, транспортный протокол не ждёт полного заполнения буфера, а отсылает сообщение немедленно. Данный флаг можно использовать при передаче приоритетных данных. При использовании MSG_OOB, Winsock-приложения поддерживающие связь, должны заранее «договориться» об использовании этого флага.
Функция recv.
MSG_PEEK — Данные копируются в принимающий буфер, но из очереди сообщений не изымаются. Функция возвращает количество принятых на данный момент байт данных.
MSG_OOB — Сообщение является OOB данными. (Out Of Band) То есть, такое сообщение передаётся вне потока. Это значит, что при отправке такого сообщения, транспортный протокол не ждёт полного заполнения TCP-буфера, а отсылает сообщение немедленно. Данный флаг можно использовать при передаче приоритетных данных. При использовании MSG_OOB, Winsock-приложения поддерживающие связь, должны заранее «договориться» о использовании этого флага.
Теперь мы знаем, какие значения может принимать переменная flags. Как, зачем, и главное нужно ли использовать эти флаги, мы рассмотрим в следующих статьях. В процессе написания новых программ.Пока все. Не будем забивать себе головы лишней для нас информацией.
Блокировка сокета. Решение проблемы.
Если вы выполняли программу из статьи 2 пошагово, то могли заметить, что некоторые Winsock-функции ожидают завершения выполняемых ими операций. Особенно надолго «подвисает» функция recv. Другими словами, выход из функции не происходит до момента завершения текущей операции. Эта особенность не очень хорошо подходит для программ, которые кроме получения/отправки данных должны выполнять еще множество других действий (отслеживание состояния системы меню, вывод информации, опрос других устройств ввода/вывода) Избежать этого можно многими способами. Можно обойтись средствами мультизадачности, и процедуру обмена данными «повесить» на отдельную ветвь. А можно решить проблему средствами Winsock. В любом случае, выбор конкретного метода остаётся за программистом. Наша же задача, разобраться с механизмом блокировки TCP-сокетов в Winsock. Итак, я хочу предложить вам два метода устранения проблемы блокировки:
1. Функция ioctlsocket.
Функция ioctlsocket позволяет менять/получать режим ввода/вывода конкретного сокета.
int ioctlsocket(SOCKET s, // Сокет [in]
long cmd, // Комманда [in]
u_long FAR *argp // Параметр/значение [in/out]
);
Для перевода сокета в не блокируемое состояние (nonblocking mode) используется команда FIONBIO. Argp должно указывать на ненулевое значение.
Кроме команды FIONBIO существуют команды FIONREAD и SIOCATMARK. Если коротко, то FIONREAD позволяет получить количество байт информации, поступившей в буфер на данный момент операции чтения, а SIOCATMARK — используется при работе с OOB данными. На данный момент нас интересует только команда FIONBIO. Остальные команды будем рассматривать более подробно по мере надобности в следующих статьях.
Итак, вернёмся к нашему примеру. После вызова ioctlsocket сокет s стал не блокируемым, то есть, Winsock-функции для этого сокета не дожидаются окончания операций ввода/вывода, что в свою очередь не вызывает нежелательных пауз в работе программы. Однако, не всё так просто. Например, возврат из функции recv может произойти до момента получения данных. Как определить, что текущая операция ввода/вывода полностью завершена? Способ есть. Имя ему — WSAEWOULDBLOCK. Что это такое? WSAEWOULDBLOCK — это код ошибки, которую возвращают Winsock-функции для nonblocked сокета, если текущая операция не завершена. То есть, если функция revc вернула этот код ошибки, значит, данные еще не готовы для чтения, и операцию придется повторить позже. В таком случае, ваша программа может выполнять другие действия, попутно проверяя, не завершена ли текущая операция ввода/вывода.
Полный исходный код программы можно взять здесь: ws3_2.zip
2. Функция select
Функция slect позволяет определить текущее состояние одного или более сокетов. То есть, из какого-то входящего множества сокетов, она формирует выходящее множество сокетов, готовых к операциям чтения/записи/.
int select(int nfds, // Не используется (оставлен для совместимости)
fd_set FAR *readfds, // множество сокетов, проверяемых на готовность к чтению
fd_set FAR *writefds, // множество сокетов, проверяемых на готовность к отсылке
fd_set FAR *exceptfds, // множество сокетов, проверяемых на ошибку/OOB данные
const struct timeval FAR *timeout // Таймаут проверки
);
Каждый из параметров readfds, writefds, exceptfds, timeout есть необязательным, и может быть проигнорирован (установлен в NULL). В случае readfds/writefds/exceptfds == NULL проверка на опущенные типы сокетов просто не будет производиться. В случае timeout ==NULL, функция select вызовет блокировку (до первого готового к вводу/выводу сокета). Функция возвращает общее количество сокетов (во всех заданных множествах readfds/writefds/exceptfds), готовых к операциям ввода/вывода.
Параметры readfds/writefds/exceptfds — указатели на тип fd_set (представляющий собою множество сокетов). Для работы с этим типом данных объявлены такие макросы:
FD_CLR (s, *set) -Удаляет дескриптор s из set.
FD_ISSET(s, *set) — Возвращает ненулевое значение, если s присутствует в set. Иначе, возвращает ноль.
FD_SET(s, *set) — добавляет s к set.
FD_ZERO(*set) — Очищает множество set
Параметр timeout — указатель на структуру timeval, позволяет задать таймаут, в течении которого сокеты будут проверяться на готовность.
struct timeval <
long tv_sec; // секунды
long tv_usec; // микросекунды
>;
С помощью функции select и этого набора макросов, мы можем проверять конечное множество сокетов на готовность к считыванию/отсылке данных, выполнения connect, на предмет входящих соединений, наличия OOB сообщений и т.п. На данном этапе нас интересует проверка сокета на возможность считывания данных, поэтому пока ограничимся самым простым вызовом select. Для этого нам необходимо поместить наш сокет в множество на которое будет указывать readfds (в примере это read_s), задать timeout и выполнить select.
fd_set read_s; // Множество
timeval time_out; // Таймаут
FD_ZERO (&read_s); // Обнуляем мнодество
FD_SET (s, &read_s); // Заносим в него наш сокет
time_out.tv_sec = 0;time_out.tv_usec = 500000; //Таймаут 0.5 секунды.
if (SOCKET_ERROR == (res = select (0, &read_s, NULL, NULL, &time_out) ) ) return -1;
if ((res!=0) && (FD_ISSET (s, &read_s)) ) // Использую FD_ISSET только для примера! 🙂
// Получаю данные
>
// .
Полный исходный код программы можно взять здесь: ws3_3.zip
Итак, мы рассмотрели некоторые нюансы работы с TCP-сокетами и простейшие способы решения проблемы блокировки. Конечно, каждый пример носит условный характер, но ведь это же всё-таки примеры!
В следующих статьях мы приступим к написанию приложения-сервера, принимающего входящие соединения, и продолжим изучать Winsock-функции, которые пригодятся нам при написании более сложных сетевых приложений.
- Приём входящих соединений
- Учим новые функции.
Winsock что это такое
И так, что же такое Winsock и с чем его едят? Если сказать в «двух словах», то Winsock это интерфейс, который упрощает разработку сетевых приложений под Windows. Всё что нам нужно знать, это то что Winsock представляет собою интерфейс между приложением и транспортным протоколом, выполняющим передачу данных.
Не будем вдаваться в детали внутренней архитектуры, ведь нас интересует не то, как он устроен внутри, а то, как использовать функции, предоставляемые Winsock пользователю для работы. Наша задача — на конкретных примерах разобраться с механизмом действия WinsockAPI. «Для чего это можно использовать? Ведь существуют библиотеки, упрощающие работу с сетями и имеющие простой интерфейс?» — спросите вы. Я отчасти согласен с этим утверждением, но по-моему полностью универсальных библиотек, ориентированных под все задачи существовать не может. Да и к тому же, намного приятней разобраться во всём самому, не чувствуя неловкости перед «чёрным ящиком» принципа работы которого не понимаешь, а лишь используешь как инструмент 🙂 Весь материал рассчитан на новичков. Я думаю с его освоением не будет никаких проблем. Если вопросы всё-таки возникнут, пишите на pepper@anotherd.com. Отвечу всем. Для иллюстрации примеров будем использовать фрагменты кода Microsoft VC++. Итак, приступим!
Winsock — с чего начать?
Итак, первый вопрос — если есть Winsock, то как его использовать? На деле всё не так уж и сложно. Этап первый — подключение библиотек и заголовков.
#include «winsock.h» или #include «winsock2.h» — в зависимости от того, какую версию Winsock вы будете использовать
Так же в проект должны быть включены все соответствующие lib-файлы (Ws2_32.lib или Wsock32.lib)
Шаг 2 — инициализация.
Теперь мы можем спокойно использовать функции WinsockAPI. (полный список функций можно найти в соответствующих разделах MSDN).
Для инициализации Winsock вызываем функцию WSAStartup
int WSAStartup( WORD wVersionRequested, (in) LPWSADATA lpWSAData (out) );
Параметр WORD wVersionRequested — младший байт — версия, старший байт — под.версия, интерфейса Winsock. Возможные версии — 1.0, 1.1, 2.0, 2.2. Для «сборки» этого параметра используем макрос MAKEWORD. Например: MAKEWORD (1, 1) — версия 1.1. Более поздние версии отличаются наличием новых функций и механизмов расширений. Параметр lpWSAData — указатель на структуру WSADATA. При возврате из функции данная структура содержит информацию о проинициализированной нами версии WinsockAPI. В принципе, ёё можно игнорировать, но если кому-то будет интересно что же там внутри — не поленитесь, откройте документацию 😉
Так это выглядит на практике:
При ошибке функция возвращает SOCKET_ERROR. В таком случае можно получить расширенную информацию об ошибке используя вызов WSAGetLastError(). Данная функция возвращает код ошибки (тип int)
Шаг 3 — создание сокета.
Итак, мы можем приступить к следующему этапу — создания основного средства коммуникации в Winsock- сокета (socket). С точки зрения WinsockAPI сокет — это дескриптор, который может получать или отправлять данные. На практике всё выглядит так: мы создаём сокет с определёнными свойствами и используем его для подключения, приёма/передачи данных и т.п. А теперь сделаем небольшое отступление. Итак, создавая сокет мы должны указать его параметры: сокет использует TCP/IP протокол или IPX (если TCP/IP, то какой тип и т.д.). Так как следующие разделы данной статьи будут ориентированы на TCP/IP протокол, то остановимся на особенностях сокетов использующих этот протокол. Мы можем создать два основных типа сокетов работающих по TCP/IP протоколу — SOCK_STREAM и SOCK_DGRAM (RAW socket пока оставим в покое 🙂 ). Разница в том, что для первого типа сокетов (их еще называют TCP или connection-based socket), для отправки данных сокет должен постоянно поддерживать соединение с адресатом, при этом доставка пакета адресату гарантирована. Во втором случае наличие постоянного соединения не нужно, но информацию о том, дошел ли пакет, или нет — получить невозможно (так называемые UDP или connectionless sockets). И первый и второй типы сокетов имеют своё практическое применение. Начнём наше знакомство с сокетами с TCP (connection-based) сокетов.
Для начала объявим его:
Создать сокет можно с помощью функции socket
SOCKET socket ( int af (in), // протокол (TCP/IP, IPX. )
int type (in), // тип сокета (SOCK_STREAM/SOCK_DGRAM)
int protocol (in) // для Windows приложений может быть 0
);
if (INVALID_SOCKET == (s = socket (AF_INET, SOCK_STREAM, 0) ) )
// Error.
error = WSAGetLastError();
// .
>
При ошибке функция возвращает INVALID_SOCKET. В таком случае можно получить расширенную информацию об ошибке используя вызов WSAGetLastError().
Шаг 4 -устанавливаем соединение.
В предыдущем примере мы создали сокет. Что же теперь с ним делать? 🙂 Теперь мы можем использовать этот сокет для обмена данными с другими клиентами winsock-клиентами и не только. Для того, что бы установить соединение с другой машиной необходимо знать ее IP адрес и порт. Удалённая машина должна «слушать» этот порт на предмет входящих соединений (т.е. она выступает в качестве сервера). В таком случае наше приложение это клиент.
Для установки соединения используем функцию connect.
int connect(SOCKET s, // сокет (наш сокет)
const struct sockaddr FAR *name, // адрес
int namelen // длинна адреса
);
// Объявим переменную для хранения адреса
sockaddr_in s_addr;
// Заполним ее:
ZeorMemory (&s_addr, sizeof (s_addr));
// тип адреса (TCP/IP)
s_addr.sin_family = AF_INET;
//адрес сервера. Т.к. TCP/IP представляет адреса в числовом виде, то для перевода
// адреса используем функцию inet_addr.
s_addr.sin_addr.S_un.S_addr = inet_addr («193.108.128.226»);
// Порт. Используем функцию htons для перевода номера порта из обычного в //TCP/IP представление.
s_addr.sin_port = htons (1234);
При ошибке функция возвращает SOCKET_ERROR.
Теперь сокет s связан с удаленной машиной и может посылать/принимать данные только с нее.
Шаг 5 — посылаем данные.
Для того что бы послать данные используем функцию send
int send(SOCKET s, // сокет- отправитель
const char FAR *buf, // указатель на буффер с данными
int len, // длинна данных
int flags // флаги (может быть 0)
);
Пример использования данной функции:
При ошибке функция возвращает SOCKET_ERROR.
Длинна пакета данных ограничена самим протоколом. Как узнать максимальную длину пакета данных мы рассмотрим в следующий раз. Возврат из функции не происходит до тех пор, пока данные не будут отправлены.
Шаг 6 -принимаем данные.
Принять данные от машины с которой мы предварительно установили соединение позволяет функция recv.
int recv(SOCKET s, // сокет- получатель
char FAR *buf, // адрес буфера для приёма данных
int len, // длинна буфера для приёма данных
int flags // флаги (может быть 0)
);
Если вы заранее не знаете размер входящих данных, то длинна буфера-получателя должна быть не меньше чем максимальный размер пакета, иначе сообщение может не поместится в него и будет обрезано. В этом случае функция возвращает ошибку.
Пример:
int actual_len = 0;
Если данные получены, то функция возвращает размер полученного пакета данных (а примере — actual_len) При ошибке функция возвращает SOCKET_ERROR. Заметьте, что функции send/recv будут ждать пока не выйдет тайм-аут или не отправится/придет пакет данных. Это соответственно вызывает задержку в работе программы. Как этого избежать читайте в следующих выпусках.
Шаг 6 -закрываем соединение.
Процедура закрытия активного соединения происходит с помощью функций shutdown и closesocket. Различают два типа закрытия соединений: abortive и graceful. Первый вид — это экстренное закрытие сокета (closesocket). В таком случае соединение разрывается моментально. Вызов closesocket имеет мгновенный еффект. После вызова closesocket сокет уже недоступен. Как закрыть сокет с помощью shutdown/closesocket читайте в следующих выпусках, так как эта тема требует более полного знания Winsock.