среда, 5 декабря 2012 г.

Сервер на Python

Напишем простой сервер на Python.

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3.  
  4. import socket
  5.  
  6. host = "localhost"
  7. port = 44444
  8.  
  9. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  10. s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  11. s.bind((host, port))
  12. s.listen(5)
  13. sock, addr = s.accept()
  14. while True:
  15. buf = sock.recv(1024)
  16. if buf == "exit":
  17. sock.send("bye")
  18. break
  19. elif buf:
  20. sock.send(buf)
  21. sock.close()

Разберем код нашего сервера.

Сначала в строке 4 мы подключаем модуль для работы с сокетами. Он содержит весь необходимый нам функционал.

Далее в строках 6 и 7 мы определим хост, на котором сервер будет ждать соединение, и порт, который он будет слушать.

9 строка создает сокет для Ipv4.

В 10 строке мы устанавливаем опцию повторного использования порта, чтобы не ждать пока он освободится после останова сервера.

Далее в строке 11 мы ассоциируем (биндим) сокет с хостом и портом.

В 12 строке мы указываем количество ожидающих обработки запросов.

В строке 13 функция accept() переводит приложение в режим ожидания подключения клиента. При успешном подключении accept возвратит кортеж (пару) из объекта соединения и адреса клиента. Полученный объект мы и будем использовать для взаимодействия с клиентом.

В строке 14 мы запускаем вечный цикл while, в котором читаем из объекта отправленные данные блоками указанной в 15 строке величины (в данном случае 1024).

В строках с 16 по 20 мы проверяем полученные данные. Если клиент прислал слово exit, то мы отправляем ему bye и выходим из цикла, закрывая соединение. Если же принятые данные не exit, то отправляем их обратно.

Теперь мы напишем клиент к нашему серверу на Python.
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3.  
  4. import socket
  5.  
  6. host = "localhost"
  7. port = 44444
  8.  
  9. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  10. s.connect((host, port))
  11. while True:
  12. buf = raw_input(">>")
  13. s.send(buf)
  14. result = s.recv(1024)
  15. print result
  16. if buf == "exit":
  17. break
  18. s.close()
Разберем код клиента.

В начале все как у сервера. Создаём сокет, биндим к адресу и порту сервера. Далее в цикле организуем что то вроде чата с сервером.

В 12 строке читаем введенные с клавиатуры данные. Отправляем их серверу, получаем ответ и выводим в консоль.

Далее, если мы отправили серверу команду exit, то выходим из клиента или же переходим к следующей итерации цикла, возвращаясь к приему данных с клавиатуры.

Запустите на разных терминалах клиент с сервером на Python и попробуйте протестировать их взаимодействие.

Сервер получился самый простой. После закрытия соединения клиентом сервер сам закрывается.

Далее мы попробуем его немного усложнить так, чтобы он мог обрабатывать теоретически неограниченное количество подключений одновременно.
Для этого используем многопоточность из модуля threading.


Код многопоточный сервера на Python.
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3.  
  4. import socket
  5. import threading
  6.  
  7. host = "localhost"
  8. port = 44444
  9.  
  10. class Connect(threading.Thread):
  11. def __init__(self, sock, addr):
  12. self.sock = sock
  13. self.addr = addr
  14. threading.Thread.__init__(self)
  15. def run (self):
  16. while True:
  17. buf = self.sock.recv(1024)
  18. if buf == "exit":
  19. self.sock.send("bye")
  20. break
  21. elif buf:
  22. self.sock.send(buf)
  23. self.sock.close()
  24.  
  25. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  26. s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  27. s.bind((host, port))
  28. s.listen(5)
  29. while True:
  30. sock, addr = s.accept()
  31. Connect(sock, addr).start()
Разберем код многопоточного сервера.

Сначала мы подключили модуль threading.

В 10 строке создаём класс Connect, являющийся наследником класса threading.Thread, в котором описываем взаимодействие с клиентом переопределив родительский метод run(). Именно этот метод создает отдельный поток и выполняет в нём своё содержимое.

Далее после создания и бинда сокета запускаем цикл в котором ожидаем подключение.

При соединении запускаем обработку отдельным потоком, то есть переходим к ожиданию следующего подключения независимо от состояния предыдущего.

Вот собственно и все. Через сокеты между клиентом и сервером можно передавать практически любую информацию, как текст так и байтовый поток. Это позволяет вам создать что угодно: например собственный чат или файловый сервер.

Воспользовавшись модулем pickle можно консервировать и отправлять любые структуры данных: объекты, списки и так далее.

Другой пример кода сервера на Python приведен ниже.

Код сервера.

#!/usr/bin/python
# -*- coding: utf-8 -*-

# Импортируем необходимые библиотеки
import socket
import threading

# Указываем IP адрес и порт, на котором будет сервер
host = "127.0.0.1"
port = 12815

# Создаем класс-потомок threading.Thread в котором
# описываем взаимодействие с клиентом переопределив родительский
# метод run(). Именно этот метод создаёт отдельный поток и выполняет
# в нём своё содержимое.

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 == 'help':
            sock.send('\nThis is help!')
         # Если же прислал exit - разрываем соединение
            elif buf == 'exit':
                sock.send('Bye')
                break
            # Неопределенная команда вернёт её обратно
            elif buf:
                self.sock.send('Dont know what is '+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()
Код клиента.

#!/usr/bin/python
# -*- coding: utf-8 -*-

# Импортируем необходимые библиотеки
import socket

# Указываем IP адрес и порт для подключения
host = "127.0.0.1"
port = 12815

# Приветственное сообщение
print 'Welcome to our superserver Client!\n\r'

# Задаем способ подключения и подключаемся к серверу по IP и порту
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))

# Циклически "болтаем" с сервером о том и о сем :)
# А если вернее - принимаем команды, пока не будет exit
while True:
   # получаем команду от пользователя
    buf = raw_input('serv: >> ')
    # отсылаем серверу
    s.send(buf)
    # получаем и выводим ответ
    result = s.recv(1024)
    print result
    # елси команда была exit - разъединяемся
    if buf == "exit":
        break
s.close()
Далее представим код сервера на Python, предназначенного для обработки на нем загруженных данных. В задаче необходимо получить от пользователя exe-файл, на сервере его обработать нужными программами и выдать результат, то есть обработанный файл для скачивания.

На Python такой тип программы можно реализовать достаточно просто, используя стандартные заготовки и библиотеки, такие как BaseHTTPServer.

Ниже следует листинг сервера с краткими комментариями.

# -*- coding: utf-8 -*-

import cgi
from os import curdir, sep
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import subprocess

class MyHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        try:
            if self.path != "/output.exe":
                f = open(curdir+sep+"upload.html")
                self.send_response(200)
                self.send_header("Content-type", "text/html")
                self.end_headers()
                self.wfile.write(f.read())
                f.close()
            else:
                self.send_response(200)
                self.send_header("Content-type", "application/octet-stream")
                self.end_headers()
                self.wfile.write(open(curdir+sep+"output.exe", "rb").read())
        except IOError:
            self.send_error(404,"File Not Found: %s" % self.path)

    def do_POST(self):
        try:
            ctype, pdict = cgi.parse_header(self.headers.getheader("content-type"))
            if ctype == "multipart/form-data":
                query = cgi.parse_multipart(self.rfile, pdict)
            self.send_response(200)
            self.end_headers()
            upfile = query.get("file")

            f = open(curdir+sep+"output.exe", "wb")
            f.write(upfile[0])
            f.close()

            params = " np output.exe"

            p = query.get("encryption")
            if p[0] == "aes":
                params += " sf 1"
            elif p[0] == "rc5":
                params += " sf 2"
            elif p[0] == "xor":
                params += " sf 3"
            else:
                params += " sf 0"

            p = query.get("hw_bind")
            if p[0] == "yes":
                p = query.get("hw_bind_serial")
                assert len(p[0]) == 8
                params += " sn " + p[0]
            else:
                params += " sn 0"

            p = query.get("passwd")
            assert len(p[0]) > 0
            params += " pass " + p[0]

            p = query.get("pack")
            if p[0] == "yes":
                params += " pack 1"
            else:
                params += " pack 0"

            pipe = subprocess.Popen("processor.exe "+params, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            pipe.stdin.close()
            pipe.wait()

            self.wfile.write('<a href="/output.exe">Download results</a>.');
        except :
            pass

if __name__ == "__main__":
    try:
        server = HTTPServer(("", 8080), MyHandler)
        print "started httpserver..."
        server.serve_forever()
    except KeyboardInterrupt:
        print "^C received, shutting down server"
        server.socket.close()

Итак, для начала импортируем из библиотеки BaseHTTPServer два класса: HTTPserver - собственно сам сервер и BaseHTTPRequestHandler - класс, который служит для обработки запросов от пользователей.

Объявляем свой класс MyHandler, который наследуется от BaseHTTPRequestHandler. И перегружаем в нём два метода: do_GET и do_POST для обработки данных, переданных методом GET и POST соответственно.

В первом обработчике проверяем запрос от пользователя (содержится в self.path). Если запрашиваемый документ не является /output.exe, то выдаём форму для загрузки файла, иначе считываем и выдаём output.exe. Так же перед этим посылается ответ от сервера с кодом 200, что соответствует успешной обработке запроса от клиента и передаем заголовок, в котором указан тип содержимого (text/html для html-код или application/octet-stream для бинарного файла).

В обработке POST-запроса проверяем какие данные были переданы. Сначала получаем файл и записываем его под именем output.exe. Далее формируем строку параметров на основе переданных от пользователя данных, запускаем хранящуюся на сервере программу для обработки полученных файлов, ждем пока она завершится и выдаем ссылку на скачивание результата.

Далее в самой программе создаем сервер, указав при этом порт, на котором он будет работать (в примере это 8080) и класс, который необходимо использовать для обработки запросов от пользователей.

Недостаток заключается в том, что данная реализация не сможет обработать одновременную загрузку файлов от разных пользователей, так как. в лучшем случае просто один из запросов затрет файл output.exe, полученный в другом запросе.

Так же приведем код html-файла upload.html, который должен располагаться в той же директории, что и сервер на Python.

<form action="/" enctype="multipart/form-data" method="post">
<table border="1">
<tbody>
<tr>
<td>Encryption:</td>
<td>
<input checked="checked" name="encryption" type="radio" value="none" />None
<input name="encryption" type="radio" value="aes" />AES
<input name="encryption" type="radio" value="rc5" />RC5
<input name="encryption" type="radio" value="xor" />XOR</td>
</tr>
<tr>
<td>Hardware binding:</td>
<td>
<input checked="checked" name="hw_bind" type="radio" value="no" />No
<input name="hw_bind" type="radio" value="yes" />Yes
<input name="hw_bind_serial" value="media serial number" /></td>
</tr>
<tr>
<td>Password:</td>
<td>
<input name="passwd" type="password" /></td>
</tr>
<tr>
<td>Pack:</td>
<td>
<input checked="checked" name="pack" type="radio" value="no" />No
<input name="pack" type="radio" value="yes" />Yes</td>
</tr>
<tr>
<td colspan="2">
<input name="file" type="file" />
<input type="submit" value="Отправить" /></td>
</tr>
</tbody></table>
</form>

Надеемся, что приведенные примеры создания серверов на Python помогут вам в вашей работе.


Комментариев нет:

Отправить комментарий