If (gethostname(szInfo,sizeof(szInfo)))




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;

}

 



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-02-13 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: