Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
чтиво_ч3.doc
Скачиваний:
4
Добавлен:
15.11.2019
Размер:
435.2 Кб
Скачать

Создание socket’а

Сведущий шаг – создание socket’а, к которому будем подключаться. Это делается довольно просто, всего лишь вызываем функцию socket с нужными параметрами:

cout << "Creating socket... ";

if ((hSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)

throw HeadReqException("could not create socket.");

cout << "created.\n";

Если socket создать не получится, то ему будет присвоено значение INVALID_SOCKET. В этом случае никаких дальнейших действий не произойдет, а в ход пойдет та, часть кода, которая находится сразу за блоком catch.

if (hSocket!=INVALID_SOCKET)

{

closesocket(hSocket);

}

Заметьте, функция closesocket будет вызвана в любом случае, вне зависимости от того как завершилась функция socket.

Подключение socket’а

Теперь, когда у нас есть socket, мы можем подключить его к удаленному серверу. Для этого воспользуемся функцией connect. Функция требует структуру sockaddr_in, которую мы заполнили ранее функцией FillSockAddr. Если возвращаемое значение функции не равно нулю, значит в соединении произошла ошибка и нам об этом будет сообщено. Если соединение произошло успешно, на консоли появится строка с адресом и портом сервера. Функция inet_ntoa используется для преобразования IP адреса, состоящего из чисел, в строку, разделенную точками.

// Соединяемся с сервером

cout << "Attempting to connect to " << inet_ntoa(sockAddr.sin_addr)

<< ":" << SERVER_PORT << "... ";

if (connect(hSocket, reinterpret_cast<sockaddr*>(&sockAddr), sizeof(sockAddr))!=0)

throw HeadReqException("could not connect.");

cout << "connected.\n";

Отправляем запрос

Когда socket соединен с сервером можно смело отправлять запрос. Вызов функции send довольно прост: каждый вызов отправляет определенное количество байт из буфера. Функция send заблокирует нашу программу, пока не передаст все данные или функция не завершится неудачно.

cout << "Sending request... ";

 

// Отправляем запрос part 1

if (send(hSocket, HEAD_REQUEST_PART1, sizeof(HEAD_REQUEST_PART1)-1, 0)==SOCKET_ERROR)

throw HeadReqException("failed to send data.");

 

// Отправляем имя хоста

if (send(hSocket, pServername, lstrlen(pServername), 0)==SOCKET_ERROR)

throw HeadReqException("failed to send data.");

 

// Отправляем запрос part 2

if (send(hSocket, HEAD_REQUEST_PART2, sizeof(HEAD_REQUEST_PART2)-1, 0)==SOCKET_ERROR)

throw HeadReqException("failed to send data.");

 

cout << "request sent.\n";

Обратите внимание, что мы отправляем данных на 1 байт меньше, чем размер буфера. Дело в том, что нам не надо отправлять завершающий нуль-терминатор.

Получаем ответ

Заключительный шаг программы – это получение ответа от сервера и вывод его на экран, пока сервер не закроет соединение. HTTP заголовок "Connection: close" в нашем запросе говорит серверу о том, что он должен закрыть соединение после того, как отправит ответ. Данные от сервера мы получаем с помощью функции recv. Т.к. мы просто выводим на экран полученные данные, а не обрабатываем их каким-то образом, то нам достаточно вызывать функцию recv пока соединение не будет закрыто. Функция recv также будет блокировать нашу программу, до тех пор, пока мы не получим все данные или функция не завершится ошибкой.

cout << "Dumping received data...\n\n";

// Выводим все данные в цикле

while(true)

{

int retval;

retval = recv(hSocket, tempBuffer, sizeof(tempBuffer)-1, 0);

if (retval==0)

{

break; // Соединение было закрыто

}

else if (retval==SOCKET_ERROR)

{

throw HeadReqException("socket error while receiving.");

}

else

{

// retval - количество полученных байт

// Завершаем буфер нулем и выводим строку

tempBuffer[retval] = 0;

cout << tempBuffer;

}

}

В переменную tempBuffer мы будем помещать полученные данные. Обратите внимание, что получаем мы на 1 байт меньше, чем может в себя вместить tempBuffer. Дело в том, что нам придется завершить tempBuffer нуль-терминатором. Вообще, мы вполне могли получить 0 байт, поскольку данные полученные через TCP/IP не ограничиваются только текстом. Вы должны будете обработать эти данные как двоичные. Однако HTTP не позволяет возвращать в ответном сообщении ноль байт, поэтому мы не можем получить данные размером в 0 байт. Но если вдруг такое случится, то будет напечатана пустая строка (строка размером в 0 байт будет ошибочно принята за нуль-терминатор).

Очищаем socket

В конце концов socket будет закрыт (если он был создан), а функция RequestHeaders возвратит true или false, в зависимости от того как она была завершена (успешно или нет). Возвращаясь в главную функцию winsock будет очищен (WSACleanup) и будет выведено последнее сообщение.

Закончили

Хех…=) Не стоит так радоваться, мы закончили писать программу, а не статью. Я выкладываю ихсодник. В архиве находится .cpp файл с кодом и .exe файл. Если вы захотите проверить работу программы, написанной мной, то .exe файл необходимо запустить в консоли Windows (Win+R -> cmd -> e:\\ Head_Request_Client.exe)

Сервер

Теперь, когда Вы узнали как работает клиент, пришло время ознакомится с реализацией сервера, построенного на блокирующих socket’ах. Сейчас Вы узнаете как создать сервер, которые кодирует полученные данные с помощью ROT13 и отправляет их обратно.

Ход работы программы

-сервер создает socket -socket связывается с адресом -socket устанавливается в режим прослушки -при запросе соединения, сервер разрешает соединение и делает клиентский socket доступным -полученная от клиента информация (каждый байт) кодируется с помощью ROT13 и отправляется обратно -когда клиент закрывает соединение, завершаем программу

База

Основа программы почти такая же, как и в клиентской части, за исключением того, что класс HeadReqException переменован в ROTException и добавлена пара дополнительных библиотек. Кстати, не забудьте настроить проект так же, как и клиентскую часть.

#include <iostream>

#include <string>

#include <sstream>

 

#include <winsock2.h>

#include <windows.h>

 

using namespace std;

 

class ROTException

{

public:

ROTException() :

m_pMessage("") {}

virtual ~ROTException() {}

ROTException(const char *pMessage) :

m_pMessage(pMessage) {}

const char * what() { return m_pMessage; }

private:

const char *m_pMessage;

};

 

int _tmain(int argc, _TCHAR* argv[])

{

//тут код

}

Константы и глобальные переменные

В программе используется три константы: порт сервера по-умолчанию (4444), требуемая версия WinSock и буфер.

const int REQ_WINSOCK_VER = 2; // Минимальная требуемая версия WinSock

const int DEFAULT_PORT = 4444;

const int TEMP_BUFFER_SIZE = 128;

Главная функция

Эта функция имеет много общего с функцией main клиентской части.

int _tmain(int argc, _TCHAR* argv[])

{

WSADATA wsaData;

 

cout << "Initializing winsock... ";

 

if (WSAStartup(MAKEWORD(REQ_WINSOCK_VER,0), &wsaData)==0)

{

// Проверяем подходит ли нам максимально доступная версия WinSock

if (LOBYTE(wsaData.wVersion) >= REQ_WINSOCK_VER)

{

cout << "initialized.\n";

 

int port = DEFAULT_PORT;

if (argc > 1)

port = atoi(argv[1]);

 

RunServer(port);

}

else

{

cerr << "required version not supported!";

}

 

cout << "Cleaning up winsock... ";

 

// Очищаем WinSock

if (WSACleanup()!=0)

{

cerr << "cleanup failed!\n";

}

cout << "done.\n";

}

else

{

cerr << "startup failed!\n";

}

return 0;

}

WinSock инициализирован и очищен (освобожден) в конце программы. Между инициализацией и освобождением WinSock код запуска сервера. Сервер запускается с номером порта по-умолчанию (4444) функцией RunServer (она будет описана далее) с номером порта, в качестве параметра.

RunServer

RunServer – функция, в которой сервер запускается и принимает соединения:

bool RunServer(int portNumber)

{

SOCKET hSocket = INVALID_SOCKET,

hClientSocket = INVALID_SOCKET;

bool bSuccess = true;

sockaddr_in sockAddr = {0};

 

try

{

// Создаем socket

cout << "Creating socket... ";

if ((hSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)

throw ROTException("could not create socket.");

cout << "created.\n";

 

// Связывае socket

cout << "Binding socket... ";

SetServerSockAddr(&sockAddr, portNumber);

if (bind(hSocket, reinterpret_cast<sockaddr*>(&sockAddr), sizeof(sockAddr))!=0)

throw ROTException("could not bind socket.");

cout << "bound.\n";

 

// Устанавливаем socket в режим прослушивания

cout << "Putting socket in listening mode... ";

if (listen(hSocket, SOMAXCONN)!=0)

throw ROTException("could not put socket in listening mode.");

cout << "done.\n";

 

// Ждем соединений

cout << "Waiting for incoming connection... ";

 

sockaddr_in clientSockAddr;

int clientSockSize = sizeof(clientSockAddr);

 

// Принимаем соединение:

hClientSocket = accept(hSocket,

reinterpret_cast<sockaddr*>(&clientSockAddr),

&clientSockSize);

 

// Проверяем успешно ли принято соединение

if (hClientSocket==INVALID_SOCKET)

throw ROTException("accept function failed.");

cout << "accepted.\n";

 

// Обрабатываем соединение:

HandleConnection(hClientSocket, clientSockAddr);

 

}

catch(ROTException e)

{

cerr << "\nError: " << e.what() << endl;

bSuccess = false;

}

 

if (hSocket!=INVALID_SOCKET)

closesocket(hSocket);

 

if (hClientSocket!=INVALID_SOCKET)

closesocket(hClientSocket);

 

return bSuccess;

}

Socket для сервера создается обычным способом, а так же резервируется переменная, содержащая socket для клиента. Socket для клиента мы не создаем, позже WinSock это сделает за нас. Оба socket’а закрываются, когда они нам больше не нужны.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]