Разработка сетевых приложений
Модель OSI
Чтобы понять все принципы взаимодействия компьютеров на расстоянии, надо знать так называемую модель OSI (ISO OSI == International Organization for Standardization Open System Interconnection - Взаимодействие Открытых Систем по Стандарту Международной Организации по Стандартизации).
Чтобы понять все принципы взаимодействия компьютеров на расстоянии, надо знать так называемую модель OSI (ISO OSI == International Organization for Standardization Open System Interconnection - Взаимодействие Открытых Систем по Стандарту Международной Организации по Стандартизации).
Вот эти уровни:
7. Прикладной
Это уровень, максимально приближенный к пользовательскому интерфейсу. Пользователи конечного программного продукта не волнует, как передаются данные, зачем и через какое место... Они сказали "ХОЧУ!" - а мы, программисты, должны им это обеспечить. В качестве примера можно взять на рассмотрение любую сетевую игру: для игрока она работает на этом уровне. Пользователь куда то ткнул, в интерфейсной части программы зафиксирована его команда. Что надо передать? Что то приняли, что произошло в мире игры?
6. Представительский
Здесь программист имеет дело с данными, полученными от низших уровней. В основном, это конвертирование и представление данных в удобоваримом для пользователя виде.
5. Сеансовый
Этот уровень позволяет пользователям осуществлять "сеансы связи". То есть именно на этом уровне передача пакетов становится для программиста прозрачной, и он может, не задумываясь о реализации, непосредственно передавать данные, как цельный поток. Здесь на сцену вступают протоколы HTTP, FTP, Telnet, SMTP и т.д.
4. Транспортный
Осуществляет контроль над передачей данных (сетевых пакетов). То есть, проверяет их целостность при передаче, распределяет нагрузку и т.д. Этот уровень реализует такие протоколы, как TCP, UDP и т.д. Для нас представляет наибольший интерес.
3. Сетевой
Логически контролирует адресацию в сети, маршрутизацию и т.д. Должен быть интересен разработчикам новых протоколов и стандартов. На этом уровне реализованы протоколы IP, IPX, IGMP, ICMP, ARP. В основном, управляется драйверами и операционными системами. Сюда влезать, конечно, стоит, но только когда ты знаешь, что делаешь, и полностью в себе уверен.
2. Канальный
Этот уровень определяет, как биты собираются в пакеты, какую служебную информацию надо передавать и как на неё реагировать, но поле данных каждого пакета максимально сырое и представляет из себя просто биты в навал, нет вообще ни какой последовательности пакетов в длинных информационных потоках. В поле данных просто вложены пакеты сетевых протоколов, всё, что чего не хватает должно быть сделано в них, или ещё выше.
1. Аппаратный (Физический)
Контролирует передачи представление битов и служебных сигналов физическими процессами и отправку физических сигналов между аппаратными устройствами, входящими в сеть. То есть управляет передачей электронов по проводам. Нас он не интересует, потому что все, что находится на этом уровне, контролируется аппаратными средствами (реализация этого уровня - это задача производителей хабов, мультиплексоров, повторителей и другого оборудования). Мы не физики-радиолюбители, а геймдевелоперы.
Итак, подведем небольшой итог к тому, что было представлено... Мы видим, что, чем выше уровень - тем выше степень абстракции от передачи данных, к работе с самими данными. Это и есть смысл всей модели OSI: поднимаясь все выше и выше по ступенькам ее лестницы, мы все меньше и меньше заботимся о том, как данные передаются, мы все больше и больше становимся заинтересованными в самих данных, нежели в средствах для их передачи. Каждый следующий уровень скрывает в себе предыдущий, облегчая жизнь пользователю этого уровня, будь он программист, радиоинженер или твоя подруга, которая не знает, как настроить MS Outlook Express...
Нас, как программистов, интересуют уровни 3, 4 и 5. Мы должны использовать средства, которые они предоставляют, для того чтобы построить 6 и 7 уровни, с которыми смогут работать конечные пользователи.
Сокеты
У каждой современной операционной системы есть средства для взаимодействия с другими компьютерами. Самым распространенным среди программистов средством для упомянутых целей являются сокеты. Сокеты - это API (Application Programming Interface - Интерфейс Программирования Приложений) для работы с уровнями OSI. Сокеты настолько гибки, что позволяют работать почти с любым из уровней модели OSI. Хочешь - формируй IP-пакеты руками и займись хакингом, отправляя "неправильные" пакеты, которые будут вводить сервера в ступор, хочешь - займись более благоразумным делом и создай новый удобный голосовой чат, хочешь - игрульку по сети гоняй, не хочешь - твое право, но этот случай мы в данном руководстве не рассматриваем... :)
Когда мы создаем сокет (socket - гнездо), мы получаем возможность доступа к нужному нам уровню OSI. Ну а дальше мы можем использовать соответствующие вызовы для взаимодействия с ним. Когда мы создаем сокет, мы также заставляем систему организовать два канала: входящий (это как громкоговоритель у телефона) и исходящий (микрофон). Осуществляя чтение и запись в эти каналы, мы приказываем системе взять на себя дальнейшую судьбу данных, т.е. передать и проследить, чтоб данные дошли вовремя, в нужной последовательности, не искаженные и т.п. Система должна давать (и дает) максимум гарантий (для каждого уровня OSI - гарантии свои), что данные будут переданы правильно. Наша задача - поместить их в очередь, а на другом конце - прочитать из входящей очереди и обработать должным образом. В каждой ОС степень поддержки сокетов разная, но точно могу сказать: в современных операционных системах MS и *nix - сокеты поддерживаются настолько, насколько нам, они могут понадобиться.
Введение в Windows Sockets API C++
Выделяют клиентские и слушающие (серверные) сокеты. Различия между ними очевидны: клиентские подключаются к процессу (к слушающему сокету), слушающие сокеты, соответственно, обрабатывают эти подключения. Передача данных между процессами происходит через клиентские сокеты.
Общая схема работы с сокетами в Windows
Клиент
• Инициализация WSA
• Создание сокета
• Присоединение к серверу
• Прием/передача данных
• Разрыв соединения
Сервер
• Инициализация WSA
• Создание слушающего сокета и привязка к порту
• Прослушивание порта
• Обработка входящих подключений
• Прием/передача данных
• Разрыв соединения
Пример. Клиентское и серверное приложение
КЛИЕНТ:
#include < winsock2.h > #include < ws2tcpip.h > #include < iostream > #define ip "127.0.0.1" #define port 27015 #define bufsize 256 #pragma comment(lib, "Ws2_32.lib") int main() { setlocale(LC_ALL, "Russian"); //Инициализация WSA WSADATA wsaData; int result; result = WSAStartup(MAKEWORD(2, 2), &wsaData); if (result != 0) { std::cout << "Ошибка WSAStartup: " << result << std::endl; return 1; } //Создание сокета SOCKET clientSocket = INVALID_SOCKET; clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (clientSocket == INVALID_SOCKET) { std::cout << "Ошибка socket(): " << WSAGetLastError() << std::endl; WSACleanup(); return 1; } //Присоединение к серверу sockaddr_in clientService; clientService.sin_family = AF_INET; clientService.sin_addr.s_addr = inet_addr(ip); clientService.sin_port = htons(port); result = connect( clientSocket, reinterpret_cast< SOCKADDR* >(&clientService), sizeof(clientService) ); if (result != 0) { std::cout << "Ошибка в connect(): " << WSAGetLastError() << std::endl; WSACleanup(); return 1; } //Передача данных серверу char data[] = "Test"; result = send(clientSocket, data, static_cast< int >(strlen(data)), 0); if (result < 0) { std::cout << "Ошибка в send(): " << WSAGetLastError() << std::endl; return 1; } //Прием данных от сервера char buf[bufsize]; int r; do { r = recv(clientSocket, buf, bufsize, 0); if (r > 0) std::cout << "Приянтно " << r << " байт" << std::endl; else if (r == 0) std::cout << "Соединение разорвано" << std::endl; else std::cout << "Ошибка в recv(): " << WSAGetLastError() << std::endl; } while (r > 0); //Разрыв соединения closesocket(clientSocket); // Если работа с сокетами больше не предполагается вызываем WSACleanup() WSACleanup(); return 0; }
СЕРВЕР:
#include < winsock2.h > #include < ws2tcpip.h > #include < iostream > #define DEFAULT_PORT "27015" #define DEFAULT_BUFLEN 512 #pragma comment(lib, "Ws2_32.lib") int main() { setlocale(LC_ALL, "Russian"); //Инициализация WSA int iResult; WSAData d; iResult = WSAStartup(MAKEWORD(2, 2), &d); if (iResult != 0) { std::cout << "Error at WSAStartup: " << iResult; return 1; } //Подготовка данных для создания сокета 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; } //Создание сокета 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; } //Связывание сокета с сетвевым адресом 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); //Прослушивание подключений if (listen(listenSocket, SOMAXCONN) == SOCKET_ERROR) { std::cout << "Listen failed with error: " << WSAGetLastError(); closesocket(listenSocket); WSACleanup(); return 1; } //Обработка запроса на подключение 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; } //Обмен данными 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); }