Итак сегодня мы обсудим использование высокоуровнего програмного интерфейса под названием сокет.
Программирование сокетов несложно само по себе, но довольно поверхностно описано в доступной литературе, а Windows Sockets SDK содержит множество ошибок как в технической документации, так и в прилагаемых к ней демонстрационных примерах. К тому же имеются значительные отличия реализаций сокетов в UNIX и в Windows, что создает очевидные проблемы.
Сокеты — представляют собой высокоуровневый унифицированный интерфейс взаимодействия с телекоммуникационными протоколами. Они бывают двух типов: "блокируемые"(синхронные) и "неблокируемые"(ассинхронные).
Синхронные сокеты блокируют управление до получение ответа, а неблокируемые возвращают управление немедленно, выполняя работу в фоновом режиме.
Основная литература по сокетам - WinDows SDK. SDK - (Software Development Kit) - это документация, набор заголовочных файлов и инструментарий разработчика. Он может стать основным инструментом в изучении WinSock.
[Для чего нам нужны сокеты?]
Очень часто при программировании нам требуется работа с протоколом TCP/IP/соединение между двумя компьютерами. В этом случае мы можем воспользоваться компонентами, но если основной аспект программы - малый размер удобнее всего использовать WinSock.
[Начало работы][Блокируемые сокеты]
Для начала работы с WinScokets в C/C++ нужно подключить заголовочный файл
#include winsock2.h
И слинковать исходный файл с lib файлом - ws2_32.lib или ls2_ws32.a для Dev-CPP.
В среде разработки Microsoft Visual Studio для этого достаточно нажать
[Инициализирование библиотек]
Для начала работы с функциями WinScok мы должны инициализировать вызовом функции
int WSAStartup (WORD wVersionRequested, LPWSADATA lpWSAData)
Итак wVersionRequested - Номер версии,
lpWSAData - Указатель на структуру WSDATA в которую после инициализации будет занесена информация о производителях библиотеки.
Обычный вызов функции будет выглядеть так:
//Для начала объявим структуру WSADATA
WSADATA WSAData;
//Вызываем функцию инициализации,
//попутно проверяя возвращаемое значение, если не ноль, то выходим.
//Номер версии - 2, подверсии - тоже 2.
if (WSAStartup(0x0202, &WSAData))
return 0;
Никакой особой роли вызов этой функции не играет, и посему при разработке прикладных приложений ее можно не использовать. Если инициализация проваливается приложение возвращает 0;
[Создание объекта]
Следующим шагом мы должно создать объект сокет. Его прототип:
SOCKET socket(int af, int type, int protocol);
af-используемые протоколы. Для интернет приложений он должен иметь значение - AF_INET.
Аргумент type - тип создаваемого сокеты CK_STREAM или дейтаграммный - SOCK_DGRAM.
Аргумент protocol - какой протокол нужно будет использовать
TCP -для потоковых
UDP - для дейтаграмных.
Последний аргумент уточняет какой транспортный протокол следует использовать. Нулевое значение соответствует выбору по умолчанию: TCP - для потоковых сокетов и UDP для дейтаграммных.
Если функция завершилась успешно она возвращает дескриптор сокета. И ,конечно, перед программистом встанет вопрос:"Что использовать UDP или TCP"? Транспортный протокол UDP не гарантирует отправку сообщений, поэтому решение этой проблемы ложится на плечи программиста.
[Клиент][0]
Чтобы установить соединение с удаленным компьютером мы можем использовать функцию connect - int connect(SOCKET s, const struct sockaddr FAR* name, int namelen);
Аргумент s -дескриптор сокетов
Аргумент name - Указатель на адрес и порт удаленного узла
Аргумент namelen - размер структуры Sockaddr.
Структура sockaddr выглядит так:
struct sockaddr_in
{
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
}
Аргумент sin_family - Семейство протоколов, для работы с сетью интернет - AF_INET
Аргумент sin_port - Порт подключения
Аргумен sin_addr - IP компьютера, к которому подключается приложение.
[Сервер][0]
Прежде, чем сервер сможет использовать сокет,. он должен связать его с локальным адресом. Для связывания может использоваться
int bind (SOCKET s, const struct sockaddr FAR* name, int namelen);
Аргумент s - дескриптор сокеты
Аргумент name - Указатель на адрес и порт.
Аргумент namelen - Размер структуры SockAddr.
[Сервер][1]
Выполнив связывание потоковый сервер переходит в режим ожидания подключений вызывая функцию - int listen (SOCKET s, int backlog );
Аргумент s -дескриптор сокета
Аргумент backlog - Максимально допустимый размер очереди сообщений.
Нужно внимательно подходить к количеству очереди сообщений, т.к. если очередь сообщений будет переполнена, очередной клиент при попытке соединения получит отказ.
[Сервер][2]
Извлечение запросов на соединение из очереди осуществляется функцией "SOCKET accept (SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen)", которая автоматически создает новый сокет, выполняет связывание и возвращает его дескриптор, а в структуру sockaddr заносит сведения о подключившемся клиенте (IP-адрес и порт).Эта функция автоматически создает новый сокет, выполняет связывание и возвращает его дескриптор, а в структуру sockaddr заносит сведения о подключившемся клиенте (IP-адрес и порт).
[Передача и прием данных]
После того как мы установили соединение мы можем начать прием передачу данных. Делать это мы можем с помощью функций
int send(SOCKET s, const char FAR * buf, int len,int flags); //Для посылки данных
и
int recv(SOCKET s, char FAR* buf, int len, int flags); //Для приема данных
Функция send возвращает управление сразу же после ее выполнения, независимо от того, получила ли принимающая сторона наши данные или нет.
При успешном завершении функция возвращает количество передаваемых данных.
Теперь рассмотрим эти функции поподробнее.
Функция send- возвращает управление программе вне зависимости от того дошли до сервера данные или нет. При успешном завершении функция возвращает количество передаваемых (не переданных!) данных - т. е. успешное завершение еще не свидетельствует от успешной доставке.
Функция recv же наоборот ожидает на линии пока данные не будут приняты.
[Неблокируемые сокеты]
[Создаем неблокируемый сокет]
Точнее переводим его в асинхронный режим
SCOKET s;
u_long c=1;
u_long i;
nRet = ioctlsocket(s, FIONBIO, (u_long *) &c);
Если сервер не послал нам ответ сразу, функции будут возвращать WSAEWOULDBLOCK. В принципе, можно выполнять команду до получения результата. Т.е.
while ( !connect( s, (struct sockaddr *)&sa, sizeof(sa) ) != WSAEWOULDBLOCK )
{
connect(s,(struct sockaddr*)&sa,
sizeof(sa));
}
Но это приводит нас обратно к блокируемым сокетам. У нас другая цель. Поэтому, если нам нужно через определенное время посылать пинги или проверять есть ли данные в буфере, мы можем использовать ввод-вывод в WinSock.
[SELECT]
Это можно реализовать с помощью функции select(). Сама функция:
int select(
fd_set FAR * readfds,/*Возможность чтения*/
fd_set FAR * writefds,/*Возможность записи*/
fd_set FAR * exceptfds,/*Срочне данные*/
const struct timeval FAR * timeout/*Отсчет времени*/
);
Для работы с fd_set определены макросы
FD_ISSET(s, *set)/*Проверяет входит ли сокет в набор*/
FD_SET(s, *set)/*Добавляет сокет в набор*/
FD_CRL(s,*set)/*Удаляет сокет s из наборы*/
Итак, для примера напишем приложение проверяющее сокет на возможность записи
WSADATA WSAData;
if (WSAStartup(0x0202, &WSAData))
return 0;
SOCKET s;
sockaddr_in sa;
sa.sin_family=AF_INET;
sa.sin_port=htons(12345);
sa.sin_adrr.s_addr=inet_addr("127.0.0.1")
s=(AF_INET,SOCK_STREAM,0);
fd_set f;
int r;
char buff;
connect(s,(struct sockaddr *)&sa, sizeof(sa));
if(r=select(0,NULL,&f,NULL,NULL))=SOCKET_ERROR)
{
cout<<"Error";
}
if(FD_ISSET(s, &f))
{
send(s, buff, sizeof(buff),0);
}else{ cout<<"Error";}
[Неблокирующий connect]
В любом случае connect() остается неблокирющим. Но его можно перевести в неблокирющий режим функцией fcntl()
int flags = fcntl(s, F_GETFL, 0);
if(fcntl(s, F_SETFL, flags | O_NONBLOCK) ==SCOKET_ERROR)
{
cout<"Error";
}
if(connect(s, (struct sockaddr*)&sa, sizeof(sa)) !=SOCKET_ERROR)
{
if(errno == EINPROGRESS)
{
/*ту мы с помощью select() проверяем сокет на готовность*/
}
else
{
cout<<"Error";
}
}
else
{
/*
* Соединение было установлено за время
* системного вызова, работа продолжается
* традиционным способом.
*/
}
[Приемер][Клиент]
Итак, напишем небольшой пример клиента-сервера
#include winsock2.h
#include windows.h
WSADATA WSAData;
if (WSAStartup(0x0202, &WSAData))
return 0;
SOCKET s;
sockaddr_in sa;
sa.sin_family=AF_INET;
sa.sin_port=htons(1234);
sa.sin_addr.s_addr = inet_addr("127.0.0.1");
s=(AF_INET,SOCK_STREAM,0);
connect(s,(struct sockaddr *)&sa, sizeof(sa));
int buff[1024] = {0};
send(sock, buff, sizeof(buff), 0);
[Пример][Сервер]
#include winsock2.h
#include windows.h
WSADATA WSAData;
if (WSAStartup(0x0202, &WSAData))
return 0;
SOCKET s;
sockaddr_in sa;
sa.sin_family=AF_INET;
sa.sin_port=htons(1234);
sa.sin_addr.s_addr = inet_addr("127.0.0.1");
s=(AF_INET,SOCK_STREAM,0);
if (!bind(s, (struct sockaddr *)&sa, sizeof(struct sockaddr_in)))
return 0;
if (!listen(s, 0))
return 0;
AcpSock = accept(s, (struct sockaddr *)&sa, &SaLen);
int buff[1024] = {0};
recv(sock, buff, sizeof(buff), 0);
[P.S]
Итак, насколько мы убедились работа с сокетами не такая сложная, как может показаться, но все-таки я советую подробнее изучить литературу на данную тему. Ведь сокеты- это гораздо больше чем описано в статье. Тщательное изучение материала позволит вам писать разнообразные приложения. От простых прикладных для работы с вебом до громоздких и сложных приложений взаимодействующих с базами данных.
Реальне кодер %)
ОтветитьУдалитьединственная книга, которая нормально описывает работу с WinSock - это "Разработка сетевых приложений в Windows 98". Золотая книженция.
ОтветитьУдалить