Программирование на C++ глазами хакера

Пример работы TCP-клиента


Сервер готов, теперь можно приступить к написанию клиентской части. Для этого создайте новый проект Win32 Project и назовите его TCPClient.

Найдите функцию _tWinMain и до цикла обработки сообщений добавьте следующий код:

WSADATA wsd; if (WSAStartup(MAKEWORD(2,2), wsd) != 0) { MessageBox(0, "Can't load WinSock", "Error", 0); return 0; }

HANDLE hNetThread; DWORD dwNetThreadId; hNetThread = CreateThread(NULL, 0, NetThread, 0, 0, dwNetThreadId);

Здесь также загружается библиотека WinSock версии 2.2, хотя функции будут использоваться только из первой версии и достаточно было бы ее. Но я напоминаю, что в более новой версии могут быть исправлены какие-то ошибки, и в учебных целях я решил использовать эту версию.

Как и в случае с сервером, для работы с сетью будет использоваться отдельный поток, но для клиента достаточно только одного. Он также создается функцией CreateThread, а в качестве третьего параметра передается имя функции, которая будет выполняться в отдельном потоке — NetThread. Ее еще нет в созданном проекте, поэтому давайте введем сейчас. Добавьте до функции _tWinMain код из листинга 4.13.

Листинг 4.13. Поток работы с сетью
DWORD WINAPI NetThread(LPVOID lpParam) { SOCKET sClient; char szBuffer[1024]; int ret, i; struct sockaddr_in server; struct hostent *host = NULL; char szServerName[1024], szMessage[1024];

strcpy(szMessage, "get"); strcpy(szServerName, "127.0.0.1");

// Создание сокета sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sClient == INVALID_SOCKET) { MessageBox(0, "Can't create socket", "Error", 0); return 1; } // Заполнение структуры с адресом сервера и номером порта server.sin_family = AF_INET; server.sin_port = htons(5050); server.sin_addr.s_addr = inet_addr(szServerName);



// Если указано имя, то перевод символьного адреса сервера в IP if (server.sin_addr.s_addr == INADDR_NONE) { host = gethostbyname(szServerName); if (host == NULL) { MessageBox(0, "Unable to resolve server", "Error", 0); return 1; } CopyMemory(server.sin_addr, host-h_addr_list[0], host-h_length); } // Соединение с сервером if (connect(sClient, (struct sockaddr *)server, sizeof(server)) == SOCKET_ERROR) { MessageBox(0, "connect failed", "Error", 0); return 1; }


// Отправка и прием данных ret = send(sClient, szMessage, strlen(szMessage), 0); if (ret == SOCKET_ERROR) { MessageBox(0, "send failed", "Error", 0); }

// Задержка Sleep(1000);

// Получение данных char szRecvBuff[1024]; ret = recv(sClient, szRecvBuff, 1024, 0); if (ret == SOCKET_ERROR) { MessageBox(0, "recv failed", "Error", 0); } MessageBox(0, szRecvBuff, "Recived data", 0); closesocket(sClient); }

Давайте подробно рассмотрим, что здесь происходит. В переменной szMessage хранится текст сообщения, которое отправляется серверу. Для примера жестко определена строка "get". В переменной szServerName указывается адрес сервера, с которым нужно произвести соединение. В данном случае установлен адрес 127.0.0.1, что соответствует локальному компьютеру. Это значит, что серверная и клиентская программы должны запуститься на одном и том же компьютере. После этого создается сокет так же, как и при создании сервера.

Следующим этапом надо подготовить структуру типа sockaddr_in (в нашем случае это структура server), в которой нужно указать семейство протоколов, порт (у сервера мы использовали 5050) и адрес сервера.

В примере указан IP-адрес, но в реальной программе у вас может быть и имя удаленного компьютера, которое нужно привести к IP. Именно поэтому адрес проверяется на равенство константе INADDR_NONE:

if (server.sin_addr.s_addr == INADDR_ NONE)

Если условие выполняется, то в качестве адреса указано символьное имя, и тогда с помощью функции gethostbyname выполняется преобразование в IP-адрес. Результат записывается в переменную типа hostent. Как я уже говорил, компьютер может иметь несколько адресов, тогда результатом будет массив структур типа hostent. Чтобы не усложнять задачу, просто возьмите первый адрес, который можно получить так: host-h_addr_list[0].

Теперь все готово к соединению с сервером. Для этого будет использоваться функция connect. Ей указывается созданный сокет, структура с адресом и размер структуры. Если функция вернет значение, отличное от SOCKET_ERROR, т о соединение прошло успешно, иначе произошла ошибка.



Следующим этапом отправляются данные серверу с помощью функции send. Вроде бы все отправлено, и сервер должен ответить, но не стоит торопиться читать данные из буфера, потому что на передачу и обработку сервером информации нужно время. Если сразу после отправки попробовать вызвать функцию recv, то мы скорей всего получим ошибку, потому что данные еще не поступили. Именно поэтому после функции send нужно сделать задержку.

В реальной программе задержку делать не стоит, потому что можно поступить другим способом, например, запустить цикл получения сообщения и ожидать, пока функция recv вернет данные, а не ошибку. Это самый простой способ, который в данном случае будет работать корректно и достаточно эффективно.

Для компиляции проекта, как в случае с сервером, необходимо подключить модуль winsock2.h и библиотеку ws2_32.lib.

Примечание
Исходный код примера, описанного в этом разделе, вы можете найти на компакт - диске в каталоге \Demo\Chapter4\TCPCIient.

Содержание раздела