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

Установка серверов Tornado, Nginx и Django на Windows

Что это такое и как работает:

Tornado - это неблокирующий, высоко производительный веб-сервер с открытыми исходным кодом. Сервер написан на языке Python и без проблем выдерживает несколько тысяч одновременных подключений.

Nginx - маленький но очень быстрый веб-сервер, думаю с ним вы уже знакомы.

Django - фреймворк для веб-приложений на языке Python.


Это все будет работать по нашей любимой схеме frontend - backend:

frontend - nginx, передаёт все соединения, кроме статических файлов, на backend. Просто отдаёт статику.

backend - tornado + django

По запуску Tornado+Nginx под linux написано очень много и трогать єту тему не будем. Мы же займёмся установкой этой чудесной связки под винду. Но крайне не рекомендую ставить рабочие проекты под Windows (тем более под XP).

Для большей реалистичности эксперименты будут проводится над django приложением OSQA(качать по ссылке ниже). OSQA - это Open Source система построения Q&A(Вопросы и ответы) сообществ.

Понадобиться:
1. Windows
2. Python 2.6 или 2.7
3. Dajngo
4. Tornado
5. NIMPix 1.2.0 RC2
6. OSQA
7. Консоль

В статье подразумевается ,что у вас уже установлены :
1. Windows
2. Python
2.0 djаngo
2.1 markdown
2.2 html5lib
2.3 south
2.4 python-openid
2.5 setuptools
3. NIMPix
процесс их установки не описан.

Сервер NIMPix находится на диске С:\

Глава 2. Установка и настройка OSQA
Внимание если у вас версия django выше 1.3, то OSQA работать не будет. Рекомендовано django 1.2 на 1.3(с плясками).
1. Создать папку apps в С:\nimpix в ней папку osqa. Здесь будет находиться наш сайт вопросов и ответов.
2. Распаковать все файлы из архива OSQA в эту папку.
3. Запускам Mysql и Nginx на NIMPix
4. Созадем Mysql базу с названием osqa
5. Проверяем все ли нужные модули Python установлены. Для этого в консоли пишем
python
открывается интерпретатор в нем вводим
>>> help(’modules’)
Найдите в появившемся списке такие модули django, html5lib, south, markdown, openid. Если одного из них нет то необходимо его установить т.к. все они нужный для работы OSQA.
Для этого :
1. Ставим setuptools http://pypi.python.org/pypi/setuptools или distribute http://pypi.python.org/pypi/distribute. Рекомендую distribute, но я пользуюсь setuptools(по привычке).
2. Ставим Если у вас не прописан в PATCH путь до каталога site-packages, то пишите как написано ниже.
“C:\Program Files\Python27\Lib\site-packages\easy_install.py” django
“C:\Program Files\Python27\Lib\site-packages\easy_install.py” markdown
“C:\Program Files\Python27\Lib\site-packages\easy_install.py” html5lib
“C:\Program Files\Python27\Lib\site-packages\easy_install.py” south
“C:\Program Files\Python27\Lib\site-packages\easy_install.py” python-openid
Если же у вас этот путь есть то будет достаточно просто easy_install ….. , без указания полного пути.
Переходим к настройке OSQA.
1. !!!ВНИМАНЕ!!!Для тех у кого django 1.3
в С:\nimpix\apps\osqa\forum\utils\html.py строку
from django.template import mark_safe
меняем на
from django.utils.safestring import mark_safe
в C:\nimpix\apps\osqa\forum\urls.py
вместо
url(r’^%s(.*)’ % _(’nimda/’), admin.site.root),
должно быть так
url(r’^%s’ % _(’nimda/’), include(admin.site.urls)),
2. Открываем С:\nimpix\apps\osqa\settings_local.py и настраиваем подключение к БД
DATABASES = {
‘default’: {
‘ENGINE’: ‘.db.backends.mysql’,
‘NAME’: ‘osqa’,
‘USER’: ‘root’,
‘PASSWORD’: ‘J8uv01Bq431j’,
‘HOST’: ”,
‘PORT’: ”,
}
}
3. Создаём базу данных и все такое.
С:\nimpix\apps\osqa\manage.py syncdb –all
!!!При запросе супер пользователя НЕ СОЗДАЁМ!!!
С:\nimpix\apps\osqa\manage.py migrate forum –fake

Глава 3. Установка и настройка связки Tornado+Nginx
Начнем:
Установка торнадо:
Способ(Рекомендованный) 1.
Устанавливаем с помощью setuptools или distribute.
“C:\Program Files\Python27\Lib\site-packages\easy_install.py” tornado

Способ 2.
С шаманскими плясками и бубнами.
1. Скачать Tornado
http://github.com/downloads/facebook/tornado/tornado-1.2.1.tar.gz
2. распаковать в C:\tornado
3. Заходим в папку и устанавливаем.
cd C:\tornado
setup.py build
setup.py install
4. торнадо установлен.
5. Создаём файл tornading.py и помешаем его в корень нашего проекта.
# -*- coding: utf-8 -*-
import os
import sys
# настройки
DJANGO_ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Между ” укажите путь до ваших приложений, пустой указывает на корень сайта.
DJANGO_APPS_DIR = os.path.join(DJANGO_ROOT_DIR, ”)
def daemon(iport):
import tornado.wsgi
import tornado.ioloop
import tornado.httpserver
# настраиваем django
sys.path.insert(0, DJANGO_APPS_DIR)
os.environ['DJANGO_SETTINGS_MODULE'] = ’settings’
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()
# подключаем tornado
container = tornado.wsgi.WSGIContainer(application)
http_server = tornado.httpserver.HTTPServer(container)
http_server.listen(iport)
tornado.ioloop.IOLoop.instance().start()
if __name__ == "__main__":
# на вход должен быть передан порт,
# на котором будет запущен tornado-сервер
daemon(int(sys.argv[1]))
6. Запускаем 2 экземпляра на портах 8001 и 8002(порты могут быть и другие). Естественно путь к файлу tornading.py должен быть ваш.
С:\nimpix\apps\osqa\tornading.py 8001&
С:\nimpix\apps\osqa\tornading.py 8002&
7. Запускаем NIMPix, создаём виртуальный хост osqa.lan, открываем его для редактирования и приводим его к такому виду.
У меня путь к проекту django С:/nimpix/apps/osqa/, вы его должны поменять на свой.
upstream backends {
server 127.0.0.1:8001;
server 127.0.0.1:8002;
}
server {
listen 80;
server_name www.osqa.lan osqa.lan;
access_log /nimpix/component/logs/site/osqa.buh.lan.nginx.access.log;
error_log /nimpix/component/logs/site/osqa.buh.lan.nginx.error.log;
location /media/ {
alias С:/nimpix/apps/osqa/media/;
expires 30d;
}
location /admin/ {
alias С:/nimpix/apps/osqa/media_admin/;
expires 30d;
}
location / {
proxy_pass http://backends;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
8. Включаем виртуальный хост и запускаем сервер.
9. Заходим на сайт, создаём пользователя. Первый созданный пользователь будет администратором .
10. Вот и все

Установка сервера Apache, mod_python, Django, MySQL и Sqlite3 на Windows

Сегодня я расскажу вам, как собрать полноценный сервер для Django приложений. Для написания и тестирования вполне хватает встроенного сервера, но часто бывает, что работа приложения на деволоперском сервере запускаемом командой (django-admin.py runserver) отличается от работы на боевом хостинге, проявляются разнообразные баги глюки и остальная нечисть.
И сегодняшней нашей целью является создание полноценного сервера, что бы проверить работу нашего сайта в условиях максимально приближенным к боевым.

Конечно, можно воспользоваться уже готовыми решениями типа Bitnami Djangostack это замечательная сборка из Apache HTTP Server, MySQL, Python, SQLite после установки вы получите работающий сервер, но мы, же не ищем лёгких путей и будем устанавливать, и настраивать это всё сами.

Связка будет выглядеть, таким образом: Apache+Mysql+mod_python+Django+Sqlite и сие чудо мы будем ставить на Windows.


Что нам надо:
0. Windows
1. Apach v.2.2.11
2. Mysql v.5.1.31
3. Sqlite v.3.6.11
4. Python v.3.0.1
5. Django v.1.0.2
6. mod_python v.3.3.1

ШАГ 1
Надеюсь, Windows у вас уже установлен и я опущу этот этап и начну с установки Apache.

Установить http сервер не сложно: просто запускаем скачанный инсталлер apache_2.2.11-win32-x86-no_ssl.msi

apache_1

Клацаем по кнопке “Next” до тех пор, пока установщик не попросит нас ввести Network Domain и Server name, так как мы делаем сервер исключительно для себя, то в обоих случаях вводим localhost или 127.0.0.1 кому как нравится.

apache_2

Это значит, что наш сервер будет видно только для данного компьютера. Если же вы хотите, что бы он был доступен для всех, введите свой IP адрес, или домен. После того как все заполнили выбираем “For All Users” и жмём Next и смотрим как устанавливается. По окончанию установки он должен запустится автоматически как сервис. Для проверки набираем в браузере http://localhost, если вы видите надпись “It works!” всё готово и можете читать дальше.

apache_7


Если же нет то
1.Попробуйте перезагрузить компьютер.
2.Проверьте, а не запущен ли у вас другой http сервер, если да то остановите его.
3.Может фаерволом (брендмуаром) у вас заблокирован порт 80, необходимо этот порт разблокировать.
4.Попробуйте просмотреть через нормальный браузер НЕ Internet Explorer
5.Если ни одно из вышеуказанных действий не помогло, то стучите в бубен, наверняка это проделки демонов мешают запуску нашего сервера.
Теперь нам понадобится конфигурационный файл, который находится
C:\Program Files\Apache Software Foundation\Apache2.2\conf\httpd.conf
открываем его в блокноте:
строка 170 расскоментируйте (уберите знак #)
ServerName localhost:80
строка 177 и 204 замените параметр DocumentRoot на свой, например C:/www
DocumentRoot “C:/www”
Сохраняем файл httpd.conf и перезапускаем сервер вуаля все должно работать.

ШАГ 2 - Установка MySql 
MySql установить не сложнее, чем Apache. Для начала запускаем mysql-5.1.31-win32.msi и снова кнопка next->

mysql_1

Выбираем способ установки (я советую Typical)

mysql_2

После того все файлы установились, будим конфигурировать mysql

mysql_3

Выбираем “Detailed Configuratin”(детальная настройка)

mysql_4

Клацаем на пункте “Developer Machine”, мы же не просто пользователи, а Разработчики

mysql_5

Потом выбираем “Multifunctional Database”, тоесть мы сможем работать как с таблицами типа InnoDB, так и со скоростными MyISAM в большинстве случаев используются последние.

mysql_6

Выбираем диск для хранения таблиц типа InnoDB, оставляем всё как есть и жмем “Дальше”

mysql_7

Далее мы должны выбрать, сколько будет одновременных подключений к нашему серверу. Думаю 20 для нас будет предостаточно, поэтому выбираем “Decision Support (DSS)/OLAP”,

mysql_8

Следующий этап оставляем по умолчанию, должно быть отмечено “Enable TCP/IP Networking” и “Enable Strict Mode”

mysql_9

Выбираем кодировку, которую MySql сервер будет использовать по умолчанию
“Manual Selected Default Character Set / Collation” и выделяем utf-8

mysql_10

Будет ли наш сервер запускаться как сервис или нет, мы выбираем на этом этапе, а так же рекомендую включить Include Bin Directory in Windows PATH” - это позволит нам работать с сервером из командной строки.

mysql_11

Завершающий этап - это ввод пароля (хотя можно и без него, но крайне нежелательно, поэтому введите что нибудь).

mysql_12

Ура всё!!

mysql_13

Далее идём в C:\Program Files\MySQL\MySQL Server 5.1\my.ini

Здесь нам необходимо заменить строку 84, что бы использовалась по “default” MYISAM
меняем
default-storage-engine=INNODB
на
default-storage-engine=MYISAM
Пуск -> программы -> MySQL -> MySQL Server 5.1 -> MySQL Comand Line Client
Появляется консоль с запросом ввести пароль. Вводим и получаем

mysql_15

Проверяем, что у нас за базы у нас есть! Вводим: SHOW DATABASES;

mysql_16

Все, с установкой MySql закончили!

С установкой python думаю проблем у вас не возникнет, так как вся установка заключается из двух этапов, скачать дистрибутив и запустить его, а вот на остальных моментах остановлюсь подробнее. Сразу скажу, что установка связки Apache + mod_python омрачила  меня одним моментами,  бинарник mod_python под Windows подходит только для Python 2.5.  Если хотите установить его на Python 2.6 и выше, то придется   устраивать ритуальные пляски с бубнами.


Приступим к установке mod_python (я надеюсь, python 2.5 вы уже установили)!!
Идем на офф сайт и закачиваем последнюю версию на сегодня это 3.3.1.  Запускаем приложение.

mod_python

Если вылетает окно с ошибкой типа “не найден MSVCR71.dll повторная установка приложения может решить проблему” то надо закачать отсюда MSVCR71.dll и положить его в  C:\WINDOWS\system32 это должно решить проблему. Повторно запускаем mod_python-3.3.1.win32-py2.5-Apache2.2.exe.
В процессе указываем корневой каталог Apach и ждем окончания установки.

mod_python

Теперь установим Django framework, есть несколько способов установки Django. Мы пойдем простейшим - установим из svn последнею версию дистрибутива. Для этого нам потребуется svn клиент (я взял Slik) Устанавливаем клиент!! После этого открываем консоль(cmd) заходим в каталог site-packages
cd C:\Python25\Lib\site-packages
И скачиваем  django командой
svn co http://code.djangoproject.com/svn/django/trunk/django django

django_install_end_2

Теперь наш свежеиспечённый django находится в C:\Python25\Lib\site-packages\django. Если у вас уже есть установленный, то надо его обновить командой.
svn update
при этом находясь в директории django. Вот и все!

django_update_end_4

Далее нам понадобится пустой джанго проект для тестирования. Создадим:
C:\Documents and Settings\User>cd C:\
C:\>mkdir example
C:\>cd example
С:\example>django-admin.py startproject testproject
Если у вас появилась ошибка типа “django-admin.py не является внутренней или внешней командой…” То скопируйте django-admin.py из папки C:\Python25\Lib\site-packages\django\bin в ту папку, где вы  хотите создать проект, в нашем случае это  С:\example, есть и другой способ решения данной проблемы, но об этом в другой раз. Повторите запрос
django-admin.py startproject testproject
Вот у нас есть пустой проект.  Переходим к конфигурации Apache, для этого открываем файл
C:\Program Files\Apache Software Foundation\Apache2.2\conf\httpd.conf
И подключаем в самом конце файла модулю питона.
LoadModule python_module modules/mod_python.so
затем пишем
<Location “/testproject/”>
SetHandler python-program
PythonHandler django.core.handlers.modpython
SetEnv DJANGO_SETTINGS_MODULE testproject.settings
PythonOption django.root c:/example/testproject
PythonDebug On
PythonPath “['/example'] + sys.path”
</Location>

httpd_conf_1

Перезапускаем Apache заходим http://localhost/testproject/ радуемся.

localhost_testproj

вторник, 25 декабря 2012 г.

Python Получение переменных GET и POST из адресной строки

В PHP вы можете использовать $_POST для POST и $_GET для GET для получения переменных из адресной строки (Query string variables). Каков же будет эквивалент этих команд в Python?

Python - это просто язык программирования, поэтому для того, чтобы получить данные GET и POST вам необходимо использовать специальный фреймворк, написанный на Python, такой, например, как: Django, Turbogears, Pylons, CherryPy, web.py, mod_python, fastcgi и многие другие.

Представим, что вы отправили значения на сервер из данной формы:

<input type="text" name="username" />


Тогда при использовании чистого CGI вам поможет данный код:

import cgi
form = cgi.FieldStorage()
print form["username"]


Если вы используете Django или Pylons:

print request.GET['username'] # для GET
print request.POST['username'] # для POST


Если вы используете Turbogears или Cherrypy:

from cherrypy import request
print request.params['username']

Используя Cherrypy или Turbogears вы можете также определить управляющую функцию, принимающую параметры напрямую:

def index(self, username):
    print username


Если вы используете Web.py:

form = web.input()
print form.username


Если вы используете Werkzeug:

print request.form['username']


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

import os, sys

# строка запроса, содержащая необработанные данные GET
# (Для примера для http://example.com/myscript.py?a=b&c=d&e
# это будет "a=b&c=d&e")

os.getenv("QUERY_STRING")

# чистые данные POST
sys.stdin.read()

MySQL CheatSheet


Query

SELECT * FROM table
SELECT * FROM table1, table2, ...
SELECT field1, field2, ... FROM table1, table2, ...
SELECT ... FROM ... WHERE condition
SELECT ... FROM ... WHERE condition GROUP BY field
SELECT ... FROM ... WHERE condition GROUP BY field HAVING condition2
SELECT ... FROM ... WHERE condition ORDER BY field1, field2
SELECT ... FROM ... WHERE condition ORDER BY field1, field2 DESC
SELECT ... FROM ... WHERE condition LIMIT 10
SELECT DISTINCT field1 FROM ...
SELECT DISTINCT field1, field2 FROM ...
SELECT ... FROM t1 JOIN t2 ON t1.id1 = t2.id2 WHERE condition
SELECT ... FROM t1 LEFT JOIN t2 ON t1.id1 = t2.id2 WHERE condition
SELECT ... FROM t1 JOIN (t2 JOIN t3 ON ...) ON ...
SELECT ... FROM t1 JOIN t2 USING(id) WHERE condition

Conditionals

field1 = value1
field1 <> value1
field1 LIKE 'value _ %'
field1 IS NULL
field1 IS NOT NULL
field1 IN (value1, value2)
field1 NOT IN (value1, value2)
condition1 AND condition2
condition1 OR condition2

Data Manipulation

INSERT INTO table1 (field1, field2, ...) VALUES (value1, value2, ...)
INSERT table1 SET field1=value_1, field2=value_2 ...
DELETE FROM table1 / TRUNCATE table1
DELETE FROM table1 WHERE condition
-- join:
DELETE FROM table1, table2 WHERE table1.id1 = table2.id2 AND condition
UPDATE table1 SET field1=new_value1 WHERE condition
-- join:
UPDATE table1, table2 SET field1=new_value1, field2=new_value2, ...
WHERE table1.id1 = table2.id2 AND condition

Browsing

SHOW DATABASES
SHOW TABLES
SHOW FIELDS FROM table / SHOW COLUMNS FROM table / DESCRIBE table / DESC table / EXPLAIN table
SHOW CREATE TABLE table
SHOW CREATE TRIGGER trigger
SHOW TRIGGERS LIKE '%update%'
SHOW PROCESSLIST
KILL process_number
$ mysqlshow
$ mysqlshow database

Create / delete / select / alter database

CREATE DATABASE [IF NOT EXIST] mabase [CHARACTER SET charset] [COLLATE collation]
CREATE DATABASE mabase CHARACTER SET utf8
DROP DATABASE mabase
USE mabase
ALTER DATABASE mabase CHARACTER SET utf8

Create/delete/modify table

CREATE TABLE table (field1 type1, field2 type2, ...)
CREATE TABLE table (field1 type1, field2 type2, ..., INDEX (field))
CREATE TABLE table (field1 type1, field2 type2, ..., PRIMARY KEY (field1))
CREATE TABLE table (field1 type1, field2 type2, ..., PRIMARY KEY (field1, field2))
CREATE TABLE table1 (fk_field1 type1, field2 type2, ...,
  FOREIGN KEY (fk_field1) REFERENCES table2 (t2_fieldA))
    [ON UPDATE|ON DELETE] [CASCADE|SET NULL]
CREATE TABLE table1 (fk_field1 type1, fk_field2 type2, ...,
  FOREIGN KEY (fk_field1, fk_field2) REFERENCES table2 (t2_fieldA, t2_fieldB))
CREATE TABLE table IF NOT EXISTS (...)
CREATE TABLE new_tbl_name LIKE tbl_name
  [SELECT ... FROM tbl_name ...]
CREATE TEMPORARY TABLE table (...)
DROP TABLE table
DROP TABLE IF EXISTS table
DROP TABLE table1, table2, ...
DROP TEMPORARY TABLE table
ALTER TABLE table MODIFY field1 type1 
ALTER TABLE table MODIFY field1 type1 NOT NULL ... 
ALTER TABLE table CHANGE old_name_field1 new_name_field1 type1
ALTER TABLE table CHANGE old_name_field1 new_name_field1 type1 NOT NULL ...
ALTER TABLE table ALTER field1 SET DEFAULT ...
ALTER TABLE table ALTER field1 DROP DEFAULT
ALTER TABLE table ADD new_name_field1 type1
ALTER TABLE table ADD new_name_field1 type1 FIRST
ALTER TABLE table ADD new_name_field1 type1 AFTER another_field
ALTER TABLE table DROP field1
ALTER TABLE table ADD INDEX (field);
ALTER TABLE table ADD PRIMARY KEY (field);
-- Change field order:
ALTER TABLE table MODIFY field1 type1 FIRST
ALTER TABLE table MODIFY field1 type1 AFTER another_field
ALTER TABLE table CHANGE old_name_field1 new_name_field1 type1 FIRST
ALTER TABLE table CHANGE old_name_field1 new_name_field1 type1 AFTER another_field
ALTER TABLE old_name RENAME new_name;

Keys

CREATE TABLE table (..., PRIMARY KEY (field1, field2))
CREATE TABLE table (..., FOREIGN KEY (field1, field2) REFERENCES table2 (t2_field1, t2_field2))
ALTER TABLE table ADD PRIMARY KEY (field);

Privileges

GRANT ALL PRIVILEGES ON base.* TO 'user'@'localhost' IDENTIFIED BY 'password';
GRANT SELECT, INSERT, DELETE ON base.* TO 'user'@'localhost' IDENTIFIED BY 'password';
REVOKE ALL PRIVILEGES ON base.* FROM 'user'@'host'; -- one permission only
REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'user'@'host'; -- all permissions
SET PASSWORD = PASSWORD('new_pass')
SET PASSWORD FOR 'user'@'host' = PASSWORD('new_pass')
SET PASSWORD = OLD_PASSWORD('new_pass')
DROP USER 'user'@'host'

Main data types

TINYINT (1o: -127+128) SMALLINT (2o: +-65 000)
  MEDIUMINT (3o: +-16 000 000) INT (4o: +- 2 000 000 000)
  BIGINT (8o: +-9.10^18)
  Precise interval: -(2^(8*N-1)) -> (2^8*N)-1
  /!\ INT(2) = "2 digits displayed" -- NOT "number with 2 digits max"
INT NOT NULL auto_increment PRIMARY KEY -- auto-counter for PK
FLOAT(M,D) DOUBLE(M,D) FLOAT(D=0->53) 
  /!\ 8,3 -> 12345,678 -- NOT 12345678,123!
TIME (HH:MM) YEAR (AAAA) DATE (AAAA-MM-JJ) DATETIME (AAAA-MM-JJ HH:MM; années 1000->9999)
  TIMESTAMP (like DATETIME, but 1970->2038, compatible with Unix)
VARCHAR (single-line; explicit size)  TEXT (multi-lines; max size=65535)  BLOB (binary; max size=65535)
  Variants for TEXT&BLOB: TINY (max=255) MEDIUM (max=~16000) LONG (max=4Go)
 Ex: VARCHAR(32), TINYTEXT, LONGBLOB, MEDIUMTEXT
ENUM ('value1', 'value2', ...) -- (default NULL, or '' if NOT NULL)

Forgot root password?

$ /etc/init.d/mysql stop
$ mysqld_safe --skip-grant-tables
$ mysql # on another terminal
mysql> UPDATE mysql.user SET password=PASSWORD('nouveau') WHERE user='root';
## Kill mysqld_safe from the terminal, using Control + \
$ /etc/init.d/mysql start

Repair tables after unclean shutdown

mysqlcheck --all-databases
mysqlcheck --all-databases --fast

понедельник, 24 декабря 2012 г.

Отличия TCP от UDP

В чем отличия TCP от UDP?

Прежде всего, TCP и UDP - это протоколы передачи данных по сети. Основное их отличие в том, что TCP - это протокол с гарантированной доставкой пакетов, а UDP - нет.

TCP - предоставляет "гарантированный" транспортный механизм с предварительным установлением соединения, предоставляющий приложению надёжный поток данных, дающий уверенность в безошибочности получаемых данных, перезапрашивающий данные в случае потери и устраняющий дублирование данных. TCP позволяет регулировать нагрузку на сеть, а также уменьшать время ожидания данных при передаче на большие расстояния. Более того, TCP гарантирует, что полученные данные были отправлены точно в такой же последовательности. В этом его главное отличие от UDP.

UDP - это протокол передачи датаграмм без установления соединения. Также его называют протоколом "ненадёжной" передачи, в смысле невозможности удостовериться в доставке сообщения адресату, а также возможного перемешивания пакетов. В приложениях, требующих гарантированной передачи данных, используется протокол TCP.

UDP обычно используется в таких приложениях, как потоковое видео и компьютерные игры, где допускается потеря пакетов, а повторный запрос затруднён или не оправдан, либо в приложениях вида запрос-ответ (например, запросы к DNS), где создание соединения занимает больше ресурсов, чем повторная отправка.

И TCP, и UDP используют для определения протокола верхнего уровня число, называемое портом.

пятница, 14 декабря 2012 г.

Python Keyboardinterrupt

Python Threading и KeyboardInterrupt.

Как организовать пул потоков?

Допустим у вас есть 100 данных (пусть они находятся в каком-либо списке) и все их нужно переработать в 10 потоков.

За всё время работы скрипта стартует только 10 потоков. Каждый из них, отработав со своим 1 данным, не дохнет, а берет из списка следующее 1 данное и делает с ним эту же работу:

# -*- coding: utf-8 -*-
import threading
inporty Queue
import time
import traceback

class Worker(threading.Thread):
    def __init__(self,queue):
        threading.Thread.__init__(self)
        self.__queue = queue

    def run(self):
        while True:

            try: item = self.__queue.get_nowait() # ждём данные
            except Queue.Empty: break # данные закончились, прекращаем работу

            try: self.work(item) # работа с данными
            except Exception: traceback.print_exc()

            time.sleep(0.5)
            self.__queue.task_done() # задача завершена
        return
    def work(self,item):
        print item
        #pass


def main():
    # выводим в 5 потоков цифры от 1 до 100.
    queue = Queue.Queue()
    num_threads = 5 # 5 потоков

    for x in xrange(100):
        queue.put(x) # заносим данные в очередь
    for i in xrange(num_threads):
        t = Worker(queue) # создаем поток
        t.start() # стартуем
        time.sleep(0.1) # чтобы в консоли друг на друга не накладывались

    queue.join() #блокируем выполнение программы, пока не будут израсходованы данные

    print "Done!"
if __name__ == '__main__':
    main()

Хотелось бы еще, чтобы было можно все это в любой момент остановить. Но поскольку выполнение программы блокируем с помощью queue.join(), то скрипт не обратит внимание на нажатия клавиш Ctrl+C для перерывания программы. Воспользуемся данным решением.

# -*- coding: utf-8 -*-
import threading
import Queue
import time
import traceback



class Worker(threading.Thread):
    def __init__(self,queue):
        threading.Thread.__init__(self)
        self.__queue = queue

        self.kill_received = False # флаг прекращения работы
    def run(self):
        while not self.kill_received:

            try: item = self.__queue.get_nowait() # ждём данные
            except Queue.Empty: break

            try: self.work(item)
            except Exception: traceback.print_exc()

            time.sleep(0.5)
            self.__queue.task_done() # задача завершена
            self.__queue.put(item) # зациклим
        return
    def work(self,item):
        print item


def main():
    queue = Queue.Queue()
    num_threads = 5 # 5 потоков

    threads = []
    for x in xrange(100):
        queue.put(x) # заносим данные в очередь
    for i in xrange(num_threads):
        t = Worker(queue) # создаем нить
        threads.append(t)
        t.start() # стартуем
        time.sleep(0.1)


    # ждем пока в живых не останется только главный поток
    while threading.activeCount()>1:
        try:
            time.sleep(1)
        except KeyboardInterrupt:
            print 'Ctrl+C received! Sending kill to threads...'
            for t in threads:
                t.kill_received = True # даем сигнал о завершении всем потокам
    print "Done!"
if __name__ == '__main__':
    main()

вторник, 11 декабря 2012 г.

Python Многопоточный клиент

Касательно активности главного потока. Когда, как вам кажется, вы запускаете ОДИН поток, фактически работает уже ДВА потока. Нужно понимать, что количество потоков, активных в данный момент равняется количеству потоков, запущенных в данный момент вами +1 поток, в котором работает основное тело приложения. Лично я стараюсь писать таким образом, чтобы четко отделять основной поток от запущенных мной. Если этого не делать, то возможно преждевременное (как вам кажется) завершение работы приложения, хотя на самом деле приложение отработает именно так, как вы его написали.

Вроде на словах понятно, теперь приступаем к практике. На практике в CPython есть такое понятние как GIL (Global Interpreter Lock). Под сим подразумевается глобальная блокировка интерпритатора в тот момент когда потоки вашего приложения обращаются к процессору. Фактически, в каждый отдельно взятый момент с процессором работает только один поток. В связи с этим максимальное количество потоков, которое вообще можно запустить в стандартном CPython колеблется в районе 350 штук.
В качестве примера будет сделана попытка реализовать многопоточный парсер www.google.com. Как я уже написал выше, для работы будут использованы исключительно стандартные модули, для выполнения задачи понадобятся модули urllib2, urllib, queue, threading, re.

По порядку:

#==================<Имортирование необходимых модулей>==================
import urllib2
#Модуль для работы с протоколом HTTP, высокоуровневый
import urllib
#Модуль для работы с протоколом HTTP, более низкоуровневый чем urllib2,
#фактически из него необходима одна функция - urllib.urlquote
from Queue import Queue
#Модуль, который представляет собой "Pool", фактически это список, в
#котором на нужных местах вставлены замки таким образом, чтобы к нему
#одновременно мог обращаться только один поток
import threading
#Модуль для работы с потоками, из него понадобится только
#threading.active_count, threading.Thread, threading.Thread.start,
#threading.Rlock
import re
#Модуль для работы с регулярными выражениями, его использование выходит
#за пределы статьи
import time
#Модуль для работы со временем, из него нужна только функция sleep
queue = Queue()
#Обязательное присваивание, нужно делать именно так (т.е. импортировать
#класс Queue из модуля Queue и инициализировать его)
#==================</Имортирование необходимых модулей>=================

#==============================<Настройки>==============================
PROXY = "10.10.31.103:3128"
#Во время написания статьи сижу за прокси-сервером, поэтому в статье
#затрагивается и этот вопрос, этой строкой обьявляется глобальная
#переменная PROXY, в которой находится адрес прокси-сервера. Для работы
#напрямую необходимо указать значение None
HEADERS = {"User-Agent" : "Opera/9.64 (Windows NT 5.1; U; en) Presto/2.1.1",
           "Accept" : "text/html, application/xml;q=0.9, application/xhtml+xml, image/ png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1",
           "Accept-Language" : "ru,uk-UA;q=0.9,uk;q=0.8,en;q=0.7",
           "Accept-Charset" : "iso-8859-1, utf-8, utf-16, *;q=0.1",
           "Accept-Encoding" : "identity, *;q=0",
           "Connection" : "Keep-Alive"}
#Для того чтобы получить страницу с www.google.com НЕОБХОДИМО использовать
#заголовки браузера, они представлены выше в ассоциативном массиве HEADERS,
#соответствуют реальным заголовкам браузера Opera с маленько модификацией, эти
#заголовки означают что клиент не может принимать zlib compressed data, т.е.
#сжатые данные - не хотел я заморачиваться еще и с разархивироанием страниц, тем
#более что не все сайты их сжимают...
THREADS_COUNT = 10
#В принципе это все настройки приложения, это-количество потоков
DEEP = 30
#Это - значение, которое отвечает за глубину страниц поиска, которые
#нужно просматривать, фактически же определяет собой количество ссылок,
#которые будут собраны сборщиком.
ENCODING = "UTF-8"
#Кодировка ваших файлов (для загрузки данных из файла с запросами и
#последующего их перевода в юникод)
#==============================</Настройки>===================================

LOCK = threading.RLock()
# Вот тут то впервые и затрагивается модуль threading
#создается обьект LOCK, который представляет собой класс threading.RLock из
#модуля threading, это -простейший замок, который запрещает исполнение
#несколькими потоками участка кода который идет после вызова его метода
#acquire() Основным отличием threading.RLock от threading.Lock (тоже класс из
#модуля threading) является то, что каждый поток может обращаться к обьекту
#threading.RLock неограниченное количество раз, обьект threading.Lock может
#вызываться каждым потоком только единожды.

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

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

    def worker():
    # Обьявление функции worker, входных аргументов нет
        global queue
        #Здесь и далее я буду обьявлять функции из глобального пространства
        #имен в локальном для лучшей читабельности кода, хотя в написании
        #софта такое делать строго не рекомендую (!)
        while True:
        #Запуск бесконечного цикла, в котором будет происходить работа
            try:
            #Обработка ошибок, блок try/except, когда обработается
            #ошибка QueueEmpty это значит, что список задач пуст, и поток
            #должен завершить свою работу
                target_link =  queue.get_nowait()
                #Эта строчка олицетворяет собой получение задачи потоком из
                #списка задач queue
            except Exception, error:
            #сам перехват ошибки
                return
                #Завершение работы функции
            parsed_data = get_and_parse_page(target_link)
            #Позже будет реализована функция, которая будет получать
            #страницу и доставать из нее необходимые значения
            if parsed_data != "ERROR":
            #Проверка на то, была ли получена страница
                write_to_file(parsed_data)
                #Также будет реализована функция для записи собранных данных в файл
            else:
                queue.put(target_link)
                #Если страница не была получена, то забрасываем ее обратно в queue

Главное, что нужно четко усвоить — это алгоритм работы самого потока, и что именно потоки должны обрабатывать независимо друг от друга. Итого, задачи потока очень просты — получить ссылку на страницу поиска, передать ее в функцию-обработчик, из которой вернутся ссылки на найденные сайты а также title этих сайтов, после записать ссылки и title в файл (все это будет находиться в parsed_data).

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

def write_to_file(parsed_data):
#Обявление функции write_to_file, аргумент –массив данных для записи
    global LOCK
    global ENCODING
    LOCK.acquire()
    #"Накидывание замка", следующий далее участок кода может выполнятся
    #только одним потоком в один и тот же момент времени
    with open("parsed_data.txt", "a") as out:
    #Используется with statement, открывается файл parsed_data.txt с
    #правами "a", что означает дозапись в конец файла, и присваиваевается
    #хэндлеру на файл имя out (я так привык)
        for site in parsed_data:
        #Проход циклом по всем элементам parsed data, имя активного в
        #данный момент элемента будет site
            link, title = site[0], site[1]
            #Присваивание переменным link и title значений из кортежа site
            title = title.replace("<em>", "").replace("</em>", "").replace("<b>", "").replace("</b>", "")
            #.replace -это замена HTML-тэгов, которые проскакивают в title и совершено не нужны
            out.write(u"{link}|{title}\n".format(link=link, title=title).encode("cp1251"))
            #Производится сама запись в файл, используется оператор форматирования
            #строк .format, в отличие от % он поддерживает именованные аргументы, чем я и не
            #преминул воспользоваться, таким образом в файл пишется строка вида:
            #ссылка на сайт | title страницы\n -символ переноса строки(все это переводится
            #из юникода в cp1251)
    LOCK.release()
    #"Отпирание"  замка, в противном случае ни один из следующих
    #потоков не сможет работать с этим участком кода. По-хорошему, тут тоже нужно
    #сделать обработку ошибок, но это учебный пример, да и ошибка там может
    #возникнуть (после добавки замка в этот участок кода) только если во время
    #работы приложения выставить атрибут “только чтение” для данного пользователя
    #относительно файла parsed_data.txt

Далее идет реализация функции get_and_parse_page:

def get_and_parse_page(target_link):
#Обьявление функции, аргумент – ссылка на страницу
    global PROXY
    #Указывает на то, что в данной функции используется переменная PROXY
    #из глобального пространства имен
    global HEADERS
    #То же и для переменной Headers
    if PROXY is not None:
    #Если значение PROXY не равно None
        proxy_handler = urllib2.ProxyHandler( { "http": ""+PROXY+"/" } )
        #Создается Прокси-Хэндлер с указанным прокси
        opener = urllib2.build_opener(proxy_handler)
        #Далее создается opener c созданным ранее Прокси-Хэндлером
        urllib2.install_opener(opener)
        #И наконец-то он устанавливается, теперь нет необходимости в
        #шаманствах, все запросы в которых будет использоваться urllib2
        #(в пределах этой функции будут направляться через указанный ранее
        #PROXY)
    page_request = urllib2.Request(url=target_link, headers=HEADERS)
    #Создается обьект Request, который олицетворяет собой Request instance,
    #фактически это GET запрос к серверу с указанными параметрами, мне
    #же необходимо использовать заголовки...
    try:
    #Обработка всех возможных ошибок, возникающих во время получения
    #страницы, это нехорошо, но лучше чем полное отсутствие обработки
        page = urllib2.urlopen(url=page_request).read().decode("UTF-8", "replace")
        #Переменной page присваиваем прочитанное значение страницы запроса, переведенное
        #в unicode из кодировки UTF-8 (кодировка, используемая на www.google.com) (в
        #Python 2.6 unicode -это отдельный тип данных(!))
    except Exception ,error:
    #Сам перехват ошибки и сохранение ее значения в переменную error
        print str(error)
        #Вывод ошибки в консоль, прведварительно переведя ее в строку
        #(просто на всякий случай)
        return "ERROR"
        #Возврат из функции в том случае, если во время работы возникла ошибка
    harvested_data = re.findall(r'''\<li\ class\=g\>\<h3\ class\=r\>\<a\ href\=\"(.*?)".*?>(.*?)\<\/a\>\<\/h3\>''', page)
    #Сбор со страницы поиска ссылок и title найденных страниц
    #Очистка данных от результатов поиска по блогам, картинкам и др. сервисам гугла
    for data in harvested_data:
    #Для каждого элемента массива harvested_data присвоить ему имя data
        if data[0].startswith("/"):
        #Если нулевой элемент массива data(ссылка) начинается с символа /
            harvested_data.remove(data)
            #Удаляем его из массива harvested_data
        if ".google.com" in data[0]:
        #Если нулевой элемент массива data(ссылка) имеет в себе .google.com
            harvested_data.remove(data)
            #Также удаляем его из массива harvested_data
    return harvested_data
    #Возвращаем собранные значения из функции

Наконец-то дошла очередь до реализации основного тела приложения:

def main():
#Обявление функции, входных аргментов нет
    print "STARTED"
    #Вывод в консоль о начале процесса
    global THREADS_COUNT
    global DEEP
    global ENCODING
    #Обьявляние о том что эти переменные будут использоваться
    #из глобального пространства имен
    with open("requests.txt") as requests:
    #Открываем файл requests в котором находятся запросы к поисковику
         for request in requests:
         #На данном файлхэндлере доступен итератор, поэтому можно
         #пройтись по файлу циклом, без загрузки файл в оперативку, но это
         #тоже не важно, я все равно его туда загружу:)
                request = request.translate(None, "\r\n").decode(ENCODING, "replace")
                #Очистка запроса от символов конца строки а также их
                #перевод в юникод (с заменой конфликтных символов)
                empty_link = "www.google.com/search?hl=ru&client=opera&rls=ru&hs=67v&q={request}&start={N}&sa=N"
                #Это пустой адрес страницы поиска, отформатирован
                for i in xrange(0, DEEP, 10):
                #Проход итератором по диапазону #чисел от 0 до DEEP,
                #который представляет собой максимальную глубину поиска с
                #шагом в 10, т.е. получаем из этого диапазона только
                #числа десятков, т.е. 10, 20, 30 (как идет поиск у гугла)
                     queue.put(empty_link.format(request=request.encode("UTF-8"), N=i))
                     #Добавление в очередь каждой сгенерированной ссылки
                     #и перевод её в кодировку UTF-8 (для гугла)
    for _ in xrange(THREADS_COUNT):
    #Проход циклом по диапазону чисел количества потоков
        thread_ = threading.Thread(target=worker)
        #Создается поток, target-имя функции, которая являет собой
        #участок кода, выполняемый многопоточно
        thread_.start()
        #Вызывается метод start() , таким образом поток запускается
    while threading.active_count() >1:
    #До тех пор, пока количество активных потоков больше 1 (значит,
    #запущенные потоки продолжают работу)
        time.sleep(1)
        #Основной поток засыпает на 1 секунду
    print "FINISHED"
    #Вывод в консоль о завершении работы приложения

В итоге получаем нормально работающий многопоточный парсер.

Код:
Эта статья + исходники: sendspace.com/file/mw0pac
Код с русскими коментариями: dumpz.org/15202/

Добавлю свои 5 копеек: никто не запрещает разделить задачу еще больше — сделать 3 очереди и 3 типа потоков (скачка, парсинг, сохранение). Это позволит начать обработку входных данных не дожидаясь окончательной их загрузки (если потоки запустить до начала загрузки данных в очередь) и сильнее разнести части кода (например для увеличения кол-ва разработчиков). Дальше можно нитки демонизировать, что позволит оставить их болтаться до тех пор пока не закончится основной поток. Забирать данные в этом случае можно и проще и надежнее:
в нитке остаётся только бесконечно крутить:
while True:
# забираем данные из очереди дожидаясь пока они там будут
data = queueN.get()
# делаем свое грязно дело
do_some_work(data)
# помечаем сделанную работу в очереди
queue.task_done()

в основной поток будет выглядеть примерно вот так:
queue1 = Queue()
queue2 = Queue()
queue3 = Queue()

# тип 1
for _ in range(NUMBER):
thread_ = threading.Thread(target=worker)
thread_.setDaemon(True) # для питона 2.5 (в 2.6+ есть обратносовместимый метод)
thread_.start()
# тип 2
for _ in range(NUMBER):
thread_ = threading.Thread(target=worker2)
thread_.setDaemon(True)
thread_.start()

# тип 3
for _ in range(NUMBER):
thread_ = threading.Thread(target=worker3)
thread_.setDaemon(True)
thread_.start()

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

# ждем когда закончатся все очереди
queue1.join()
queue2.join()
queue3.join()

Все, после того как третья очередь завершится основной поток выйдет и грохнет все висящие нитки.