Сегодня мы рассмотрим пример программирования сокетов Python. Мы создадим серверные и клиентские приложения на Python.
Программирование сокетов
Чтобы понять программирование сокетов Python, нам нужно знать о трех интересных темах – Socket Server, Socket Client и Socket.
Итак, что такое сервер? Сервер – это программное обеспечение, которое ожидает запросов клиентов и обслуживает или обрабатывает их соответственно.
С другой стороны, клиент запрашивает эту услугу. Клиентская программа запрашивает некоторые ресурсы к серверу, и сервер отвечает на этот запрос.
Socket – это конечная точка двунаправленного канала связи между сервером и клиентом. Сокеты могут обмениваться данными внутри процесса, между процессами на одной машине или между процессами на разных машинах. Для любого взаимодействия с удаленной программой мы должны подключаться через порт сокета.
Основная цель этого руководства по программированию сокетов – познакомить вас с тем, как сервер сокетов и клиент взаимодействуют друг с другом. Вы также узнаете, как написать программу сервера сокетов в Python.
Сокеты в программировании. Пишем свой сервер и клиент.
Пример
Ранее мы говорили, что клиент сокета запрашивает некоторые ресурсы у сервера, и сервер отвечает на этот запрос.
Итак, мы разработаем и серверную, и клиентскую модель, чтобы каждый мог общаться с ними. Шаги можно рассматривать так:
- Программа сервера сокетов запускается сначала и ждет любого запроса.
- Клиентская программа сначала инициирует диалог.
- Затем серверная программа будет реагировать на запросы клиента соответственно.
- Клиентская программа будет завершена, если пользователь введет сообщение «до свидания». Серверная программа также завершится, когда завершится клиентская программа, это необязательно, и мы можем поддерживать выполнение серверной программы на неопределенный срок или завершить работу с помощью какой-либо конкретной команды в клиентском запросе.
Сервер сокетов
Веб-сервер на C++ и сокетах
Создадим HTTP-сервер, который обрабатывает запросы браузера и возвращает ответ в виде HTML-страницы.
- Введение в HTTP
- Что будет делать сервер?
- О сокетах
- Создание сокета
- Привязка сокета к адресу (bind)
- Подготовка сокета к принятию входящих соединений (listen)
- Ожидание входящего соединения (accept)
- Получение запроса и отправка ответа
- Последовательная обработка запросов
Введение в HTTP
Для начала разберемся, что из себя представляет HTTP. Это текстовый протокол для обмена данными между браузером и веб-сервером.
Пример HTTP-запроса:
GET /page.html HTTP/1.1 Host: site.com
Первая строка передает метод запроса, идентификатор ресурса (URI) и версию HTTP-протокола. Затем перечисляются заголовки запроса, в которых браузер передает имя хоста, поддерживаемые кодировки, cookie и другие служебные параметры. После каждого заголовка ставится символ переноса строки rn .
Socket или как создать собственный сервер на Python в домашних условиях #1 | Базовый курс Python
У некоторых запросов есть тело. Когда отправляется форма методом POST, в теле запроса передаются значения полей этой формы.
Иногда необходимо передать данные в другом формате. Например, при загрузке файлов на сервер, бинарные данные кодируются методом «multipart/form-data».
Сервер обрабатывает запрос клиента и возвращает ответ.
Пример ответа сервера:
HTTP/1.1 200 OK Host: site.com Content-Type: text/html; charset=UTF-8 Connection: close Content-Length: 21
Test page.
В первой строке ответа передается версия протокола и статус ответа. Для успешных запросов обычно используется статус «200 OK». Если ресурс не найден на сервере, возвращается «404 Not Found».
Тело ответа так же, как и у запроса, отделяется от заголовков одной пустой строкой.
Полная спецификации протокола HTTP описывается в стандарте rfc-2068. По понятным причинам, мы не будем реализовывать все возможности протокола в рамках этого материала. Достаточно реализовать поддержку работы с заголовками запроса и ответа, получение метода запроса, версии протокола и URL-адреса.
Что будет делать сервер?
Сервер будет принимать запросы клиентов, парсить заголовки и тело запроса, и возвращать тестовую HTML-страничку, на которой отображены данные запроса клиента (запрошенный URL, метод запроса, cookie и другие заголовки).
О сокетах
Для работы с сетью на низком уровне традиционно используют сокеты. Сокет — это абстракция, которая позволяет работать с сетевыми ресурсами, как с файлами. Мы можем писать и читать данные из сокета почти так же, как из обычного файла.
В этом материале мы будем работать с виндовой реализацией сокетов, которая находится в заголовочном файле . В Unix-подобных ОС принцип работы с сокетами такой же, только отличается API. Вы можете подробнее почитать о сокетах Беркли, которые используются в GNU/Linux.
Создание сокета
Создадим сокет с помощью функции socket , которая находится в заголовочном файле . Для работы с IP-адресами нам понадобится заголовочный файл .
#include #include #include // Для корректной работы freeaddrinfo в MinGW // Подробнее: http://stackoverflow.com/a/20306451 #define _WIN32_WINNT 0x501 #include #include // Необходимо, чтобы линковка происходила с DLL-библиотекой // Для работы с сокетам #pragma comment(lib, «Ws2_32.lib») using std::cerr; int main() < // служебная структура для хранение информации // о реализации Windows Sockets WSADATA wsaData; // старт использования библиотеки сокетов процессом // (подгружается Ws2_32.dll) int result = WSAStartup(MAKEWORD(2, 2), // Если произошла ошибка подгрузки библиотеки if (result != 0) < cerr struct addrinfo* addr = NULL; // структура, хранящая информацию // об IP-адресе слущающего сокета // Шаблон для инициализации структуры адреса struct addrinfo hints; ZeroMemory( // AF_INET определяет, что используется сеть для работы с сокетом hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; // Задаем потоковый тип сокета hints.ai_protocol = IPPROTO_TCP; // Используем протокол TCP // Сокет биндится на адрес, чтобы принимать входящие соединения hints.ai_flags = AI_PASSIVE; // Инициализируем структуру, хранящую адрес сокета — addr. // HTTP-сервер будет висеть на 8000-м порту локалхоста result = getaddrinfo(«127.0.0.1», «8000», addr); // Если инициализация структуры адреса завершилась с ошибкой, // выведем сообщением об этом и завершим выполнение программы if (result != 0) < cerr // Создание сокета int listen_socket = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); // Если создание сокета завершилось с ошибкой, выводим сообщение, // освобождаем память, выделенную под структуру addr, // выгружаем dll-библиотеку и закрываем программу if (listen_socket == INVALID_SOCKET) < cerr // .
Как написать программу для сервера
По этой теме с сети написано немало материалов, как простых, так и сложных, но даже среди такого обилия информации разобраться в предмете непросто. Если же нужно написать сервер, который может обслуживать множество одновременных подключений, работающий быстро и надёжно, задача сильно усложняется. Почему же вопрос так труден для освоения? Мне кажется, причины следующие.
1. Нужно хорошо ориентироваться в терминологии локальных сетей, многоуровневой модели OSI (которая и так достаточно запутана — многие технологии и протоколы относятся сразу к нескольким уровням) многообразии сетевых технологий, в методах адресации — чтобы подобрать оптимальные протоколы работы приложения. Однако, наиболее распространен, без всяких сомнений, TCP/IP.
2. На каждой платформе свои обёртки к сетевому IP. Например, в MFC (Visual Studio) это CSocket, в Delphi TSocket, TServerSocket, TClientSocket. Простые примеры из help или Интернета обычно работают нормально, но если надо что-то переделать — перехватить ошибки, организовать обработку множества одновременных подключений к серверу — обёртки только усложняют задачу, и хороший/понятный пример найти уже сложнее.
3. Качественное применение обёрток подразумевает Ваше хотя бы приблизительное знакомство с базовыми функциями и принципами работы библиотеки Windows Sockets — иначе будет трудно понять и применить многие свойства и методы обёрток (связанные, например, с блокирующими функциями Windows Sockets).
4. Если Вы решили отказаться от оберток в пользу прямого вызова функций Windows Sockets (хороший выбор!) — будьте готовы к тому, что потребуются хорошие знания по написанию многопоточных приложений, методам корректного выделения и освобождения памяти, функциям обратного вызова (callback), межпотоковым взаимодействиям и т. п. Всё это потому, что многие функции Windows Sockets блокируют ход выполнения программы.
5. Для написания Winsock-приложения можно использовать пять моделей управления вводом-выводом (select, WSAAsyncSelect, WSAEventSelect, перекрытый ввод-вывод и порты завершения). Разобраться в этих технологиях и выбрать для себя одну (или несколько) может оказаться непростым делом.
Из всего вышесказанного ясно, что у “новичка в чистом виде” практически никаких шансов написать что-то путное, которое будет работать хотя бы с самыми щадящими требованиями к надежности и быстродействию. Если Вы — стреляный воробей, любите C (не купились сомнительной лёгкостью Visual Basic и Delphi), и успели слегка попробовать и то, и это (с переменным успехом), но понимания в сетевом программировании ещё не достигли, хотите чего-то большего, неплохо бы прочитать хотя бы разок хорошую книжку по предмету.
Лучшая из тех, что мне попадалась — “Программирование в сетях Windows” Э. Джонс, Д. Оланд, издательство Microsoft Press 2002 года — ни капли лишнего, максимально широко рассмотрена тема (в пределах разумного), много хороших работоспособных примеров (на CD-ROM). Самое оно. Прочитаете, и сразу полегчает, останется только написать программу. Вам пока только кажется, что все понятно, не обольщайтесь — придётся ещё не раз перечитывать в книге многие места.
Напоследок приведу по шагам алгоритм работы сервера, который может одновременно обслуживать множество клиентских подключений. Исходный код можно скачать по ссылкам [1, 2] (проекты Win32 Console Visual C++). Первая программа может соединять сессии telnet с COM-портом. Вторая — консольный сервер telnet.
Писать многопоточный сервер предлагаю в такой последовательности:
1. Нужно загрузить правильную версию библиотеки Winsock. Я пользовался версией 2.0, хотя уже существует версия 2.2. Делается это так:
WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 2, 0 ); err = WSAStartup( wVersionRequested,
Если err не равно 0, то произошла ошибка (err содержит код ошибки, который можно расшифровать подпрограммой WSA_Err_Decode). В случае ошибки завершаем программу.
2 [можно пропустить]. Проверяем версию загруженной библиотеки. Она должна равняться запрашиваемой.
if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 0 ) < WSACleanup( ); WLog («Couldn’t find a usable WinSock DLL»); //далее идёт код, который обеспечивает завершение программы по ошибке >
3 [можно пропустить]. Получение информации о том, сколько нужно памяти для опроса протоколов.
4 [можно пропустить]. WSAGetLastError() должна вернуть WSAENOBUFS.
5 [можно пропустить]. Выделение памяти для опроса протоколов.
6 [можно пропустить]. Загрузка в память информации о протоколах.
7 [можно пропустить]. Опрос всех доступных протоколов и поиск среди них нужного (в моей программе используется TCP/IP, и условие будет
(wsapi->iAddressFamily == AF_INET) (wsapi->iProtocol == IPPROTO_TCP)).
8. Создание сокета, который будет принимать соединения вызовом WSASocket. Если WSASocket вернул INVALID_SOCKET, то получаем код ошибки по WSAGetLastError и завершаем программу. Код ошибки можно расшифровать подпрограммой WSA_Err_Decode.
9. Привязка сокета к адресу (bind). Для того, чтобы сервер был привязан ко всем адресам, используется константа INADDR_ANY. Если bind вернул не 0, то произошла ошибка. Получаем код ошибки по WSAGetLastError и завершаем программу.
Код ошибки расшифровывается подпрограммой WSA_Err_Decode.
10. Перевод сокета в режим приёма входящих соединений или режим прослушивания (listen). На этом этапе задаётся максимальное число допустимых подключений. Если listen вернул не 0, то произошла ошибка. Получаем код ошибки по WSAGetLastError и завершаем программу.
Код ошибки можно расшифровать подпрограммой WSA_Err_Decode.
11. Далее запускается бесконечный цикл ожидания клиентов. В теле этого цикла присутствует блокирующий метод accept — прокрутка цикла останавливается, пока не приконнектится любой клиент. Этот метод accept возвращает дескриптор сокета, через который можно работать с соединившимся клиентом. Через этот сокет и происходит дальнейший обмен данными с клиентом.
Если наш сервер должен обслуживать несколько клиентов (а мы так и хотели), то в этом месте нужно запустить 2 потока, которые будут работать с сокетом клиента (один на приём, другой на передачу). 2 потока, а не один, нужны потому, что метод recv, который читает данные из сокета, блокирующий (есть и другие варианты, но я их рассматривать не буду, для этого есть спецлитература).
Вот и всё (?) что нужно знать, чтобы написать “всамделишный” сервер.
[Ссылки]
Источник: microsin.net