If (WSAGETASYNCERROR(lParam))




{

strcpy(g_szStatus, “Ошибка определения клиента”);

return;

}

if (WSAGETSELECTEVENT(lParam) == FD_ACCEPT)

{

length = sizeof(SOCKADDR);

clientSocket = accept(g_serverSocket, (LPSOCKADDR)&socketclientaddr, &length);

if (clientSocket == INVALID_SOCKET)

{

strcpy(g_szStatus, “Ошибка создания сокета клиента”);

return;

}

}

else if (WSAGETSELECTEVENT(lParam) == FD_READ)

{

clientSocket = (SOCKET)wParam;

ZeroMemory(g_szStatus, sizeof(g_szStatus));

error = recv(clientSocket, g_szStatus, sizeof(g_szStatus), 0);

if (error == SOCKET_ERROR)

{

strcpy(g_szStatus, “Ошибка в получении запроса клиента”);

return;

}

error = send(clientSocket, html, (int)strlen(html), 0);

if (error == SOCKET_ERROR)

{

strcpy(g_szStatus, “Ошибка в ответе клиенту”);

return;

}

closesocket(clientSocket);

}

strcpy(g_szStatus, “Клиент подключился”);

return;

}

 

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)

{

wsprintf(g_szStatus, “Ошибка связи сокета с портом: %d”, WSAGetLastError());

return false;

}

 

error = WSAAsyncSelect(g_serverSocket, hWnd, WM_SERVER_ACCEPT, FD_ACCEPT | FD_READ);

 

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;

}

 

Изменения следующие:

Код обработки сообщения WM_SERVER_ACCEPT вынесен в отдельную функцию OnServerAccept() для упрощения оконной процедуры.

В вызове функции WSAAsyncSelect флаг заменен на FD_ACCEPT | FD_READ, что означает генерацию сообщения при подключении клиента и при передаче клиентом данных. Дело в том, что послать клиенту данные возможно сразу после установки соединения функцией accept(), однако в случае использования web-браузера это не получится. Web-браузер после установки соединения посылает серверу запрос, например, с именем страницы и требует, чтобы сервер принял этот запрос. Без этого обратная передача данных не будет осуществляться. Следовательно, в нашем случае, требуется не только установить соединение, но и сначала подождать запрос от клиента, то есть сообщения FD_READ. Для произвольной передачи данных c целью тестирования сервера, удобно воспользоваться программой telnet в качестве клиентской. Для ее запуска достаточно набрать в командной строке telnet адрес порт через пробел и нажать enter.

Приведенную функцию обработки запросов клиентов можно мысленно разделить на две части: установку соединения, обработку запроса клиента. Текст первой части следующий:

if (WSAGETSELECTEVENT(lParam) == FD_ACCEPT)

{

length = sizeof(SOCKADDR);

clientSocket = accept(g_serverSocket, (LPSOCKADDR)&socketclientaddr, &length);

if (clientSocket == INVALID_SOCKET)

{

strcpy(g_szStatus, “Ошибка создания сокета клиента”);

return;

}

}

То есть, если произошла попытка установки соединения, то вызывается функция accept(), которая его устанавливает. Имеет три аргумента: сокет сервера, необязательный (можно задать NULL) указатель на структуру, куда запишется IP – адрес клиента и указатель на ячейку, где хранится размер этой структуры, тоже необязательный. Из-за существования разных версий Winsock требуется явное преобразование второго аргумента. Accept() автоматически создает новый сокет, выполняет связывание и возвращает его дескриптор. Если в момент вызова accept() очередь пуста, функция не возвращает управление до тех пор, пока с сервером не будет установлено хотя бы одно соединение. Следовательно, сервер можно создать и в консольном режиме без использования сообщений Windows, путем постоянного вызова accept() в бесконечном цикле. В случае возникновения ошибки функция возвращает отрицательное значение.

Текст второй части, отвечающей за ответ клиенту, следующий:

else if (WSAGETSELECTEVENT(lParam) == FD_READ)

{

clientSocket = (SOCKET)wParam;

ZeroMemory(g_szStatus, sizeof(g_szStatus));

error = recv(clientSocket, g_szStatus, sizeof(g_szStatus), 0);

if (error == SOCKET_ERROR)

{

strcpy(g_szStatus, “Ошибка в получении запроса клиента”);

return;

}

error = send(clientSocket, html, (int)strlen(html), 0);

if (error == SOCKET_ERROR)

{

strcpy(g_szStatus, “Ошибка в ответе клиенту”);

return;

}

closesocket(clientSocket);

}

 

Дескриптор сокета клиента, запросившего данные, содержится в переменной wParam. Использовать дескриптор, полученный ранее от функций accept(), не рекомендуется, так как одновременно могут поступать быть запросы с разных клиентов.

После тока как соединение установлено, потоковые сокеты могут обмениваться с удаленным узлом данными, вызывая функции «int send (SOCKET s, const char FAR * buf, int len,int flags) » и «int recv (SOCKET s, char FAR* buf, int len, int flags) » для посылки и приема данных соответственно.

Функция же recv() (rec ei v e, получить) возвращает управление после того, как получит датаграмму. Датаграмма – это совокупность одного или нескольких IP пакетов, посланных вызовом send(). Первый аргумент – сокет клиента, второй буфер, в который записываются получаемые данные, третий размер этого буфера, четвертый дополнительные флаги настроек. Подразумевается, что функции recv() предоставлен буфер достаточных размеров, - в противном случае ее придется вызвать несколько раз. Однако, при всех последующих обращениях данные будет браться из локального буфера сетевого интерфейса, а не приниматься из сети, т.к. TCP-провайдер не может получить «кусочек» датаграммы, а только ею всю целиком.

Функция send() возвращает управление сразу же после ее выполнения независимо от того, получила ли принимающая сторона наши данные или нет. Аргументы функции аналогичны аргументам функции recv(). При успешном завершении функция возвращает количество передаваемых (не переданных!) данных – т. Е. успешное завершение еще не свидетельствует от успешной доставке! Протокол TCP гарантирует успешную доставку данных получателю, но лишь при условии, что соединение не будет преждевременно разорвано. Если связь прервется до окончания пересылки, данные останутся не переданными, но вызывающий код не получит об этом никакого уведомления. Ошибка возвращается лишь в случае, если соединение разорвано до вызова функции send().

Результат работы сервера показан на рисунке 2.4.8.

Рисунок 2.4.8 – Работа сервера по протоколу HTTP

Реализация клиентских программ часто гораздо проще. В этом случае вызов функции bind() заменяется на connect() с указанием адреса сервера, функцией send() посылается запрос, далее ожидается ответ – recv().

 

#include <stdio.h>

#include <winsock.h>

 

const int WINSOCK_VERSION = 0x0101;

 

void main()

{

char buffer[512];

SOCKET clientSocket;

SOCKADDR_IN socketaddr;

WSADATA wsaData;

 

static char request[] = “GET / \r\nHTTP 1.0 \r\n\r\n”;

 

printf(“Enter IP – address: “);

scanf(“%s”, buffer);

 

if (WSAStartup(WINSOCK_VERSION, &wsaData))

printf(“Winsock startup FAILED!\n”);

else

printf(“Winsock startup is successful.\n”);

 

clientSocket = socket(PF_INET, SOCK_STREAM, 0);

 

if (clientSocket == INVALID_SOCKET)

printf(“Socket creation FAILED!\n”);

 

socketaddr.sin_family = AF_INET;

socketaddr.sin_addr.s_addr = inet_addr(buffer);

socketaddr.sin_port = htons(80);

 

if (connect(clientSocket, (LPSOCKADDR)&socketaddr, sizeof(socketaddr)) == SOCKET_ERROR)

printf(“Socket connecting FAILED!\n”);

else

printf(“Socked connecting is successful.\n”);

 

send(clientSocket, request, (int)strlen(request), 0);

 

printf(“\n”);

 

while (1)

{

ZeroMemory(buffer, sizeof(buffer));

 

if (recv(clientSocket, buffer, sizeof(buffer) – 1, 0) <= 0)

break;

 

printf(“%s”, buffer);

}

 

printf(“\n\n”);

 

closesocket(clientSocket);

 

if (WSACleanup())

printf(“Winsock cleanup FAILED!\n”);

else

printf(“Winsock cleanup is successful.\n”);

 

system(“pause”);

 

return;

}

В строчке

socketaddr.sin_addr.s_addr = inet_addr(buffer);

функцией inet_addr() преобразовывается адрес в текстовом виде во внутреннее представление.

Аргументы функции connect() аналогичны аргументам функции bind(): сокет, указатель на структуру с адресом и портом, размер структуры.

Следует заметить, что функция recv() – блокирующая. Это означает, что управление программе не вернется до получения данных (аналогично accept()) и сложные клиентские программы следует реализовать по принципу рассмотренного сервера – принимать данные, исключительно при обработке соответствующего сообщения (FD_READ). Результат работы программы при подключении к финальной версии предложенного сервера показан на рисунке 2.4.9.

Рисунок 2.4.9 – Работа клиента в консольном режиме

Таким образом, в этом разделе рассмотрены:

  1. основные понятия, необходимые для разработки программ, обеспечивающих сетевое взаимодействие электронных устройств: сетевые модели, протоколы, адреса, порты;
  2. важнейшие функции Беркли, обеспечивающие приложений в сети;
  3. принципы построения серверных программ на языке программирования Си;
  4. принципы построения клиентских программ на языке программирования Си.

Приведенные примеры программ работают на 80 порте, на практике необходимо выбирать неиспользуемый другими программами порт.

Следует отметить, что в коде приведенных программ достаточно большое количество условных операторов: при написании сетевых программ следует уделять пристальное внимание результатам выполнения функций, а также принимаемым по сети данным. Ошибки могут возникать на любом этапе работы. Например, функция bind() вернет ошибку, если желаемый порт уже занят. Это может быть из-за запущенного другого экземпляра сервера или программы фильтрации HTTP трафика (антивируса). В случае подобных программ, ошибки могут привести к серьезным нарушениям в сетевой безопасности и привести к уязвимостям данных и оборудования.

 

Контрольные вопросы

 

  1. В чем отличие между сетевым протоколом и сетевой моделью? Зачем сетевые модели имеют многоуровневую структуру?
  2. Какое назначение портов в модели TCP/IP? Могут ли две программы, запущенных на одном компьютере, прослушивать в ожидании данных один порт?
  3. Что такое сервер?
  4. Какое назначение DNS-сервера? Где обычно он располагается? Что произойдет если такой сервер выйдет из строя?
  5. Можно ли передавать информацию между устройствами по сети, если им не назначены IP-адреса?
  6. Вызов каких функций Беркли необходим для прослушивания порта 80? Почему при указании порта сокету запись socketaddr.sin_port = 80; была бы ошибочной?
  7. После установки сокета в режим прослушивания, как программе среагировать на подключение клиента?
  8. Какую стандартную программу Windows можно использовать в качестве универсального клиента при тестировании разработанных серверных приложений?
  9. Почему не следует использовать декриптор сокета полученный сервером при подключении клиента в процессе обмена данными?



Поделиться:




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

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


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