- •1. Сокеты, датаграммы и каналы связи
- •2. Инициализация приложения при работе с сокетами и завершение его работы
- •3. Создание и инициализация сокета
- •3.1. Создание сокета
- •3.2. Удаление сокета
- •3.3. Параметры сокета
- •3.4. Привязка адреса к сокету
- •3.5. Создание канала связи
- •3.5.1.Сторона сервера
- •3.5.1. Сторона клиента
- •3.5.3. Передача и прием данных
- •4. Решения при работе с сокетами
- •5. Порядок выполнения работы
- •6. Контрольные вопросы
- •Приложения
- •1. Коды ошибок различных функций при работе с сокетами
- •2. Сервер сокетов с оконным интерфейсом (протокол tcp/ip).
- •3. Клиент сокетов с оконным интерфейсом (протокол tcp/ip).
- •4. Сервер сокетов с оконным интерфейсом (протокол udp).
- •5. Клиент сокетов с оконным интерфейсом (протокол udp).
- •6. Сервер неблокирующих сокетов с использованием события wsaevent (протокол tcp/ip)
- •7. Клиент неблокирующих сокетов с использованием события wsaevent (протокол tcp/ip)
- •8. Сервер неблокирующих сокетов с использованием функции select
- •9.Клиент неблокирующих сокетов с использованием функции select
- •10. Сервер блокируюющих сокетов (протокол tcp/ip)
- •11. Клиент блокируюющих сокетов (протокол tcp/ip)
3.5.1. Сторона клиента
Рассмотрим процедуру установки канала связи со стороны клиента, использованную нами в приложении CLIENT, исходные тексты которого будут приведены ниже.
Для установки соединения в приложении используется функция SetConnection:
SOCKADDR _IN dest_sin;
void SetConnection(HWND hWnd)
{
PHOSTENT phe;
// Создаем сокет
srv_socket = socket(AF_INET , SOCK_STREAM, 0);
if(srv_socket == INVALID_SOCKET)
{
MessageBox(NULL, "socket Error", "Error", MB_OK);
return;
}
// Устанавливаем адрес IP и номер порта
dest_sin.sin_family = AF_INET ;
// Определяем адрес узла
phe = gethostbyname ("localhost ");
if(phe == NULL)
{
closesocket (srv_socket);
MessageBox(NULL, "gethostbyname Error", "Error", MB_OK);
return;
}
// Копируем адрес узла
memcpy((char FAR *)&(dest_sin.sin_addr ), phe->h_addr ,
phe->h_length);
// Копируем номер порта
dest_sin.sin_port = htons(SERV_PORT);
// Устанавливаем соединение
if(connect(srv_socket , (PSOCKADDR )&dest_sin,
sizeof(dest_sin)) < 0)
{
closesocket (srv_socket);
MessageBox(NULL, "connect Error", "Error", MB_OK);
return;
}
}
Вначале с помощью функции socket эта функция создает сокет. Затем выполняется заполнение адресной информацией структуры dest_sin.
Обратите внимание, что для получения адреса IP мы воспользовались функцией gethostbyname , указав ей имя узла localhost .
Это имя отображается в файле HOSTS на адрес 127.0.0.1:
localhost
Адрес 127.0.0.1 является локальным. Вы можете использовать его для тестирования приложений, выполняющих обмен данными при помощи протокола TCP/IP, запуская сервер и клиент на одном и том же компьютере.
После заполнения структуры с адресной информацией функция connect создает канал связи с сервером.
3.5.3. Передача и прием данных
После того как канал создан, можно начинать передачу данных. Для передачи данных при помощи протокола гарантированной доставки TCP вы можете воспользоваться функциями send и recv , которые входят в программный интерфейс Windows Sockets.
Функция передачи данных send имеет три параметра - дескриптор сокета sock, на котором выполняется передача, адрес буфера buf, содержащего передаваемое сообщение, размер этого буфера bufsize и флаги flags:
int send (SOCKET sock, const char FAR* buf, int bufsize, int flags);
В нашем приложении CLIENT мы передаем данные серверу следующим образом:
char szBuf[80];
lstrcpy(szBuf, "Test string");
send (srv_socket , szBuf, lstrlen(szBuf), 0);
Параметры функции recv, предназначенной для приема данных, аналогичны параметрам функции send :
int recv (SOCKET sock, char FAR * buf, int bufsize, int flags);
Заметим, что функции recv и send возвращают количество, соответственно, принятых и переданных байт данных. Приложение, которое принимает данные, должно вызывать функцию recv в цикле до тех пор, пока не будут приняты все переданные данные. При этом на один вызов функции send может приходиться несколько вызовов функции recv.
В случае ошибки обе эти функции возвращают значение SOCKET_ERROR . Для анализа причин возникновения ошибки следует воспользоваться функцией WSAGetLastError .
Cписок кодов ошибок, которые могут возникать при вызове команды send см. Таблица 6. в приложениях.
При выполнении функции recv могут возникать ошибки см. таблицу 9 в приложениях.
Передача и прием данных в цикле может привести к блокировке работы приложения. Если это неприемлемо, следует воспользоваться асинхронным расширением интерфейса Windows Sockets. Первое приложение SERVER демонстрирует асинхронный прием данных.
После установки канала связи оно вызывает функцию WSAAsyncSelect , указывая ей в качестве последнего параметра комбинацию констант FD_READ и FD_CLOSE . При этом функция главного окна приложения будет получать сообщение WSA_NETEVENT в тот момент времени, когда чтение данных не вызовет блокировки приложения:
#define WSA_NETEVENT (WM_USER + 2)
rc = WSAAsyncSelect (srv_socket , hWnd, WSA_NETEVENT,
FD_READ | FD_CLOSE );
При необходимости выполнения асинхронной посылки данных вы можете указать функции WSAAsyncSelect еще и параметр FD_WRITE .
Если функция WSAAsyncSelect выполнилась успешно, она возвращает нулевое значение, при ошибке - значение SOCKET_ERROR.
В зависимости от значения последнего параметра могут возникать разные коды ошибки, которые можно получить при помощи функции WSAGetLastError. Следующие ошибки могут возникнуть при любом значении параметра (см. таблицу 10 в приложениях).
Дополнительный код ошибки можно получить из параметра lParam при помощи макрокоманды WSAGETSELECTERROR.
При использовании параметра FD_CONNECT возможно появление следующих ошибок см. Таблицу 11 в приложениях:
Если используется параметр FD_CLOSE, может возникнуть одна из следующих ошибок см. Таблицу 12 в приложениях.
В том случае, когда указаны параметры FD_READ , FD_WRITE , FD_OOB , или FD_ACCEPT , может возникнуть ошибка с кодом WSAENETDOWN .
Обработчик сообщения WSA_NETEVENT должен выполнить анализ причины, по которой он был вызван, так как за один вызов функции WSAAsyncSelect можно задать несколько событий, вызывающих генерацию сообщения. Этот анализ проводится, например, следующим образом:
void WndProc_OnWSANetEvent(HWND hWnd, UINT msg,
WPARAM wParam, LPARAM lParam)
{
char szTemp[256];
int rc;
// Если на сокете выполняется передача данных,
// принимаем и отображаем эти данные в виде
// текстовой строки
if(WSAGETSELECTEVENT(lParam) == FD_READ )
{
rc = recv ((SOCKET)wParam, szTemp, 256, 0);
if(rc)
{
szTemp[rc] = '\0';
MessageBox(NULL, szTemp, "Reсeived data", MB_OK);
}
return;
}
// Если соединение завершено, выводми об этом сообщение
else if(WSAGETSELECTEVENT(lParam) == FD_CLOSE )
{
MessageBox(NULL, "Connection closed", "Server", MB_OK);
}
}
Отметим, что параметр wParam содержит дескриптор сокета, на котором выполняется передача данных, а параметр lParam - код события, которое произошло в сети.