Содержание


Программирование на Python

Часть 10. Сетевое программирование

Comments

Серия контента:

Этот контент является частью # из серии # статей: Программирование на Python

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Программирование на Python

Следите за выходом новых статей этой серии.

Компьютерный мир глобализируется на основе сетевых коммуникаций и протоколов. Интернет становится обязательным атрибутом повседневности. Все больше появляется приложений, ориентированных на сеть: это серверы баз данных, сетевые игры, различные сетевые протоколы, Web-серверы, апплеты, сервлеты, CGI-скрипты и т.д. Более того, сеть – это уже компьютер в том случае, когда используется распределенная кластерная архитектура вычислений. В этой статье речь пойдет о сетевом программировании на Python. Модуль socket предлагает простой интерфейс для использования сокетов. Программисты на C/C++ найдут, что здесь реализация сокетов значительно проще. В Python есть и другие сетевые модули: httplib, ftplib, telnetlib, smtplib, реализующие различные сетевые протоколы. Кроме того, в статье значительное внимание будет уделено инструментарию twisted, который еще в большей степени унифицирует рутинные операции, связанные с особенностями сетевого программирования.

Сегодня мы рассмотрим следующие темы.

  1. Как написать простой TCP клиент-сервер.
  2. Архитектура TCP-сервера.
  3. Что такое Twisted.
  4. Twisted протокол.
  5. Twisted фабрика.
  6. Twisted клиент-сервер.
  7. Twisted чат-сервер.

1. TCP клиент-сервер

TCP – стандартный протокол для межсетевого взаимодействия. Его основным достоинством является принцип гарантированной доставки – все пакеты, посланные сервером, будут доставлены клиенту.

Напишем простое клиент-серверное приложение. Для этого нам нужно импортировать класс socket из стандартной библиотеки, в котором есть все методы для организации соединения. Клиент посылает строку на сервер, сервер получает ее и отсылает клиенту обратно.

Код простого сервера: вначале мы создаем сокет, представляющий собой указатель на объект соединения. Этому сокету мы передаем два аргумента: первый аргумент говорит о том, что это интернет-сокет, второй – что мы используем TCP-протокол.

Первый метод, который мы используем – bind(), он инициализирует ip-адрес и порт. При этом проверяется, не занят ли порт другой программой.

Второй метод – listen() – устанавливает количество клиентских соединений, которые будет обслуживать операционная система.

Третья функция – accept() – блокирует приложение до тех пор, пока не придет сообщение от клиента. Функция возвращает кортеж из двух параметров – объект самого соединения и адрес клиента.

Четвертая функция – recv() – читает данные из сокета. Аргумент устанавливает максимальное количество байтов в сообщении.

Пятая функция – send() – отсылает данные клиенту.

Шестая функция – close() – закрывает сокет.

Функция raw_input() просто блокирует клавиатуру.

import socket
import sys
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
host = 'localhost'
port = 8007
s.bind((host), (port))
s.listen(1)
conn, addr = s.accept()
data = conn.recv(1000000)
print 'client is at', addr , data
conn.send(data)
z = raw_input()
conn.close()

Клиент вначале создает точно такой же сокет, что и сервер. Первый клиентский метод – connect() – позволяет соединиться с сервером. Второй метод – send() – отсылает данные на сервер. Третий метод – recv() – получает данные с сервера. Четвертый метод – close() – закрывает сокет.

import socket
import sys
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
host = 'localhost'
port = 8007
s.connect((host, port))
s.send('hello')  
data = s.recv(1000000) 
print 'received', data, len(data), 'bytes'
s.close()

Что стоит за этими простыми методами? По сути, они представляют собой оболочки (wrappers) для аналогичных системных вызовов. Так, метод socket.send() фактически вызывает вызов send() операционной системы. Посылаемые данные копируются в буфер операционной системы и при этом могут разбиваться на отдельные блоки (chunk). После того как последний блок будет скопирован в буфер, функция send() вернет управление программе, при этом совсем не факт, что все данные уже уйдут по назначению и будут получены на том конце клиентом. Клиент по дороге может обнаружить, что отдельные блоки пропали, и запросит их у сервера повторно. Но это уже не наша забота – за полную гарантированную отсылку данных отвечает операционная система, а не приложение.

2. Архитектура TCP-сервера

В предыдущем простом примере сервера вы успели заметить, что функция accept() блокирует приложение. Пример банальный, рассчитан на одного клиента, и к реальной жизни имеет отдаленное отношение. Реальные серверы имеют нагрузку в несколько тысяч клиентов, и нужна более серьезная реализация. Для этого существует несколько различных подходов:

  1. использование отдельного потока на каждого клиента;
  2. использование неблокирующих сокетов;
  3. использование select/poll.

В Python неблокирующий сокет реализуется с помощью специального метода setblocking() с параметром, равным нулю.

Пример:

lstn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
cs = []
nc = 2
for i in range(nc):
   (clnt,ap) = lstn.accept()
   clnt.setblocking(0)
   cs.append(clnt)

Недостаток этого метода в том, что нам вручную нужно проверять готовность каждого клиента. Третий вариант с использованием select() позволяет переложить эту проверку на саму операционную систему. Более поздняя его вариация – функция poll().

3. Twisted

Twisted – кросс-платформенная сетевая библиотека, написанная на Python. Это асинхронный инструмент, который избавляет вас от необходимости использовать потоки. Он поддерживает работу с mail, web, news, chat, DNS, SSH, Telnet, RPC, и т.д.

Многие дистрибутивы Linux уже включают в себя twisted. Можно установить инструментарий из исходных текстов, которые лежат тут: http://twistedmatrix.com/projects/core/

В основе Twisted лежат события – event. Работой таких событий управляют специальные функции, называемые хэндлерами – event handler. Есть специальный бесконечный цикл по обработке событий – так называемый event loop. Он отлавливает события, после чего запускает соответствующие хэндлеры. После этого кошмар с последовательной обработкой событий заканчивается. За работу цикла event loop в twisted отвечает объект, называемый reactor, который находится в модуле twisted.internet . Для его запуска нужно вызвать команду:

  reactor.run()

4. Twisted Protocol

Для написания сетевого приложения нужно использовать класс Protocol, который находится в twisted.internet.protocol.Protocol. Большинство протоколов унаследованы от этого класса. Они никогда не ждут события, обрабатывая их по мере появления. Вот простой пример:

from twisted.internet.protocol import Protocol
  class Echo(Protocol):
    def dataReceived(self, data):
      self.transport.write(data)

Это простейший протокол. Он просто пишет назад то, что пишет ему клиент, и не отвечает ни на какие события. Вот пример протокола, отвечающего на событие connect:

from twisted.internet.protocol import Protocol
class Connector(Protocol):
  def connectionMade(self):
    self.transport.write("Connection made ... \r\n") 
    self.transport.loseConnection()

Этот протокол отвечает на соединение тем, что обрывает его. Событие connectionMade происходит при коннекте. Событие connectionLost срабатывает при разрыве коннекта.

5. Twisted Factory

Конфигурация поведения протокола прописывается в фабрике – классе Factory, который унаследован от twisted.internet.protocol.Factory. В программе может быть несколько протоколов, фабрика является для них организующим и конфигурационным компонентом. По умолчанию фабрика запускает каждый протокол, и устанавливает ему атрибут, называемый factory, который указывает на себя. Пример фабрики, которая позволяет протоколу писать лог-файл:

from twisted.internet.protocol import Factory
from twisted.protocols.basic import LineReceiver
 
class LoggingProtocol(LineReceiver):
    def lineReceived(self, line):
        self.factory.fp.write(line+'\n')

class LogfileFactory(Factory):
    protocol = LoggingProtocol
    def __init__(self, fileName):
        self.file = fileName
    def startFactory(self):
        self.fp = open(self.file, 'a')
    def stopFactory(self):
        self.fp.close()

6. Twisted Client-Server

Код простого сервера: на событие dataReceived сервер выводит полученные данные и тут же отсылает их обратно:

from twisted.internet.protocol import Factory, Protocol
from twisted.internet import reactor
 
class Server(Protocol):
  def connectionMade(self):
    self.transport.write(self.factory.quote+'\r\n')
  def connectionLost(self, reason):
    print 'connection lost ...'
  def dataReceived(self, data):
    print data
    self.transport.write(data)
  
class ServerFactory(Factory):
  protocol = Server
  def __init__(self, quote=None):
    self.quote = quote 

reactor.listenTCP(8007, ServerFactory("quote"))
reactor.run()

В этом примере клиент делает два отложенных асинхронных сообщения с интервалом в одну секунду, используя метод реактора callLater:

from twisted.internet import reactor
from twisted.internet.protocol import Protocol, ClientCreator
 
class Client(Protocol):
  def sendMessage(self, msg):
    self.transport.write("%s\n" % msg)
    for i in range(1,5):
      self.transport.write("%d\n" % i)
  def dataReceived(self, data):
    print data
 
def gotProtocol(p):
    p.sendMessage("Hello")
    reactor.callLater(1, p.sendMessage, "world")
    reactor.callLater(2, p.transport.loseConnection)
 
c = ClientCreator(reactor, Client)
c.connectTCP("localhost", 8007).addCallback(gotProtocol)
reactor.run()

Вывод сервера:

Hello
1
2
3
4
world
1
2
3
4
connection lost ...

7. Twisted чат-сервер

Чат-сервер – это сервер, который делает широковещательную рассылку всех сообщений, которые были посланы клиентами. Сервер анализирует клиентские сообщения и в зависимости от их типа может сообщить остальным о том, что клиент зашел под своим именем, может просто разослать всем клиентское сообщение, либо отключает клиента. Список клиентов хранится в фабрике и называется clientProtocols. При каждом новом коннекте клиента в методе connectionMade протокола происходит добавление объекта ChatProtocol в этот список. Код простого чат-сервера:

from twisted.internet import reactor
from twisted.internet.protocol import ServerFactory 
from twisted.protocols.basic import LineOnlyReceiver 

class ChatProtocol(LineOnlyReceiver): 

    name = "" 

    def getName(self): 
        if self.name!="": 
            return self.name 
        return self.transport.getPeer().host 

    def connectionMade(self): 
        print "New connection from "+self.getName() 
        self.sendLine("Welcome to my my chat server.") 
        self.sendLine("Send '/NAME [new name]' to change your name.") 
        self.sendLine("Send '/EXIT' to quit.") 
        self.factory.sendMessageToAllClients(self.getName()+" has joined the party.") 
        self.factory.clientProtocols.append(self)

    def connectionLost(self, reason): 
        print "Lost connection from "+self.getName() 
        self.factory.clientProtocols.remove(self) 
        self.factory.sendMessageToAllClients(self.getName()+" has disconnected.") 

    def lineReceived(self, line): 
        print self.getName()+" said "+line 
        if line[:5]=="/NAME": 
            oldName = self.getName() 
            self.name = line[5:].strip() 
            self.factory.sendMessageToAllClients(oldName+" changed name 
				to "+self.getName()) 
        elif line=="/EXIT": 
            self.transport.loseConnection() 
        else: 
            self.factory.sendMessageToAllClients(self.getName()+" says "+line) 

    def sendLine(self, line): 
        self.transport.write(line+"\r\n") 

class ChatProtocolFactory(ServerFactory): 

    protocol = ChatProtocol 

    def __init__(self): 
        self.clientProtocols = [] 

    def sendMessageToAllClients(self, mesg): 
        for client in self.clientProtocols:
            client.sendLine(mesg) 

print "Starting Server"
factory = ChatProtocolFactory()
reactor.listenTCP(12345, factory)
reactor.run()

В качестве клиента можно использовать telnet:

>> telnet localhost 12345

Заключение

Сегодня мы узнали, что TCP протокол – это надежный сетевой протокол с гарантированной доставкой. При написании TCP клиент-серверных приложений функции протокола распределяются между приложением и самой операционной системой. В зависимости от архитектуры сервера, сокеты могут быть блокирующими и неблокирующими. Системные вызовы select() и poll() позволяют писать высоконагруженные серверы с подключением большого количества клиентов. Инструментарий twisted значительно облегчает труд по написанию сетевых приложений, предоставляя программисту возможность сфокусироваться на логике приложения, скрывая при этом низкоуровневые подробности сетевого протокола.

Код примеров проверялся на версии Python 2.6.

<< Предыдущая статьяСледующая статья >>


Ресурсы для скачивания


Комментарии

Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=517106
ArticleTitle=Программирование на Python: Часть 10. Сетевое программирование
publish-date=09072010