Входящие подключения со стороны сторонних приложений обрабатываются через специальный серверный (слушающий) сокет, который создается на стороне сервера с привязкой к конкретному порту.
Примером программы, использующей серверные сокеты, может служить приложение, обрабатывающие некую телеметрическую информацию, поступающую от дочерних приложений-клиентов. Приложение-сервер создает слушающий сокет с привязкой к конкретному порту и уходит в режим ожидания подключений (можно создать отельный поток для ожидания подключений, чтобы программа "не висла"). Приложение-клиент подключается к слушающему сокету, после чего можно начинать обмен данными.
Инициализация WSA
int iResult; WSAData d; iResult = WSAStartup( MAKEWORD(2, 2), &d); if(iResult != 0) { std::cout << "Error at WSAStartup: " << iResult; return 1; }
Как я уже писал в предыдущей статье, перед началом работы с сокетами необходимо провести инициализацию Windows Sockets API.
Подготовка данных для создания сокета
#define DEFAULT_PORT "27015" struct addrinfo *result = NULL, *ptr = NULL, hints; ZeroMemory(&hints, sizeof (hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; hints.ai_flags = AI_PASSIVE; iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result); if (iResult != 0) { std::cout << "Ошибка getaddrinfo: " << iResult; WSACleanup(); return 1; }
Функция getaddrinfo используется для определения значений в структуре sockaddr.
- AF_INET указывает, что используется четвертая версия IP протокола.
- SOCK_STREAM используется для определения потока сокета.
- IPPROTO_TCP используется для определения TCP протокола.
- 270015 это номер порта, к которому будет подключаться клиент.
Создание сокета
SOCKET listenSocket = INVALID_SOCKET; listenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol); if( listenSocket == INVALID_SOCKET ) { std::cout << "Error at socket(): " << WSAGetLastError(); freeaddrinfo(result); WSACleanup(); return 1; }
Для создания сокета вызываем функцию socket с данными, подготовленными в предыдущем шаге, если создание пройдет успешно, то функция вернет дескриптор сокета.
Связывание сокета с сетвевым адресом
iResult = bind(listenSocket, result->ai_addr, result->ai_addrlen); if(iResult == SOCKET_ERROR) { std::cout << "Bind failed with error: " << WSAGetLastError(); freeaddrinfo(result); closesocket(listenSocket); WSACleanup(); return 1; } freeaddrinfo(result);
После того, как мы создали сокет, его необходимо связать с конкретным сетевым адресом. Для этого
надо вызвать функцию bind и передать ей в качестве аргументов дескриптор сокета и данные из структуры sockaddr.
Информация об адресе, хранимая в переменной result, больше не требуется, поэтому мы можем ее удалить,
используя функцию freeaddrinfo.
Прослушивание подключений
if( listen(listenSocket, SOMAXCONN) == SOCKET_ERROR) { std::cout << "Listen failed with error: " << WSAGetLastError(); closesocket(listenSocket); WSACleanup(); return 1; }
Для прослушивания входящих подключений надо вызывать функцию listen, в аргументах передать созданный сокет и максимальную длину очереди клиентов ожидающих подключения. В примере использована константа SOMAXCONN, указывающая разумную максимальную длину очереди для текущей системы.
Обработка запроса на подключение
SOCKET ClientSocket; ClientSocket = INVALID_SOCKET; ClientSocket = accept(listenSocket, NULL, NULL); if (ClientSocket == INVALID_SOCKET) { std::cout << "Accept failed with error: " << WSAGetLastError(); closesocket(listenSocket); WSACleanup(); return 1; }
Обработка запроса на подключение заключается в создании клиентского сокета для текущего входящего подключения. Есть несколько методов обработки запросов, один из них заключается в том, чтобы создать непрерывный цикл, в котором проверяется наличия входящих подключений и последующая обработка запроса в текущем процессе, либо в отдельном потоке. В первом случае, программа не сможет обработать другие подключения, пока не обработается текущее подключение. В нашем случае используется тривиальный способ обработки единственного запроса, без использования потоков и циклов.
Обмен данными
#defin DEFAULT_BUFLEN 512 char recvbuf[DEFAULT_BUFLEN]; int iSendResult; int recvbuflen = DEFAULT_BUFLEN; do { // Принимаем данные от клиента iResult = recv(ClientSocket, recvbuf, recvbuflen, 0); if (iResult > 0) { std::cout << "Принято " << iResult << " байт" << std::endl; // Отправляем данные, принтые от клиента, обратно iSendResult = send(ClientSocket, recvbuf, iResult, 0); if (iSendResult == SOCKET_ERROR) { std::cout << "Send failed with error: " << WSAGetLastError(); closesocket(ClientSocket); WSACleanup(); return 1; } std::cout << "Отправлено " << iSendResult << " байт"; } else if (iResult == 0) std::cout << "Соединение закрыто..." << std::endl; else { std::cout << "Ошибка recv " << WSAGetLastError(); closesocket(ClientSocket); WSACleanup(); return 1; } } while (iResult > 0);
Создав клиентский сокет для входящего подключения, можно начать обмен данными используя функции recv и send. Пример демонстрирует примерную технологию обмена данными между двумя процессами, она ничем не отличается от технологии обмена данными на стороне клиента.
Заключение
Это заключительная теоретическая часть из серии статей посвященных введению в сетевое программирование в среде Windows. В последующих, практических статьях, я покажу как можно создать простое сетевое приложение.
Дополнительную информацию о функциях, структурах WSA можно найти в
MSDN: Winsock reference.
Введение в WSA
Комментариев нет:
Отправить комментарий