Клиент <-> Сервер (Python)Суббота, Февраль 5, 2011 г. Сегодня я хочу коснуться темы взаимодействия двух разных, а зачастую и значительно удалённых друг от друга приложений. Под взаимодействием мы будем понимать передачу каких либо данных. Примером послужит простой echo сервер на python. Задача такого сервера ожидать подключение от клиента, принимать переданные данные и отправлять их обратно. Вот сервер:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
host = "localhost"
port = 44444
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((host, port))
s.listen(5)
sock, addr = s.accept()
while True:
buf = sock.recv(1024)
if buf == "exit":
sock.send("bye")
break
elif buf:
sock.send(buf)
sock.close()
Один мой знакомый упрекнул меня в том, что я уделяю слишком мало внимания простым деталям, тем самым отпугивая «совсем новичков» кучей необъяснённого кода, который как мне кажется и так должен быть понятен. Частично я с ним согласился. Я постараюсь более подробно описывать ключевые моменты кода, но в тоже время знание основ я оставляю на совести читателя. Согласитесь, глупо браться писать и читать не выучив азбуки. Вернёмся к серверу. Подключаем модуль для работы с сокетами. Он содержит весь необходимый нам функционал. Далее мы определим хост на котором сервер будет ждать соединение и порт который он будет слушать. 9 строка — создаём сокет для Ipv4. Это далеко не единственный вариант. Для расширения кругозора советую заглянуть сюда. В 10 строке мы устанавливаем опцию повторного использования порта, чтобы не ждать пока он освободится после останова сервера. Далее биндим (ассоциируем) сокет с хостом и портом. Указываем в 12 строке количество ожидающих обработки запросов. Функция accept() переводит приложение в ожидание подключения клиента. При успешном коннекте accept возвратит кортеж (пару) из объекта соединения и адреса клиента. Полученный объект мы и будем использовать для взаимодействия с клиентом. Запускаем вечный цикл, в котором читаем из объекта отправленные данные блоками указанной в 15 строке величины (в данном случае 1024). Проверяем полученные данные, если клиент прислал слово exit, отправляем ему bye и выходим из цикла закрывая соединение. Если же принятые данные не exit, то отправляем их обратно. А вот и клиент к нему:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
host = "localhost"
port = 44444
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
while True:
buf = raw_input(">>")
s.send(buf)
result = s.recv(1024)
print result
if buf == "exit":
break
s.close()
В начале всё как и у сервера. Создаём сокет, биндим к адресу и порту сервера. Далее в цикле организуем что то вроде чата с сервером :). В 12 строке читаем введённые с клавиатуры данные. Отправляем их серверу, получаем ответ и выводим на консоль. Далее, если мы отправили серверу команду exit, выходим из клиента или же переходим к следующей итерации цикла, возвращаясь к приёму данных из клавиатуры. Запустите на разных терминалах клиент с сервером и попробуйте поиграться, это забавно :). Сервер получился самый простой. После закрытия соединения клиентом сервер сам закрывается. Далее попробуем его немного усложнить так, чтобы он мог обрабатывать теоретически неограниченное количество подключений одновременно. Используем для этого многопоточность из модуля threading. Многопоточный сервер:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
import threading
host = "localhost"
port = 44444
class Connect(threading.Thread):
def __init__(self, sock, addr):
self.sock = sock
self.addr = addr
threading.Thread.__init__(self)
def run (self):
while True:
buf = self.sock.recv(1024)
if buf == "exit":
self.sock.send("bye")
break
elif buf:
self.sock.send(buf)
self.sock.close()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((host, port))
s.listen(5)
while True:
sock, addr = s.accept()
Connect(sock, addr).start()
Что нового?.. Мы подключили модуль threading. В 10 строке создаём класс Connect потомок threading.Thread в котором описываем взаимодействие с клиентом переопределив родительский метод run(). Именно этот метод создаёт отдельный поток и выполняет в нём своё содержимое. Далее после создания и бинда сокета запускаем цикл в котором ожидаем подключение. При соединении запускаем обработку отдельным потоком, то есть переходим к ожиданию следующего подключения не зависимо от состояния предыдущего. Вот собственно и всё. Как это можно использовать? Да как угодно :) написать например собственный чат, или файловый сервер. Через сокеты между клиентом и сервером можно передавать практически любую информацию, как текст так и байтовый поток. Воспользовавшись модулем pickle можно консервировать и отправлять любые структуры данных: объекты, списки, и т. д. Богатое поле для деятельности ;). Теги:
Python
| ||
Комментарии (10)
)

Удачи!
Еще советую провентилировать вопрос "А не придет ли процессу SIGPIPE, если клиент не вовремя закроет сокет?". В C/C++ и Perl SIGPIPE 100% приходит. И по умолчанию этот сигнал убивает приложение, так что нужен свой обработчик. Как в Пайтоне - не знаю.
Еще в сях всегда остро стоият вопросы "а кто вызовет деструктор этого объекта, а будет ли освобождена память там-то", но в вашем случае, похоже, этой проблемы нет. На досуге советую подумать над корректным завершении программы (с ожиданием завершения каждого потока и закрытием каждого сокета) в случае команды администратора остановить сервер. Такой командой может быть создание какого-нибудь файла.
if buf == "exit": sock.send("bye") break elif buf: self.sock.send(buf)может все таки
self.sock.send("bye")Спасибо.
PS. И ведь до сих пор никто так и не заметил :)))