-
Notifications
You must be signed in to change notification settings - Fork 5
/
part16.tex
889 lines (629 loc) · 46.6 KB
/
part16.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
% part 16
\section{Демонизация Twisted\label{sec:part16}}
\subsection{Введение}
Серверы, которые мы написали до сих пор, запускались только
в терминальном окне, с выводом на экран через оператор
print. Это подходит для программы на стадии разработки, но
это едва ли подходит для релизов. Хорошо написанная программа,
готовая для выкладке, должа:
\begin{enumerate}
\item Запускаться как
\href{http://en.wikipedia.org/wiki/Daemon\_%28computer_software%29}{демон}, отсоединяясь от
любой терминальной или пользовательской сессии. Сервис
не должен завершаться после того, как
администратор отлогился.
\item Отправлять отладочный и ошибочный вывод во
множество ротируемых лог файлов или в сервис
\href{http://en.wikipedia.org/wiki/Syslog}{syslog}.
\item Убирать лишние привилегии, например, переключаться к
к низкопривелигированному пользователю до запуска.
\item Записывать свой
\href{http://en.wikipedia.org/wiki/Process\_ID}{pid} в файл так, чтобы администратор
мог без проблем
\href{http://en.wikipedia.org/wiki/Kill%28%29}{отправить сигналы} в демон.
\end{enumerate}
Мы можем добраться до всех этих вещей,
используя скрипт twistd, предоставляемый Twisted. Но сначала,
мы должны быть немного поменять наш код.
\subsection{Концепции}
%Understanding twistd will require learning a few new concepts in Twisted, the most important being a Service. As usual, several of the new concepts are accompanied by new Interfaces.
Для того, чтобы понять twisted, необходимо изучить несколько
новых понятий в Twisted, наиболее важное из них - Service. Как всегда,
несколько новых концепций сопровождаются новыми Interface'ми.
\subsubsection{IService}
%The IService interface defines a named service that can be started and stopped. What does the service do? Whatever you like — rather than define the specific function of the service, the interface requires only that it provide a small set of generic attributes and methods.
Интерфейс
\href{http://twistedmatrix.com/trac/browser/tags/releases/twisted-10.0.0/twisted/application/service.py#L87}{IService} опредлеяет именнованный сервис,
который может быть запущен и остановлен.
Интерфейс требует наличия небольшого
множества основных атрибутов и методов.
Существует два обязательных атрибута: name и running.
Атрибут name - это просто строка, например 'fastpoetry', или
None, если вы не хотите давать Вашему сервису название.
Атрибут running - булево значение, которое устанавливается в true,
если сервис успешно запущен.
Мы собираемся коснуться лишь некоторых методов IService.
Мы пропустим некоторые, являющиеся очевидными, и другие, которые
наиболее продвинуты и в основном не используются в простых
Twisted программах. Два основных метода IService -
\href{http://twistedmatrix.com/trac/browser/tags/releases/twisted-10.0.0/twisted/application/service.py#L130}{startService} и
\href{http://twistedmatrix.com/trac/browser/tags/releases/twisted-10.0.0/twisted/application/service.py#L135}{stopService}:
\begin{verbatim}
def startService():
"""
Start the service.
"""
def stopService():
"""
Stop the service.
@rtype: L{Deferred}
@return: a L{Deferred} which is triggered when the service has
finished shutting down. If shutting down is immediate, a
value can be returned (usually, C{None}).
"""
\end{verbatim}
Что эти методы делают будет зависеть от сервиса. Например,
метод startService может любое из:
\begin{itemize}
\item Загружать некоторые конфигурационные данные
\item Инициализировать базу данных
\item Начать слушать порт
\item Ничего не делать
\end{itemize}
Метод stopService может:
\begin{itemize}
\item Сохранять некоторое состояние
\item Закрывать открытие соединения к базе данных
\item Завершать слушать порт
\item Ничего не делать
\end{itemize}
Когда мы пишем наши пользовательские сервисы, мы должны
соответсвующе реализовать эти методы.
Для некоторых общих случаев, подобно прослушиванию порта,
Twisted предоставляет готовые сервисы, которые мы можем использовать.
Заметим, что stopService может опционально возвратить deferred,
который нужно активизировать, когда сервис окончательно
прекратил работать. Это позволяет нашим сервисам убрать за собой
до того как приложение завершится. Если Ваш сервис сразу же завершается,
вы можете возвратить None вместо deferred'а.
Сервисы могут быть организованы в коллекции, которые вместе
стартуют и завершаются. Последний метод IService, на который мы будем
смотреть -
\href{http://twistedmatrix.com/trac/browser/tags/releases/twisted-10.0.0/twisted/application/service.py#L107}{setServiceParent}, добавляет Service в коллекцию:
\begin{verbatim}
def setServiceParent(parent):
"""
Set the parent of the service.
@type parent: L{IServiceCollection}
@raise RuntimeError: Raised if the service already has a parent
or if the service has a name and the parent already has a child
by that name.
"""
\end{verbatim}
Любой сервис может иметь родительский, что означает, что
сервисы могут быть организованы в иерархию. И это подводит нас
к следующему Interface'у, на который мы собираемся сегодня посмотреть.
\subsubsection{IServiceCollection}
The IServiceCollection interface defines an object which can contain IService objects. A service collection is a just plain container class with methods to:
Интерфейс \href{http://twistedmatrix.com/trac/browser/tags/releases/twisted-10.0.0/twisted/application/service.py#L203}{IServiceCollection} определяет объект, который
может содержать объекты IService. Коллекция сервисов - просто
обычный контейнерный класс с методами:
\begin{itemize}
\item Искать сервис по названию (\href{http://twistedmatrix.com/trac/browser/tags/releases/twisted-10.0.0/twisted/application/service.py#L212}{getServiceNamed})
\item Пройтись по всем сервисам в коллекции (\href{http://twistedmatrix.com/trac/browser/tags/releases/twisted-10.0.0/twisted/application/service.py#L222}{\_\_iter\_\_})
\item Добавить сервис в коллекцию (\href{http://twistedmatrix.com/trac/browser/tags/releases/twisted-10.0.0/twisted/application/service.py#L227}{addService})
\item Удалить сервис из коллекции (\href{http://twistedmatrix.com/trac/browser/tags/releases/twisted-10.0.0/twisted/application/service.py#L236}{removeService})
\end{itemize}
Заметим, что реализация IServiceCollection не
является автоматически реализацией IService,
но нет препятсвий одному классу реализовать
оба интерфейса (и мы увидим пример).
\subsubsection{Application}
Twisted Application не определяется отдельным интерфейсом.
Скорее, объект Application требуется для реализации обоих
IService и IServiceCollection, также как и для нескольких других,
которые мы собираемся рассмотреть.
Приложение - это сервис верхнего уровня, который представляет
наше полное Twisted приложение. Все другие сервисы в вашем
демоне будут детьми (или внуками, и т.д.) объекта Application.
Редко, когда надо реализовывать свое собственное Application.
Twisted предоставляет реализацию, которую мы будем
сегодня использовать.
\subsubsection{Twisted логирование}
Twisted включает свое собственную инфраструктуру логирования в модуле
\href{http://twistedmatrix.com/trac/browser/tags/releases/twisted-10.0.0/twisted/python/log.py}{twisted.python.log}. Основные API для записи в лог простые, поэтому мы просто
включим небольшой пример, расположенный в
basic-twisted/log.py, и вы можете просмотреть интересующие Вас детали
в соответсвующем модуле Twisted.
Мы не будем вам докучать, показывая API для установки логирующих
обработчиков, поскольку twistd делает это за нас.
\subsection{FastPoetry 2.0}
Хорошо, давайте взглянем на некоторый код. Мы обновили
наш поэтический сервер для того, чтобы запускать его с
помощью twistd. Его исходный код расположен в
\href{http://github.com/jdavisp3/twisted-intro/blob/master/twisted-server-3/fastpoetry.py#L1}{twisted-server-3/fastpoetry.py}. Сначала мы посмотрим на
\href{http://github.com/jdavisp3/twisted-intro/blob/master/twisted-server-3/fastpoetry.py#L9}{PoetryProtocol}:
\begin{verbatim}
class PoetryProtocol(Protocol):
def connectionMade(self):
poem = self.factory.service.poem
log.msg('sending %d bytes of poetry to %s'
% (len(poem), self.transport.getPeer()))
self.transport.write(poem)
self.transport.loseConnection()
\end{verbatim}
%Notice instead of using a print statement, we’re using the twisted.python.log.msg function to record each new connection.
Заметьте, что вместо использования оператора print,
мы используем функцию twisted.python.log.msg для
записи каждого соединения.
Далее класс \href{http://github.com/jdavisp3/twisted-intro/blob/master/twisted-server-3/fastpoetry.py#L19}{PoetryFactory}:
\begin{verbatim}
class PoetryFactory(ServerFactory):
protocol = PoetryProtocol
def __init__(self, service):
self.service = service
\end{verbatim}
%As you can see, the poem is no longer stored on the factory, but on a service object referenced by the factory. Notice how the protocol gets the poem from the service via the factory. Finally, here’s the service class itself:
Как вы видите, поэма больше не хранится в PoetryFactory,
вместо этого она хранится в сервисном объекте, на которую
есть ссылка из PoetryFactory. Заметьте, как PoetryProtocol получает
поэму из service через factory. Наконец, далее сам
\href{http://github.com/jdavisp3/twisted-intro/blob/master/twisted-server-3/fastpoetry.py#L27}{сервисный класс}:
\begin{verbatim}
class PoetryService(service.Service):
def __init__(self, poetry_file):
self.poetry_file = poetry_file
def startService(self):
service.Service.startService(self)
self.poem = open(self.poetry_file).read()
log.msg('loaded a poem from: %s' % (self.poetry_file,))
\end{verbatim}
Так же как и с другими многочисленными классами Interface,
Twisted предоставляет базовый класс, который мы можем
использовать для того, чтобы сделать нашу
собственную реализацию используя поведение по
умолчанию. Здесь мы используем класс
\href{http://twistedmatrix.com/trac/browser/tags/releases/twisted-10.0.0/twisted/application/service.py#L154}{twisted.application.service.Service} для реализации
нашего PoetryService.
Базовый класс предоставляет реализацию по умолчанию
всех необходимых методов, поэтому нам нужно
реализовать только пользовательское поведение. В нашем случае,
мы просто перегружаем startService для
загрузки файла с поэзией. Заметим, что мы все равно вызываем
метод базового класса (который устанавливает для нас
атрибут running).
Другая сторона здесь плохо упомянута. Объект PoetryService
не знает ничего о деталях PoetryProtocol. Сервис только
должен загрузить поэму и обеспечить доступ до него любого
объекта, который нуждается в поэме. Другими словами,
PoetryService обеспокоен только высокоуровневыми деталями
в преоставлении поэзии, а не низкоуровневыми деталями
отправки по TCP соединению. Таким образом, этот сервисный
класс мог бы быть использован другим протоколом, скажем UDP или
XML-RPC. В случае нашего простого сервиса пользы от этого мало,
но можно представить преимущества в более реалистичной сервисной реализации.
Если бы это была обычная Twisted программа, весь код,
на который мы посмотрели до сих пор не был бы в одном файле,
он был бы в нескольких разных модулях (возможно,
fastpoetry.protocol и fastpoetry.service). Но, следуя нашей
обычной практике создания примеров самодостаточными, мы
включаем все что нам надо в один скрипт.
\subsubsection{Twisted tac файлы}
Оставшийся код в скрипте содержит то,
что обычно бывает содержимым Twisted tac файла.
Файл tac - Twisted Application Configuration файл, который
говорит twistd как создавать приложение. Поскольку это
конфигурационный файл, то он ответственнен за выбор настроек (подобно
номерам портов, расположениями файлов с поэзией и т.д.) для
запуска приложения определенным образом. Другими словами,
tac файл представляет определенный вид запуска для нашего сервиса (обслужить
эту поэму на этом порту), а не общий скрипт для
запуска любого поэтического сервера.
Если бы мы запускали несколько поэтических сервером на одной
машине, мы бы имели несколько tac файлов для каждого (вот почему
tac файлы обычно не содержат кода общего назначения). В нашем примере,
tac файл сконфигурирован для обслуживания poetry/ecstasy.txt на
порту 10000 loopback интерфейса:
\begin{verbatim}
# configuration parameters
port = 10000
iface = 'localhost'
poetry_file = 'poetry/ecstasy.txt'
\end{verbatim}
Заметим, что twistd ничего не знает об этих переменных,
мы их определяем здесь для того, чтобы сохранить все наши
конфигурационные переменные в одном месте. Фактически, twistd заботится
только об одной переменной во всем файле, как мы это вскоре поймем.
Далее мы \href{http://github.com/jdavisp3/twisted-intro/blob/master/twisted-server-3/fastpoetry.py#L44}{начинаем} построение нашего приложения:
\begin{verbatim}
# this will hold the services that combine to form the poetry server
top_service = service.MultiService()
\end{verbatim}
Наш поэтический сервер состоит из двух
сервисов: PoetryService, который мы определили выше, и
встроенный Twisted сервис, который создает слушающий сокет
нашей поэмы, из которого она будет предоставляться. Поскольку
эти два сервисы очевидным образом связаны друг с другом,
мы сгруппируем их вмести, используя \href{http://twistedmatrix.com/trac/browser/tags/releases/twisted-10.0.0/twisted/application/service.py#L253}{MultiService} - класс Twisted,
который реализует IService вместе с IServiceCollection.
Будучи сервисной коллекцией, MultiService сгруппирует
наши два поэтических сервиса вместе. И будучи сервисом,
MultiService запустит оба дочерних сервиса, когда
MultiService сам запустится, и остановит их, когда сам остановится.
Давайте добавим наш первый поэтический сервис в коллекцию:
\begin{verbatim}
# the poetry service holds the poem. it will load the poem when it is
# started
poetry_service = PoetryService(poetry_file)
poetry_service.setServiceParent(top_service)
\end{verbatim}
Эта достаточно удобная вещь. Мы только создаем
PoetryService и затем добавляем его в коллекцию,
используя setServiceParent, который унаследован из
базового класса Twisted. Затем мы \href{http://github.com/jdavisp3/twisted-intro/blob/master/twisted-server-3/fastpoetry.py#L53}{слушаем} TCP сокет:
\begin{verbatim}
# the tcp service connects the factory to a listening socket. it will
# create the listening socket when it is started
factory = PoetryFactory(poetry_service)
tcp_service = internet.TCPServer(port, factory, interface=iface)
tcp_service.setServiceParent(top_service)
\end{verbatim}
Twisted предоставляет сервис TCPServer для создания
слушающего TCP сокета, присоединенного к
произвольной factory (в нашем случае - PoetryFactory).
Мы не вызываем reactor.listenTCP напрямую, поскольку
работа tac файла - сделать наше приложение готовым к старту
не запуская его. TCPServer создаст сокет после того, как
он запустится скриптом twistd.
Вы могли заметить, что мы не беспокоились заданием
названия сервиса. Именование сервисов не является
обязательным, а наоборот - опциональным свойством,
которое вы можете использовать, если хотите
<<просматривать>> сервисы во время запуска. Поскольку
нам не нужно этого делать в нашем маленьком приложении,
то мы не беспокоимся об этом.
Хорошо, мы получили оба наших сервиса, объединенных
в коллекцию. Теперь мы только сделаем Application и
добавим нашу коллекцию в него:
\begin{verbatim}
# this variable has to be named 'application'
application = service.Application("fastpoetry")
# this hooks the collection we made to the application
top_service.setServiceParent(application)
\end{verbatim}
Единственная переменная в этом скрипте, которая
интересует twistd - переменная application. Это то, как
twistd найдет приложение, которое предполагается запустить (поэтому
переменная должна иметь название "application"). И когда
приложение запустится, все сервисы, которые мы туда добавили,
также будут запущены.
На рисунке \ref{fig:application} показана структура приложения,
которое мы только что построили:
\begin{figure}[h]
\begin{center}
\includegraphics[width=0.5\textwidth]{images/application.pdf}
\caption{Структура fastpoetry приложения\label{fig:application}}
\end{center}
\end{figure}
\subsubsection{Запуск сервера}
Давайте запустим наш сервер.
Поскольку наш скрипт - tac файл, то нам нужно запустить его с помощью twistd.
Конечно же, tac файлы - обычные Python скрипты.
Давайте сначала запустим tac файл без twistd,
просто используя интерпретатор Python и посмотрим, что получается:
\begin{verbatim}
python twisted-server-3/fastpoetry.py
\end{verbatim}
Если вы сделаете это, вы обнаружите, что ничего не произошло!
Как мы говорили до этого, работа tac файла - сделать приложение
готовым для запуска, без его запуска. В качестве напоминание об этом
специальном назначении tac файлов, некоторые люди называют их с
расширением .tac вместо .py. Скрипт twistd не заботится о
расширении файла.
Давайте запустим наш сервер по-настоящему, используя twistd:
\begin{verbatim}
twistd --nodaemon --python twisted-server-3/fastpoetry.py
\end{verbatim}
После запуска такой команды вы увидите похожий вывод:
\begin{verbatim}
2010-06-23 20:57:14-0700 [-] Log opened.
2010-06-23 20:57:14-0700 [-] twistd 10.0.0 (/usr/bin/python 2.6.5) starting up.
2010-06-23 20:57:14-0700 [-] reactor class: twisted.internet.selectreactor.SelectReactor.
2010-06-23 20:57:14-0700 [-] __builtin__.PoetryFactory starting on 10000
2010-06-23 20:57:14-0700 [-] Starting factory <__builtin__.PoetryFactory instance at 0x14ae8c0>
2010-06-23 20:57:14-0700 [-] loaded a poem from: poetry/ecstasy.txt
\end{verbatim}
Здесь нужно отметить несколько вещей:
\begin{enumerate}
\item вы видите вывод системы логирования Twisted, в том числе вызов
log.msg в PoetryFactory. Но мы не устанавливали logger в нашем tac
файле, так как twistd должен это сделать для нас.
\item вы также можете увидеть, что наши два основных сервиса, PoetryService и TCPServer,
запустились.
\item Командная строка не возвращается. Это означает, что наш сервер
не запущен как демон. По умолчанию, twistd запускает сервер как демон (это
основная причина существования twistd), но если вы используете опцию
--nodaemon, то twistd запустит ваш сервер как обычный консольный процесс, и
лог будет выводится на стандартный вывод. Это полезно при отладки ваших
tac файлов.
\end{enumerate}
Теперь протестируем сервер путем скачивания поэмы одним из
наших поэтических клиентов или даже netcat'м:
\begin{verbatim}
netcat localhost 10000
\end{verbatim}
Такая команда должна скачать поэму из сервера, и вы
увидите новую запись в логе подобную такой:
\begin{verbatim}
2010-06-27 22:17:39-0700 [__builtin__.PoetryFactory] sending 3003 bytes of poetry to IPv4Address(TCP, '127.0.0.1', 58208)
\end{verbatim}
Эта запись появляется в результате вызова log.msg в
PoetryProtocol.connectionMade. Если вы сделаете еще запросы к
серверу, то на каждый запрос вы увидите новую запись в логе.
Теперь остановим сервер, нажав Ctrl-C. вы должны увидеть строки подобные:
\begin{verbatim}
^C2010-06-29 21:32:59-0700 [-] Received SIGINT, shutting down.
2010-06-29 21:32:59-0700 [-] (Port 10000 Closed)
2010-06-29 21:32:59-0700 [-] Stopping factory <__builtin__.PoetryFactory instance at 0x28d38c0>
2010-06-29 21:32:59-0700 [-] Main loop terminated.
2010-06-29 21:32:59-0700 [-] Server Shut Down.
\end{verbatim}
Как вы видите, Twisted не просто разрушается, а завершает сам
себя чисто и говорит вам о завершении в логе. Заметим, что два
наших основных сервис также сами себя завершают.
Хорошо, теперь запустим сервер еще раз:
\begin{verbatim}
twistd --nodaemon --python twisted-server-3/fastpoetry.py
\end{verbatim}
Откройте другую консоль и зайдите в директорию twisted-intro.
В директории должен появится файл twistd.pid. Этот файл создан
twistd и содержит идентификатор процесса нашего запущенного
сервера. Попробуйте запустить эту команду для завершения сервера:
\begin{verbatim}
kill `cat twistd.pid`
\end{verbatim}
Заметьте, что twistd стирает файл с pid'м, когда наш сервис завершается.
\subsubsection{Реальный демон}
Теперь давайте запустим наш сервер как
демон, который даже проще сделать, поскольку
по умолчанию twistd демонизирует процесс:
\begin{verbatim}
twistd --python twisted-server-3/fastpoetry.py
\end{verbatim}
На этот раз мы сразу же получаем командную строку обратно.
Если вы посмотрите на содержимое вашей директории,
то в дополнение к файлу twistd.pid, созданное для сервера,
который мы только что запустили, вы увидите файл с
логами --- twistd.log, который отображает тоже самое, что и
ранее отображалось в консоли.
При запуске демона, twistd устанавливает обработчик логов,
который пишет в файл вместо стандартного вывода. Лог-файл
по умолчанию --- это twistd.log, расположенный в той же
директории, где вы запустили twistd, но, если вы хотите, то
можете менять файл использованием опции \textit{\symbol{45}\symbol{45}logfile}.
Обработчик, который устанавливает twistd также ротирует лог,
когда размер превышает один мегабайт.
Вы можете увидеть запущенный сервер, просмотрев список
всех процессов в вашей системе. Продолжим и проверим
сервер, скачав еще одну поэму. Вы увидите новые записи
в лог файле для каждого запроса на скачивание поэмы.
Поскольку сервер больше не присоединен к консоли (и
другим процессам, кроме init), вы не можете завершить его
нажав Ctrl-C. Как реальный демон, процесс продолжит работу,
даже если вы отлогинетесь. Но мы все еще можем использовать
файл twistd.pid, чтобы остановить процесс:
\begin{verbatim}
kill `cat twistd.pid`
\end{verbatim}
И когда это происходит, то в логе появляются
сообщения об остановке сервера, файл twistd.pid
удаляется, и наш сервер завершается.
Это хорошая идея проверить другие опции twistd.
Например, вы можете сказать twistd переключиться на
другого пользователя или группу до запуска демона (это
типичный случай понижения привелегий Вашего сервера,
которые ему не нужны, в качестве меры предосторожности).
Мы не будем беспокоиться тщательным изучением этих
дополнительных опций, вы можете найти их, используя
опцию --help.
\subsection{Системы плагинов в Twisted}
Хорошо, теперь мы можем использовать twistd для запуска
наших серверов как подлинных демонов. Это все очень
здорово, и тот факт, что наш ``конфигурационные'' файлы
являются реально файлами с исходниками на Python'е, дает
нам огромную гибкость во многих вещах. Но, нам не всегда
нужно так много гибкости. Для наших поэтических серверов,
обычно мы заботимся только о нескольких опциях:
\begin{enumerate}
\item Поэма для обслуживания
\item Порт для обслуживания поэмы
\item Интерфейс для прослушивания
\end{enumerate}
Создавать новые tac файлы с простыми изменениями
переменных кажется чрезмерным. Система плагинов в
Twisted позволяет нам сделать тоже самое.
Twisted плагины предоставляют способ
определения именованных приложений с
пользовательскими опциями, которые
twistd может автоматически распознать и
запустить. Twisted сам построен из
множества встроенных плагинов. вы можете
их увидеть, запуская twisted без аргументов. Попробуйте
запустить сейчас вне директории twisted-intro.
После раздела с помощью, вы увидите вывод подобный:
\begin{verbatim}
...
ftp An FTP server.
telnet A simple, telnet-based remote debugging service.
socks A SOCKSv4 proxy service.
...
\end{verbatim}
Каждая строка показывает один из встроенных плагинов из Twisted.
И вы можете любой из них, используя twistd.
Каждый плагин также имеет свое множество опций, которые вы
можете изучить, используя опицию --help. Давайте посмотрим,
какие опции имеет плагин ftp:
\begin{verbatim}
twistd ftp --help
\end{verbatim}
Заметим, что нужно поместить опцию --help после команды
ftp, поскольку вам нужны опции ftp, а не опции twistd.
вы можете запустить ftp сервер с помощью twistd также,
как мы запускали наш поэтический сервер. Но, поскольку это
плагин, то мы запускаем его по его названию:
\begin{verbatim}
twistd --nodaemon ftp --port 10001
\end{verbatim}
Эта команда запускает ftp плагин в недемонизированном виде
на порту 10001. Заметим, что опция twistd
\textit{\symbol{45}\symbol{45}nodaemon} помещается
до названия плагины, в то время как опции, специфичные для плагина,
помещаются после его названия. Так же как и наш поэтический
сервер, вы можете остановить плагин с помощью Ctrl-C.
Ok, давайте превратим наш поэтический сервер в Twisted
плагин. Но сначала, нам нужно осознать несколько
новый концепций.
\subsubsection{IPlugin}
Любой Twisted плагин должен реализовывать интерфейс
\href{http://twistedmatrix.com/trac/browser/tags/releases/twisted-10.0.0/twisted/plugin.py#L38}{twisted.plugin.IPlugin}.
Если вы посмотрите на его объявление, то
вы обнаружите, что он в действительности не определяет никакие методы.
Реализация IPlugin - просто способ для плагина, сказать ``Привет, я - плагин!''
так, чтобы twistd мог найти его. Конечно же, чтобы его можно было использовать,
он должен реализовать некоторый другой интерфейс, и мы вскоре
до этого доберемся.
Но, как узнать, дейcтвительно ли объект реализует пустой интерфейс?
Пакет zope.interface имеет функцию, которая позволяет указать то, что
определенный класс реализует определенный интерфейс. Мы рассмотрим пример
использования этой функции в plugin-версии нашего поэтического сервера.
\subsubsection{IServiceMaker}
В дополнение к IPlugin, наш плагин будет реализовывать
интерфейс \href{http://twistedmatrix.com/trac/browser/tags/releases/twisted-10.0.0/twisted/application/service.py#L25}{IServiceMaker}. Объект, который реализует IServiceMaker, знает
как создавать IService, который будет формировать основной элемент
запуска application. IServiceMaker определяет три атрибута и метод:
\begin{enumerate}
\item tapname: строка с названием нашего плагина. Аббревиатура ``tap''
означает Twisted Application Plugin. Заметим, что более старые версии Twisted
также использовали pickled application files, называемые ``tapfiles'', но это функциональность
устарела.
\item description: описание плагина, который twistd будет отображать как часть своего help текста.
\item options: объект, который описывает опции командной строки, которые принимает плагин.
\item makeService: метод, который создает новый объект IService
по заданному множеству опций командной строки.
\end{enumerate}
Мы увидим то, как все это соединяется вместе в следующей версии нашего
поэтического сервера.
\subsection{Fast Poetry 3.0}
Теперь мы готовы посмотреть на плагин версию Fast Poetry,
расположенную в
\href{http://github.com/jdavisp3/twisted-intro/blob/master/twisted/plugins/fastpoetry\_plugin.py#L1}{twisted/plugins/fastpoetry\_plugin.py}.
Как вы могли заметить, название директорий отличается от других
примеров. Это потому что twistd требует, чтобы плагин файлы
были расположены в директории twisted/plugins (вам не нужно
создавать никаких \_\_init\_\_.py файлов); вы можете иметь
несколько директорий twisted/plugins в PYTHONPATH,
и twisted все их найдет. Название файла, которое вы используете
для плагина, не имеет значения, но все таки хорошая идея
именовать его согласно тому приложению, которое он представляет,
подобно тому как мы это сделали.
Первая часть нашего плагина состоит из тех же
protocol, factory и service реализаций, как и наш
tac файл. И так же как и раньше, этот код обычно находится
в разных модулях, но мы поместим его в плагин, для того, чтобы
сделать пример самодостаточным.
Далее идет объявление комнадных опций плагина:
\begin{verbatim}
class Options(usage.Options):
optParameters = [
['port', 'p', 10000, 'The port number to listen on.'],
['poem', None, None, 'The file containing the poem.'],
['iface', None, 'localhost', 'The interface to listen on.'],
]
\end{verbatim}
Этот код определяет опции, специфичные для плагина, которые
пользователь может поместить после названия
плагина при использовании twistd. Мы не будем вдаваться в
подробности, так как тут все достаточно понятно. Теперь, мы
перейдем к основной части нашего плагина - классу PoetryServiceMaker:
\begin{verbatim}
class PoetryServiceMaker(object):
implements(service.IServiceMaker, IPlugin)
tapname = "fastpoetry"
description = "A fast poetry service."
options = Options
def makeService(self, options):
top_service = service.MultiService()
poetry_service = PoetryService(options['poem'])
poetry_service.setServiceParent(top_service)
factory = PoetryFactory(poetry_service)
tcp_service = internet.TCPServer(int(options['port']), factory,
interface=options['iface'])
tcp_service.setServiceParent(top_service)
return top_service
\end{verbatim}
Здесь вы видите как функция zope.interface.implements используется для
объявления того, что наш класс реализует оба IServiceMaker и IPlugin.
На этот раз, нам не нужно создавать объект Application, мы только
создаем и возвращаем сервис более высокого уровня, который запустит наш
application, и twistd позаботится обо всем остальном. Заметим то, как мы
используем аргумент options для того, чтобы получить опции
командной строки, специфичные для плагина, переданные twistd.
После объявления класса, осталась только одна вещь, которую надо
сделать:
\begin{verbatim}
service_maker = PoetryServiceMaker()
\end{verbatim}
Скрипт twistd обнаружит экземпляр нашего плагина и использует
его для создания сервиса высокого уровня. В отличие от tac файла,
переменная name, которую мы выбираем, неважна. Важно, что наш
объект реализует оба IPlugin и IServiceMaker.
Посколе того, как мы создали наш плагин, давайте запустим его.
Убедитесь, что вы находитесь в директории twisted-intro или, что
эта директория находится в PYTHONPATH. Затем, попробуйте
запустить twistd. вы должны увидеть, что ``fastpoetry'' в списке
плагинов вместе с описанием из нашего файла с плагином.
Вы также заметите, что создался новый файл dropin.cache в
директории twisted/plugins. Этот файл создается twistd для
ускорения последующих поисков плагинов.
Теперь давайте получим некоторую помощь по использованию
нашего плагина:
\begin{verbatim}
twistd fastpoetry --help
\end{verbatim}
Вы увидите опции, специфичные для плагина fastpoetry в тексте
помощи. И наконец, давайте запустим наш плагин:
\begin{verbatim}
twistd fastpoetry --port 10000 --poem poetry/ecstasy.txt
\end{verbatim}
Эта команда запустит сервер fastpoetry как демон.
Как и раньше, вы увидите оба файла twistd.pid и
twistd.log в текущей директории. После тестирования
нашего сервера, вы можете остановить его:
\begin{verbatim}
kill `cat twistd.pid`
\end{verbatim}
Вот как создавать Twisted плагины.
\subsection{Резюме}
В этой главе мы изучили то, как превращать
Twisted сервера в долгоживущие демоны. Мы
затронули систему логирования Twisted и как
использовать twistd для запуска Twisted приложений
ввиде демонизированных процессов,
как создавать tac конфигурационные файлы,
как создавать и запускать Twisted плагины.
В главе \ref{sec:part17} мы возвратимся к более фундаментальной
теме асинхронного программирования и посмотрим
на другой способ структурирования наших callback'в
в Twisted.
\subsection{Упражнения}
\begin{enumerate}
\item Поменяйте tac файл для обслуживания второй поэмы на другом
порту. Сохраните сервисы для каждой поэмы отдельно, используя другой
объект MultiService.
\item Создайте новый tac файл, который запустит поэтический прокси
сервер.
\item Модифицируйте файл plugin для того, чтобы он принимал
необязательный второй поэтический файл и второй порт, для
обслуживания.
\item Создайте новый плагин для поэтического прокси сервера.
\end{enumerate}