printf(“Local host name has not been configured\n”);
Else
printf(“Host name is %s.\n”, szInfo);
if (WSACleanup())
printf(“Winsock cleanup FAILED!\n”);
else
printf(“Winsock cleanup is successful.\n”);
system(«pause»);
return;
}
Отличие этого примера от предыдущего заключается в использовании информационной функции gethostname(), имеющей следующий прототип:
int gethostname (
char FAR * name,
int namelen
);
В эту функцию передается буфер и его длина для возврата имени. При отсутствии ошибок эта функция вернет 0. Результат штатной работы программы приведен на рисунке 2.4.2.
Рисунок 2.4.2 – Работа функции gethostname()
Рассмотрим процедуру создания и закрытия сокетов, изменив предыдущий пример:
#include <stdio.h>
#include <winsock.h>
const int WINSOCK_VERSION = 0x0101;
void main()
{
SOCKET serverSocket;
Char szInfo[BUFSIZ];
WSADATA wsaData;
if (WSAStartup(WINSOCK_VERSION, &wsaData))
printf(“Winsock startup FAILED!\n”);
else
printf(“Winsock startup is successful.\n”);
if (gethostname(szInfo,sizeof(szInfo)))
printf(“Local host name has not been configured\n”);
else
printf(“Host name is %s.\n”, szInfo);
serverSocket = socket(PF_INET, SOCK_STREAM, 0);
if (serverSocket == INVALID_SOCKET)
printf(“Socket creation FAILED!\n”);
closesocket(serverSocket);
if (WSACleanup())
printf(“Winsock cleanup FAILED!\n”);
else
printf(“Winsock cleanup is successful.\n”);
system(«pause»);
return;
}
Добавлена следующая переменная:
SOCKET serverSocket;
Тип SOCKET определяется в файле winsock.h следующим образом:
/*
* The new type to be used in all
* instances which refer to sockets.
*/
typedef UINT_PTR SOCKET;
А UINT_PTR в файле basetsd.h как
typedef _W64 unsigned int UINT_PTR, *PUINT_PTR;
То есть, по сути, является замаскированным целочисленным числом без знака типа unsigned int и содержит идентификатор сокета в системе.
За создание сокета отвечает следующая строчка:
serverSocket = socket(PF_INET, SOCK_STREAM, 0);
Функция socket() создает сокет заданного типа и возвращает его идентификатор в случае успеха или константу INVALID_SOCKET (также определена в файле winsock.h) в противном случае. Все три аргумента функции являются целочисленными, задают тип сокета указанием нужных констант, список которых можно найти в заголовочном файле, в документации среды разработки или в RFC 790, от сентября 1981 года. Первый аргумент – тип сетевой модели, PF_INET соответствует сетевой модели Интернета (TCP/IP), PF_OSI модели OSI, PF_APPLETALK модели AppleTalk и т.д. Однако указание, например, PF_OSI в качестве сетевой модели приведет к ошибке создания сокета, поскольку в рамках Windows данная модель не поддерживается, а аргумент функции и константы введены для возможности расширения интерфейса и, возможно, использования в будущих операционных системах. Дальше указывается тип потока: SOCK_STREAM используется для TCP, а SOCK_DGRAM для UDP. Третий аргумент – собственно протокол, где 0 по умолчанию IP.
После работы сокет необходимо закрыть:
closesocket(serverSocket);
Единственным параметром функции является сокет, который требуется закрыть.
Попробуем реализовать простой сервер, работающий по протоколу http и использующий соответственно порт 80.
Рассмотрим, как производится подключение к серверу. Сначала программа подключается к адресу IP с созданием сокета. Программа будет ждать подключения. Для подключения программа клиент тоже создает сокет и пытается подключиться к сокету сервера. Как только сервер зарегистрирует попытку подключения, он создаст новый сокет. И этот новый сокет будет использоваться для взаимодействия с клиентом. А тот сокет, к которому была попытка подключения, будет ждать следующего. Сокет может быть создан на основе TCP или UDP. На этой основе производится взаимодействие сервера и многими программами.
Для организации связи серверу необходимо вызвать функцию bind() для действительного сокета и связать его с номером порта, который будет «прослушиваться» для ожидания подключения. Функция требует заполненную структуру SOCKADDR_IN с такими параметрами связи, как порт и атрибуты. Ее прототип:
struct sockaddr_in{
short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
char sin_zero[8];};
В данной структуре присутствует вложенная структура sin_addr, она описана следующим образом:
struct in_addr {
union {
struct{
unsigned char
s_b1,
s_b2,
s_b3,
s_b4;
} S_un_b;
struct{
unsigned short
s_w1,
s_w2;
} S_un_w;
unsigned long S_addr;
} S_un;
};
Смысл полей достаточно прозрачен: тип сетевого адреса (соответствующий разным сетевым моделям), адрес, порт.
После описания структур и заполнения данными можно вызывать bind() с проверкой результата на ошибку:
SOCKADDR_IN socketaddr;
socketaddr.sin_family = AF_INET;
socketaddr.sin_addr.s_addr = INADDR_ANY;
socketaddr.sin_port = htons(80);
if (bind(serverSocket,(LPSOCKADDR)&socketaddr,sizeof(socketaddr)) == SOCKET_ERROR)
printf(“Socket binding FAILED!”);
else
printf(“Socked binding is successful.\n”);
Ее прототип:
int bind (
SOCKET s,
const struct sockaddr FAR* name,
int namelen
);
Если всё нормально, то данная функция вернет 0 в противном случае SOCKET_ERROR. Как видно адрес не указывается (INADDR_ANY это константа нуля), поскольку создается сокет для сервера. Полный текст программы выглядит следующим образом:
#include <stdio.h>
#include <winsock.h>
const int WINSOCK_VERSION = 0x0101;
void main()
{
SOCKET serverSocket;
SOCKADDR_IN socketaddr;
char szInfo[BUFSIZ];
WSADATA wsaData;
if (WSAStartup(WINSOCK_VERSION, &wsaData))
printf(“Winsock startup FAILED!\n”);
else
printf(“Winsock startup is successful.\n”);
if (gethostname(szInfo,sizeof(szInfo)))
printf(“Local host name has not been configured\n”);
else
printf(“Host name is %s.\n”, szInfo);
serverSocket = socket(PF_INET, SOCK_STREAM, 0);
if (serverSocket == INVALID_SOCKET)
printf(“Socket creation FAILED!\n”);
socketaddr.sin_family = AF_INET;
socketaddr.sin_addr.s_addr = INADDR_ANY;
socketaddr.sin_port = htons(80);
if (bind(serverSocket, (LPSOCKADDR)&socketaddr, sizeof(socketaddr)) == SOCKET_ERROR)
printf(“Socket binding FAILED!\n”);
Else
printf(“Socked binding is successful.\n”);
closesocket(serverSocket);
if (WSACleanup())
printf(“Winsock cleanup FAILED!\n”);
else
printf(“Winsock cleanup is successful.\n”);
system(«pause»);
return;
}
В компьютерах архитектуры Intel 80x86 и совместимыми с ними слово (двухбайтовое число) хранится следующим образом:
младщий байт n;
старший байт n+1,
где n – номер ячейки в памяти.
К сожалению в Интернете наоборот:
младщий байт n+1;
старший байт n.
Для решения этой проблемы WinSock API предоставляет следующие функции:
1. htohl – Преобразует 32 битные локальные числа к сетевым, сортируя байты;
2. htohs – Преобразует 16 битные локальные числа к сетевым, сортируя байты;
3. ntonl – Преобразует 32 битные сетевые числа к локальным, сортируя байты;
4. ntons – Преобразует 16 битные сетевые числа к локальным, сортируя байты.
На данном этапе, программа почти готова принимать сообщения по сети, однако как узнать, что клиент установил соединение с сервером? Для этого используются события и сообщения ОС Windows. Как известно, обработчиком сообщений является окно, а, следовательно, необходимо связать сокет с дескриптором окна. Для этого служит функция WSAAsyncSelect(). Как следует из приставки WSA, она относится к расширениям Windows для функций Беркли, то есть, специфична для этой ОС.
Int error;
error = WSAAsyncSelect(serverSocket, hWnd, WM_SERVER_ACCEPT, FD_ACCEPT);
if (error == SOCKET_ERROR)
printf(“WSAAsyncSelect() FAILED!\n”);
Прототип функции имеет следующий вид:
int WSAAsyncSelect (
SOCKET s,
HWND hWnd,
unsigned int wMsg,
long lEvent
);
Первым аргументом передается сокет, вторым – дескриптор окна, третьим – сообщение, которое будет послано, четвертым – событие, при котором генерируется сообщение. FD_ACCEPT означает, что сообщение сгенерируется при запросе от клиента.
Здесь интересен параметр unsigned int wMsg – этот параметр говорит о том, какое сообщение будет послано в случае подключения к серверу. Сообщения WM_SERVER_ACCEPT в Windows не существует, это пользовательское сообщение, его можно описать следующим образом:
const int WM_SERVER_ACCEPT = WM_USER + 1;
Включается «прослушивание» функцией listen().
Error = listen(serverSocket, 10);
if (error == SOCKET_ERROR)
printf(«listen() FAILED!\n»);
Прототип функции listen() выглядит следующим образом:
int listen (
SOCKET s,
int backlog
);
Первый аргументом является сокет, вторым – максимально количество подключений.
К сожалению, просто добавить в текст последней программы функцию WSAAsyncSelect не получится. Первая проблема очевидна – это отсутствие дескриптора окна у консольного приложения, вторая – отсутствие обработки поступающих сообщений. И если первую еще можно решить, например, поиском дескриптора окна по его заголовку функцией FindWindow(), то вторая приводит к необходимости создания оконной процедуры и полноценного Windows приложения.
Ниже приведен текст Winsock программы, выступающей в роли сервера HTTP:
// Стандартный включаемый файл Windows
#include <windows.h>
#include <winsock.h>
const int WINSOCK_VERSION = 0x0101;
const int WM_SERVER_ACCEPT = WM_USER + 1;
// Прототип функции обратного вызова для обработки сообщений
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
bool Start(HWND);
void Stop();
char g_szStatus[512];
SOCKET g_serverSocket;
// Функция вызывается автоматически, когда программа запускается
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
HWND hWnd;
MSG msg;
WNDCLASSEX wndclass;
// Настройка класса окна
wndclass.cbSize = sizeof(WNDCLASSEX);
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = “Window Class”; // Имя класса
wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
// Регистрация класса окна
if(RegisterClassEx(&wndclass) == 0)
{
// Сбой программы, выход
return 0;
}
// Создание окна
hWnd = CreateWindowEx(
WS_EX_OVERLAPPEDWINDOW,
«Window Class», // Имя класса
«Сервер», // Текст заголовка
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
CW_USEDEFAULT,
CW_USEDEFAULT,
600,
100,
NULL,
NULL,
hInstance,
NULL);
// Отображение окна
ShowWindow(hWnd, iCmdShow);
// Обработка сообщений, пока программа не будет прервана
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
// Функция обратного вызова для обработки сообщений
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
HDC hDC;
PAINTSTRUCT ps;
switch(iMsg)
{
// Вызывается сразу же при создании окна функцией CreateWindow() (или CreateWindowEx())
case WM_CREATE:
strcpy(g_szStatus, «Щелкните левой кнопкой мыши для запуска сервера, правой для его остановки»);
// g_serverSocket инициализируется значением недействительного сокета
g_serverSocket = INVALID_SOCKET;
break;
// Вызывается, когда пользователь отпускает левую кнопку мыши
case WM_LBUTTONUP:
// Запуск сервера
Start(hWnd);
// Перерисовка окна (генерация сообщения WM_PAINT)
InvalidateRect(hWnd, NULL, TRUE);
break;
// Вызывается, когда пользователь отпускает правую кнопку мыши
case WM_RBUTTONUP:
// Остановка сервера
Stop();
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_SERVER_ACCEPT:
strcpy(g_szStatus, “Клиент подключился”);
InvalidateRect(hWnd, NULL, TRUE);
break;
// Вызывается, когда окно обновляется
case WM_PAINT:
hDC = BeginPaint(hWnd, &ps);
TextOut(hDC, 20, 20, g_szStatus, (int)strlen(g_szStatus));
EndPaint(hWnd, &ps);
break;
// Вызывается, когда пользователь закрывает окно
case WM_DESTROY:
// Остановка сервера при закрытии окна
Stop();
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, iMsg, wParam, lParam);
}
return 0;
}
Bool Start(HWND hWnd)
{
int error;
SOCKADDR_IN socketaddr;
WSADATA wsaData;
if (g_serverSocket!= INVALID_SOCKET)
{
strcpy(g_szStatus, “Сервер уже запущен”);
return false;
}
if (WSAStartup(WINSOCK_VERSION, &wsaData))
{
strcpy(g_szStatus, “Ошибка инициализации Winsock”);
return false;
}
g_serverSocket = socket(PF_INET, SOCK_STREAM, 0);
if (g_serverSocket == INVALID_SOCKET)
{
strcpy(g_szStatus, «Ошибка создания сокета»);
return false;
}
socketaddr.sin_family = AF_INET;
socketaddr.sin_addr.s_addr = INADDR_ANY;
socketaddr.sin_port = htons(80);
error = bind(g_serverSocket, (LPSOCKADDR)&socketaddr, sizeof(socketaddr));
if (error == SOCKET_ERROR)
{
strcpy(g_szStatus, “Ошибка связи сокета с портом”);
return false;
}
error = WSAAsyncSelect(g_serverSocket, hWnd, WM_SERVER_ACCEPT, FD_ACCEPT);
if (error == SOCKET_ERROR)
{
strcpy(g_szStatus, “Ошибка связи сокета с окном”);
return false;
}
error = listen(g_serverSocket, 10);
if (error == SOCKET_ERROR)
{
strcpy(g_szStatus, “Ошибка прослушивания сокета”);
return false;
}
strcpy(g_szStatus, “Сервер успешно запущен”);
return true;
}
Void Stop()
{
if (g_serverSocket == INVALID_SOCKET)
{
strcpy(g_szStatus, «Сервер не запущен или уже остановлен»);
return;
}
closesocket(g_serverSocket);
g_serverSocket = INVALID_SOCKET;
If (WSACleanup())
{
strcpy(g_szStatus, “Ошибка освобождения Winsock”);
return;
}
strcpy(g_szStatus, “Сервер успешно остановлен”);
return;
}
Итак, нам нужно запустить и остановить сервер. Это будет происходить по нажатию левой кнопки мыши – запуск, правой – остановка. В текст простейшего приложения Windows внесены следующие изменения:
1. добавлено подключение заголовочного файла winsock.h;
2. определены константы для номера используемой версии winsock и пользовательского сообщения, которое будет генерироваться при поступлении запроса с клиентской программы;
3. введены функции Start() и Stop() для запуска и остановки сервера;
4. добавлены две глобальных переменных g_szStatus и g_serverSocket для хранения выводимой на экран текстовой информации о состоянии сервера и сокет, запись и чтение в эти переменных осуществляется как в оконной процедуре, так и в Start() и в Stop();
5. в функцию CreateWindow внесены косметические изменения – названия программы, стиль окна без изменения размера, произвольное начальное положение, описываемое константой CW_USEDEFAULT, размеры окна, опытным путем подобранные под выводимый текст состояния;
6. новая оконная процедура теперь обрабатывает сообщения о создании окна, отпускании левой кнопки мыши, отпускании правой кнопки мыши, поступлении запроса клиента, уничтожении окна.
Результат запуска приложения показан на рисунках 2.4.3, 2.4.4.
Рисунок 2.4.3 – Запуск серверной программы
Рисунок 2.4.4 – Запуск сервера по щелчку левой кнопки мыши
При запуске сервера может появиться оповещение файрволла о сетевой активности приложения. Как подключится к серверу? Для этого необходима другая программа, называемая клиентом. Поскольку при связи сокета с портом указан порт 80, используемый для HTTP протокола, можно воспользоваться любым Интернет – браузером: Internet Explorer, Firefox, Opera, Google Chrome и т.д. Чтобы узнать IP адрес сервера в сети можно воспользоваться командой ipconfig. Для это необходимо запустить программу «Командная строка» (Пуск-Все программы – Стандартные – Командная строка или Пуск – Выполнить… - набрать cmd и нажать enter), набрать ipconfig и нажать enter. Появится информация, подобная показанной на рисунке 2.4.5.
Рисунок 2.4.6 – Команда ipconfig
Соответственно, зная IP-адрес компьютера, на котором запущена приведенная программа, с помощью браузера можно к ней подключиться с любого компьютера сети. Другим вариантом является использование уникального IP – адреса 127.0.0.1, что соответствует адресу локальной машины. То есть, можно запустить сервер и подключится к нему с этого же компьютера, используя приведенный IP-адрес или URL localhost, соответствующий такому IP – адресу. Это очень удобно при отладке сетевых программ на одном компьютере, не используя подключение к локальной или глобальной сети.
Результат подключения клиента показан на рисунке 2.4.5.
Рисунок 2.4.7 – Подключение клиента к серверу
Сервер успешно среагировал на подключение клиента обработкой введенного сообщения WM_SERVER_ACCEPT. Однако для полноценной поддержки взаимодействия «клиент – сервер», необходимо, чтобы сервер «ответил» клиенту. Пока этого не происходит, что видно по состоянию браузера, который подключился к серверу, но «завис» на этапе открытия страницы, ожидая ответа.
Окончательный текст демонстрационной программы-сервера следующий:
// Стандартный включаемый файл Windows
#include <windows.h>
#include <winsock.h>
const int WINSOCK_VERSION = 0x0101;
const int WM_SERVER_ACCEPT = WM_USER + 1;
// Прототип функции обратного вызова для обработки сообщений
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void OnServerAccept(WPARAM, LPARAM);
bool Start(HWND);
void Stop();
char g_szStatus[512];
SOCKET g_serverSocket;
// Функция вызывается автоматически, когда программа запускается
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
HWND hWnd;
MSG msg;
WNDCLASSEX wndclass;
// Настройка класса окна
wndclass.cbSize = sizeof(WNDCLASSEX);
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = “Window Class”; // Имя класса
wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
// Регистрация класса окна
if(RegisterClassEx(&wndclass) == 0)
{
// Сбой программы, выход
return 0;
}
// Создание окна
hWnd = CreateWindowEx(
WS_EX_OVERLAPPEDWINDOW,
«Window Class», // Имя класса
«Сервер», // Текст заголовка
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
CW_USEDEFAULT,
CW_USEDEFAULT,
600,
100,
NULL,
NULL,
hInstance,
NULL);
// Отображение окна
ShowWindow(hWnd, iCmdShow);
// Обработка сообщений, пока программа не будет прервана
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
// Функция обратного вызова для обработки сообщений
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
HDC hDC;
PAINTSTRUCT ps;
switch(iMsg)
{
// Вызывается сразу же при создании окна функцией CreateWindow() (или CreateWindowEx())
case WM_CREATE:
strcpy(g_szStatus, «Щелкните левой кнопкой мыши для запуска сервера, правой для его остановки»);
// g_serverSocket инициализируется значением недействительного сокета
g_serverSocket = INVALID_SOCKET;
break;
// Вызывается, когда пользователь отпускает левую кнопку мыши
case WM_LBUTTONUP:
// Запуск сервера
Start(hWnd);
// Перерисовка окна (генерация сообщения WM_PAINT)
InvalidateRect(hWnd, NULL, TRUE);
break;
// Вызывается, когда пользователь отпускает правую кнопку мыши
case WM_RBUTTONUP:
// Остановка сервера
Stop();
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_SERVER_ACCEPT:
OnServerAccept(wParam, lParam);
InvalidateRect(hWnd, NULL, TRUE);
break;
// Вызывается, когда окно обновляется
case WM_PAINT:
hDC = BeginPaint(hWnd, &ps);
TextOut(hDC, 20, 20, g_szStatus, (int)strlen(g_szStatus));
EndPaint(hWnd, &ps);
break;
// Вызывается, когда пользователь закрывает окно
case WM_DESTROY:
// Остановка сервера при закрытии окна
Stop();
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, iMsg, wParam, lParam);
}
return 0;
}