IP-телефония — практические подходы к реализации схемы PC—PC
(Продолжение. Начало в № 2’ 2001 )
Занятие 2. Передача данных в сети
Занятие 2. Передача данных в сети
Как установить связь между приложениями?
Если у вас еще не установлен протокол TCP\IP, установите его.
Для решения задачи передачи данных существует три механизма:
- вызовы удаленных процедур (RPC);
- именованные программные каналы (pipes);
- гнезда (socket).
Гнезда являются одной из форм межпрограммного взаимодействия (IPC). Именно на написании гнездовых программ мы и остановимся. При их создании можно использовать стандартный класс Visual C++ CAsyncSocket. Лично я организовал свой. Постараюсь передать суть. Для работы программы необходимо организовать Клиент и Сервер. Клиент передает данные, Сервер их обрабатывает и выдает Клиенту результат.
КЛИЕНТ
Первой строкой мы подключаем необходимую нам библиотеку <winsock.h>. Для инициализации сети вызывается функция WSAStartup, которая в качестве параметра запрашивает версию 1.1 Socket DLL, а детали реализации передает обратно в структуру ImplementationDetails (листинг 1).
#include <winsock.h> WORD VersionRequested; WSADATA ImplementationDetails; int nRlt; VersionRequested=MAKEWORD(1,1); nRlt=WSAStartup(VersionRequested,&ImplementationDetails); if (nRlt!=0) <СТОП>;
Листинг 1. Инициализация сети
При создании гнезда (листинг 2), используем функцию socket, три параметра указывают, что:
- PF_INET — использует протокол TCP/IP.
- SOCK_STREM — поточное гнездо.
- 0 — без специальных атрибутов.
SOCKET nSocket; SOCKADDR_IN ClientSocketAddress; ServerSocket =socket(PF_INET,SOCK_STREAM,0); if (ServerSocket = =INVALID_SOCKET) <СТОП>; ClientSocketAddress.sin_family=AF_INET; ClientSocketAddress.sin_port=100; ClientSocketAddress.sin_addr.S_un.S_addr=inet_addr(<IP адрес Сервера>); connect(ServerSocket,(LPSOCKADDR) &ClientSocketAddress, sizeof(ClientSocketAddress));
Листинг 2. Установка соединения
При ошибке создания гнезда останавливаем выполнение программы. Если гнездо удачно создано, переходим к описанию Internet-структуры (SOCKADDR_IN), которая будет нам необходима для осуществления соединения с Серверной частью:
- AF_INET — указывает, что это структура Internet.
- 100 — указывает номер порта, к которому необходимо подключиться, с помощью адреса (100 — как пример).
- Inet_addr(<IP адрес Сервера>) — указывается IP-адрес Сервера (xxx.xxx.xxx.xxx), к которому необходимо подключиться. Адрес задается как текстовая строка и преобразуется 31-битному представлению.
Функция Connect осуществляет подключение к серверу. У данной функции три параметра:
- Номер гнезда.
- Адрес структуры SOCKADDR_IN, преобразованный к типу LPSOCKADDR.
- Размер структуры SOCKADDR_IN (в байтах).
При возникновении ошибки функция возвращает ее код, иначе — ноль. Если соединение прошло удачно, можно приступать к передаче и чтению данных. Но об этом чуть позже. Сейчас мы рассмотрим, как реализован Сервер и к чему мы, в конце концов, присоединились.
СЕРВЕР
Инициализация сети у Сервера идентична инициализации Клиента. Вы можете смело переписать программный код (см. листинг 1). Далее создаем гнездо. И если нет ошибки, переходим к описанию Internet-структуры. Здесь начинаются различия (листинг 3):
SOCKET nSocket; SOCKADDR_IN ServerSocketAddress; nSocket=socket(PF_INET,SOCK_STREAM,0); if (nSocket= =INVALID_SOCKET) <СТОП>; ServerSocketAddress.sin_family=AF_INET; ServerSocketAddress.sin_port=100; ServerSocketAddress.sin_addr.S_un.S_addr=INADDR_ANY; bind(nSocket,(LPSOCKADDR) & ServerSocketAddress, sizeof(ServerSocketAddress)); listen(nSocket,<количество клиентов>)
Листинг 3. Установка привязок
Адрес гнезда сервера устанавливается в INADDR_ANY. Адрес INADDR_ANY позволяет Серверу осуществить привязку к любому IP-адресу в системе «клиент-сервер» с множественной адресацией. Обратите внимание: значение порта у Клиента и Сервера должны совпадать (см. листинги 2 и 3). Функция bind привязывает адрес, определенный в структуре ServerSocketAddress, к конкретному гнезду. Теперь для того, чтобы одновременно несколько пользователей могли подключиться к Серверу, необходимо организовать очередь Клиентов, что мы и делаем функцией listen. Второй параметр данной функции определяет максимальное количество Клиентов, которое Сервер может принять. Когда Клиент выполняет соединение, он автоматически получает подтверждение, не блокируется в функции Connect и может продолжать обработку своих данных. Клиент ставится в очередь до тех пор, пока Сервер не сможет осуществить соединение. Если Клиент начнет передавать данные еще до того, как Сервер осуществил это соединение, то данные будут буферизироваться у Клиента до тех пор, пока буфер не заполнится.
После этого клиентская программа блокируется до тех пор, пока Сервер не сможет обработать Клиента. Прием Клиентов осуществляет функция accept (листинг 4):
// ---- Блок описания ------- fd_set AllSockets, ReadS, WriteS, ExceptS; int rv, ClientSocket; FD_ZERO(&AllSockets); FD_ZERO(&ReadS); FD_ZERO(&WriteS); FD_ZERO(&ExceptS); SOCKADDR_IN AddressClient; // ---- Блок кода ------- if (ReadS!=NULL) User_copy(ReadS,&AllSockets); if (WriteS!=NULL) User_copy(WriteS,&AllSockets); if (ExceptS!=NULL) User_copy(ExceptS,&AllSockets); rv=select(0,ReadS,WriteS,ExceptS,NULL); if (rv= = SOCKET_ERROR) { <ОБРАБОТКА ОШИБОК> } if (FD_ISSET(nSocket, ReadS)) { ClientSocket =accept(nSocket, (LPSOCKADDR)&AddressClient, NULL); if (ClientSocket= =130 INVALID_SOCKET) { <ОБРАБОТКА ОШИБОК> } FD_SET(ClientSocket, &AllSockets); FD_CLR(nSocket, ReadS); }
Листинг 4. Контроль активности, прием Клиента
Сначала мы описываем четыре переменных (fd_set), которые представляют собой массив гнезд:
- AllSockets — набор, представляющий собой список всех контролируемых гнезд.
- ReadS — набор, содержащий только гнезда, которые имеют доступные для считывания данные.
- WriteS — набор, содержащий только гнезда, которые имеют доступные для записи данные.
- ExceptS — набор, содержащий только гнезда, которые имеют доступные внедиапазонные данные.
Инициализируем переменные fd_set, используя для этой цели макрокоманду FD_ZERO, которая обнулит эти переменные. А в переменной AddressClient будет расположен адрес текущего Клиента, когда он принимается Сервером. Далее следует сам программный блок приема и обработки Клиента. Как правило, его вставляют в цикл с выходом по завершении программы.
Пользовательская функция User_copy осуществляет инициализацию наборов fd_set — ReadS, WriteS, ExceptS (листинг 5):
void User_copy(fd_set* out, fd_set* in) { UINT i; for(i=0;i<in->fd_count; i++)out->fd_array[i]=in->fd_array[i]; out->fd_count=in->fd_count; }
Листинг 5. Функция копирования fd_set
Эта функция предназначена для копирования одного набора fd_set в другой, а также для копирования значения счета из входящей структуры в исходящую структуру.
Выполняемая следующей функция select осуществляет проверку гнезд на содержание в них нормальных или внедиапазонных данных, а также на возможность произвести запись своих данных. Если данные отсутствуют, то функция входит в режим ожидания данных. При появлении данных функция select осуществляет возвращение, набор ReadS будет содержать только гнезда, которые имеют доступные для считывания данные, WriteS — доступные для записи, ExceptS — доступные внедиапазонные данные. Последний параметр функции select задает время выдержки. Если вы не хотите осуществлять режим никакой выдержки, установите значение в NULL. Вместо любого набора fd_set можно также установить значение NULL, функция select не будет производить проверку по данному набору fd_set. Если в качестве параметра используется набор WriteS, функция всегда будет производить возврат. Необходимо отметить, что запрос Клиента на соединение с Сервером на гнезде прослушивания рассматривается как наличие доступных для считывания данных и Сервер должен произвести соединение accept. Однако, прежде чем выполнять данную функцию, необходимо убедиться, что в настоящий момент другие Клиенты не посылают на Сервер данные. Для этой цели используем макрокоманду FD_ISSET, которая покажет, находится ли в данный момент номер nSocket в наборе ReadS. Если гнездо находится в наборе ReadS, то вызывается функция accept для приема нового Клиента. Функция FD_SET используется для добавления нового гнезда к набору AllSockets — для последующего за ним контроля функцией select. После того как вы обработали подключившегося Клиента, он устраняется из набора ReadS; теперь можно обрабатывать других Клиентов.
После установления соединения Сервера и Клиента остается рассмотреть вопрос передачи/получения данных. Функции, выполняющие данные операции, — send и recv. Ниже приведен их формат.
int send( |
||
SOCKET |
s, |
Идентификатор гнезда, для Клиента — значение, возвращаемое функцией connect, для Сервера — функцией accept |
const char FAR |
*buf, |
Буфер, содержащий данные для передач |
int |
len, |
Длина буфера |
int |
flags |
Индикатор типа данных: MSG_OOB — данные OOB; 0 — нормальные данные |
); |
int recv( |
||
SOCKET |
s, |
Идентификатор гнезда, для Клиента — значение, возвращаемое функцией connect, для Сервера — функцией accept |
const char FAR |
*buf, |
Буфер, содержащий данные для получения |
int |
len, |
Длина буфера |
int |
flags |
Индикатор типа данных: MSG_OOB — данные OOB; 0 — нормальные данные |
); |
Пример функции передачи/получения данных из переменной класса CString (листинги 6, 7, 8):
int MySend(SOCKET nSocket, CString lpBuf, int nBufLen, int nFlags) { int rv=send(nSocket,(LPCTSTR)lpBuf, nBufLen, nFlags); if (rv= = SOCKET_ERROR) { VERIFY(SOCKET_ERROR != closesocket(nSocket)); WSACleanup(); } return rv; }
Листинг 6. Передача строки (CString)
int MyRecv(SOCKET nSocket, CString* lpBuf, int nBufLen, int nFlags) { int rv; rv=recv(nSocket,lpBuf->GetBuffer(nBufLen),nBufLen,nFlags); lpBuf->ReleaseBuffer(); if (rv= =SOCKET_ERROR) { VERIFY(SOCKET_ERROR != closesocket(nSocket)); WSACleanup(); } return rv; }
Листинг 7. Прием строки (CString)
CString sendBuff="12345678"; CString recvBuff; SOCKET s; // значение, возвращаемое функцией connect, // для Сервера – функцией accept. MySend (s, sendBuff,8,0);
Листинг 8. Пример вызова функций
Данные функции удобно использовать, если в качестве данных используются короткие сообщения. Класс CString обладает мощными встроенными средствами для обработки текстовой информации.
Продолжение следует
КомпьютерПресс 3'2001