1
|
|
|
# -*- Mode: Python; tab-width: 4 -*- |
2
|
|
|
# $Id$ |
3
|
|
|
# Author: Sam Rushing <[email protected]> |
4
|
|
|
|
5
|
|
|
# ====================================================================== |
6
|
|
|
# Copyright 1996 by Sam Rushing |
7
|
|
|
# |
8
|
|
|
# All Rights Reserved |
9
|
|
|
# |
10
|
|
|
# Permission to use, copy, modify, and distribute this software and |
11
|
|
|
# its documentation for any purpose and without fee is hereby |
12
|
|
|
# granted, provided that the above copyright notice appear in all |
13
|
|
|
# copies and that both that copyright notice and this permission |
14
|
|
|
# notice appear in supporting documentation, and that the name of Sam |
15
|
|
|
# Rushing not be used in advertising or publicity pertaining to |
16
|
|
|
# distribution of the software without specific, written prior |
17
|
|
|
# permission. |
18
|
|
|
# |
19
|
|
|
# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, |
20
|
|
|
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN |
21
|
|
|
# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR |
22
|
|
|
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS |
23
|
|
|
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, |
24
|
|
|
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
25
|
|
|
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
26
|
|
|
# ====================================================================== |
27
|
|
|
|
28
|
|
|
"""Basic infrastructure for asynchronous socket service clients and servers. |
29
|
|
|
|
30
|
|
|
There are only two ways to have a program on a single processor do "more |
31
|
|
|
than one thing at a time". Multi-threaded programming is the simplest and |
32
|
|
|
most popular way to do it, but there is another very different technique, |
33
|
|
|
that lets you have nearly all the advantages of multi-threading, without |
34
|
|
|
actually using multiple threads. it's really only practical if your program |
35
|
|
|
is largely I/O bound. If your program is CPU bound, then pre-emptive |
36
|
|
|
scheduled threads are probably what you really need. Network servers are |
37
|
|
|
rarely CPU-bound, however. |
38
|
|
|
|
39
|
|
|
If your operating system supports the select() system call in its I/O |
40
|
|
|
library (and nearly all do), then you can use it to juggle multiple |
41
|
|
|
communication channels at once; doing other work while your I/O is taking |
42
|
|
|
place in the "background." Although this strategy can seem strange and |
43
|
|
|
complex, especially at first, it is in many ways easier to understand and |
44
|
|
|
control than multi-threaded programming. The module documented here solves |
45
|
|
|
many of the difficult problems for you, making the task of building |
46
|
|
|
sophisticated high-performance network servers and clients a snap. |
47
|
|
|
""" |
48
|
|
|
|
49
|
|
|
import exceptions |
50
|
|
|
import select |
51
|
|
|
import socket |
52
|
|
|
import string |
53
|
|
|
import sys |
54
|
|
|
|
55
|
|
|
import os |
56
|
|
|
if os.name == 'nt': |
57
|
|
|
EWOULDBLOCK = 10035 |
58
|
|
|
EINPROGRESS = 10036 |
59
|
|
|
EALREADY = 10037 |
60
|
|
|
ECONNRESET = 10054 |
61
|
|
|
ENOTCONN = 10057 |
62
|
|
|
ESHUTDOWN = 10058 |
63
|
|
|
else: |
64
|
|
|
from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, ENOTCONN, ESHUTDOWN |
65
|
|
|
|
66
|
|
|
try: |
67
|
|
|
socket_map |
|
|
|
|
68
|
|
|
except NameError: |
69
|
|
|
socket_map = {} |
70
|
|
|
|
71
|
|
|
class ExitNow (exceptions.Exception): |
72
|
|
|
pass |
73
|
|
|
|
74
|
|
|
DEBUG = 0 |
75
|
|
|
|
76
|
|
|
def poll (timeout=0.0, map=None): |
77
|
|
|
global DEBUG |
78
|
|
|
if map is None: |
79
|
|
|
map = socket_map |
80
|
|
|
if map: |
81
|
|
|
r = []; w = []; e = [] |
82
|
|
|
for fd, obj in map.items(): |
83
|
|
|
if obj.readable(): |
84
|
|
|
r.append (fd) |
85
|
|
|
if obj.writable(): |
86
|
|
|
w.append (fd) |
87
|
|
|
r,w,e = select.select (r,w,e, timeout) |
88
|
|
|
|
89
|
|
|
if DEBUG: |
90
|
|
|
print r,w,e |
91
|
|
|
|
92
|
|
|
for fd in r: |
93
|
|
|
try: |
94
|
|
|
obj = map[fd] |
95
|
|
|
try: |
96
|
|
|
obj.handle_read_event() |
97
|
|
|
except ExitNow: |
98
|
|
|
raise ExitNow |
99
|
|
|
except: |
100
|
|
|
obj.handle_error() |
101
|
|
|
except KeyError: |
102
|
|
|
pass |
103
|
|
|
|
104
|
|
|
for fd in w: |
105
|
|
|
try: |
106
|
|
|
obj = map[fd] |
107
|
|
|
try: |
108
|
|
|
obj.handle_write_event() |
109
|
|
|
except ExitNow: |
110
|
|
|
raise ExitNow |
111
|
|
|
except: |
112
|
|
|
obj.handle_error() |
113
|
|
|
except KeyError: |
114
|
|
|
pass |
115
|
|
|
|
116
|
|
View Code Duplication |
def poll2 (timeout=0.0, map=None): |
|
|
|
|
117
|
|
|
import poll |
118
|
|
|
if map is None: |
119
|
|
|
map=socket_map |
120
|
|
|
# timeout is in milliseconds |
121
|
|
|
timeout = int(timeout*1000) |
122
|
|
|
if map: |
123
|
|
|
l = [] |
124
|
|
|
for fd, obj in map.items(): |
125
|
|
|
flags = 0 |
126
|
|
|
if obj.readable(): |
127
|
|
|
flags = poll.POLLIN |
128
|
|
|
if obj.writable(): |
129
|
|
|
flags = flags | poll.POLLOUT |
130
|
|
|
if flags: |
131
|
|
|
l.append ((fd, flags)) |
132
|
|
|
r = poll.poll (l, timeout) |
133
|
|
|
for fd, flags in r: |
134
|
|
|
try: |
135
|
|
|
obj = map[fd] |
136
|
|
|
try: |
137
|
|
|
if (flags & poll.POLLIN): |
138
|
|
|
obj.handle_read_event() |
139
|
|
|
if (flags & poll.POLLOUT): |
140
|
|
|
obj.handle_write_event() |
141
|
|
|
except ExitNow: |
142
|
|
|
raise ExitNow |
143
|
|
|
except: |
144
|
|
|
obj.handle_error() |
145
|
|
|
except KeyError: |
146
|
|
|
pass |
147
|
|
|
|
148
|
|
View Code Duplication |
def poll3 (timeout=0.0, map=None): |
|
|
|
|
149
|
|
|
# Use the poll() support added to the select module in Python 2.0 |
150
|
|
|
if map is None: |
151
|
|
|
map=socket_map |
152
|
|
|
# timeout is in milliseconds |
153
|
|
|
timeout = int(timeout*1000) |
154
|
|
|
pollster = select.poll() |
155
|
|
|
if map: |
156
|
|
|
l = [] |
157
|
|
|
for fd, obj in map.items(): |
158
|
|
|
flags = 0 |
159
|
|
|
if obj.readable(): |
160
|
|
|
flags = select.POLLIN |
161
|
|
|
if obj.writable(): |
162
|
|
|
flags = flags | select.POLLOUT |
163
|
|
|
if flags: |
164
|
|
|
pollster.register(fd, flags) |
165
|
|
|
r = pollster.poll (timeout) |
166
|
|
|
for fd, flags in r: |
167
|
|
|
try: |
168
|
|
|
obj = map[fd] |
169
|
|
|
try: |
170
|
|
|
if (flags & select.POLLIN): |
171
|
|
|
obj.handle_read_event() |
172
|
|
|
if (flags & select.POLLOUT): |
173
|
|
|
obj.handle_write_event() |
174
|
|
|
except ExitNow: |
175
|
|
|
raise ExitNow |
176
|
|
|
except: |
177
|
|
|
obj.handle_error() |
178
|
|
|
except KeyError: |
179
|
|
|
pass |
180
|
|
|
|
181
|
|
|
def loop (timeout=30.0, use_poll=0, map=None): |
182
|
|
|
|
183
|
|
|
if use_poll: |
184
|
|
|
if hasattr (select, 'poll'): |
185
|
|
|
poll_fun = poll3 |
186
|
|
|
else: |
187
|
|
|
poll_fun = poll2 |
188
|
|
|
else: |
189
|
|
|
poll_fun = poll |
190
|
|
|
|
191
|
|
|
if map is None: |
192
|
|
|
map=socket_map |
193
|
|
|
|
194
|
|
|
while map: |
195
|
|
|
poll_fun (timeout, map) |
196
|
|
|
|
197
|
|
|
class dispatcher: |
198
|
|
|
debug = 0 |
199
|
|
|
connected = 0 |
200
|
|
|
accepting = 0 |
201
|
|
|
closing = 0 |
202
|
|
|
addr = None |
203
|
|
|
|
204
|
|
|
def __init__ (self, sock=None, map=None): |
205
|
|
|
if sock: |
206
|
|
|
self.set_socket (sock, map) |
207
|
|
|
# I think it should inherit this anyway |
208
|
|
|
self.socket.setblocking (0) |
209
|
|
|
self.connected = 1 |
210
|
|
|
|
211
|
|
|
def __repr__ (self): |
212
|
|
|
try: |
213
|
|
|
status = [] |
214
|
|
|
if self.accepting and self.addr: |
215
|
|
|
status.append ('listening') |
216
|
|
|
elif self.connected: |
217
|
|
|
status.append ('connected') |
218
|
|
|
if self.addr: |
219
|
|
|
status.append ('%s:%d' % self.addr) |
220
|
|
|
return '<%s %s at %x>' % ( |
221
|
|
|
self.__class__.__name__, |
222
|
|
|
string.join (status, ' '), |
223
|
|
|
id(self) |
224
|
|
|
) |
225
|
|
|
except: |
226
|
|
|
try: |
227
|
|
|
ar = repr(self.addr) |
228
|
|
|
except: |
229
|
|
|
ar = 'no self.addr!' |
230
|
|
|
|
231
|
|
|
return '<__repr__ (self) failed for object at %x (addr=%s)>' % (id(self),ar) |
232
|
|
|
|
233
|
|
|
def add_channel (self, map=None): |
234
|
|
|
#self.log_info ('adding channel %s' % self) |
235
|
|
|
if map is None: |
236
|
|
|
map=socket_map |
237
|
|
|
map [self._fileno] = self |
238
|
|
|
|
239
|
|
|
def del_channel (self, map=None): |
240
|
|
|
fd = self._fileno |
241
|
|
|
if map is None: |
242
|
|
|
map=socket_map |
243
|
|
|
if map.has_key (fd): |
244
|
|
|
#self.log_info ('closing channel %d:%s' % (fd, self)) |
245
|
|
|
del map [fd] |
246
|
|
|
|
247
|
|
|
def create_socket (self, family, type): |
248
|
|
|
self.family_and_type = family, type |
249
|
|
|
self.socket = socket.socket (family, type) |
250
|
|
|
self.socket.setblocking(0) |
251
|
|
|
self._fileno = self.socket.fileno() |
252
|
|
|
self.add_channel() |
253
|
|
|
|
254
|
|
|
def set_socket (self, sock, map=None): |
255
|
|
|
self.__dict__['socket'] = sock |
256
|
|
|
self._fileno = sock.fileno() |
257
|
|
|
self.add_channel (map) |
258
|
|
|
|
259
|
|
|
def set_reuse_addr (self): |
260
|
|
|
# try to re-use a server port if possible |
261
|
|
|
try: |
262
|
|
|
self.socket.setsockopt ( |
263
|
|
|
socket.SOL_SOCKET, socket.SO_REUSEADDR, |
264
|
|
|
self.socket.getsockopt (socket.SOL_SOCKET, socket.SO_REUSEADDR) | 1 |
265
|
|
|
) |
266
|
|
|
except: |
267
|
|
|
pass |
268
|
|
|
|
269
|
|
|
# ================================================== |
270
|
|
|
# predicates for select() |
271
|
|
|
# these are used as filters for the lists of sockets |
272
|
|
|
# to pass to select(). |
273
|
|
|
# ================================================== |
274
|
|
|
|
275
|
|
|
def readable (self): |
276
|
|
|
return 1 |
277
|
|
|
|
278
|
|
|
if os.name == 'mac': |
279
|
|
|
# The macintosh will select a listening socket for |
280
|
|
|
# write if you let it. What might this mean? |
281
|
|
|
def writable (self): |
282
|
|
|
return not self.accepting |
283
|
|
|
else: |
284
|
|
|
def writable (self): |
285
|
|
|
return 1 |
286
|
|
|
|
287
|
|
|
# ================================================== |
288
|
|
|
# socket object methods. |
289
|
|
|
# ================================================== |
290
|
|
|
|
291
|
|
|
def listen (self, num): |
292
|
|
|
self.accepting = 1 |
293
|
|
|
if os.name == 'nt' and num > 5: |
294
|
|
|
num = 1 |
295
|
|
|
return self.socket.listen (num) |
296
|
|
|
|
297
|
|
|
def bind (self, addr): |
298
|
|
|
self.addr = addr |
299
|
|
|
return self.socket.bind (addr) |
300
|
|
|
|
301
|
|
|
def connect (self, address): |
302
|
|
|
self.connected = 0 |
303
|
|
|
try: |
304
|
|
|
self.socket.connect (address) |
305
|
|
|
except socket.error, why: |
306
|
|
|
if why[0] in (EINPROGRESS, EALREADY, EWOULDBLOCK): |
307
|
|
|
return |
308
|
|
|
else: |
309
|
|
|
raise socket.error, why |
310
|
|
|
self.connected = 1 |
311
|
|
|
self.handle_connect() |
312
|
|
|
|
313
|
|
|
def accept (self): |
314
|
|
|
try: |
315
|
|
|
conn, addr = self.socket.accept() |
316
|
|
|
return conn, addr |
317
|
|
|
except socket.error, why: |
318
|
|
|
if why[0] == EWOULDBLOCK: |
319
|
|
|
pass |
320
|
|
|
else: |
321
|
|
|
raise socket.error, why |
322
|
|
|
|
323
|
|
|
def send (self, data): |
324
|
|
|
try: |
325
|
|
|
result = self.socket.send (data) |
326
|
|
|
return result |
327
|
|
|
except socket.error, why: |
328
|
|
|
if why[0] == EWOULDBLOCK: |
329
|
|
|
return 0 |
330
|
|
|
else: |
331
|
|
|
raise socket.error, why |
332
|
|
|
return 0 |
333
|
|
|
|
334
|
|
|
def recv (self, buffer_size): |
335
|
|
|
try: |
336
|
|
|
data = self.socket.recv (buffer_size) |
337
|
|
|
if not data: |
338
|
|
|
# a closed connection is indicated by signaling |
339
|
|
|
# a read condition, and having recv() return 0. |
340
|
|
|
self.handle_close() |
341
|
|
|
return '' |
342
|
|
|
else: |
343
|
|
|
return data |
344
|
|
|
except socket.error, why: |
345
|
|
|
# winsock sometimes throws ENOTCONN |
346
|
|
|
if why[0] in [ECONNRESET, ENOTCONN, ESHUTDOWN]: |
347
|
|
|
self.handle_close() |
348
|
|
|
return '' |
349
|
|
|
else: |
350
|
|
|
raise socket.error, why |
351
|
|
|
|
352
|
|
|
def close (self): |
353
|
|
|
self.del_channel() |
354
|
|
|
self.socket.close() |
355
|
|
|
|
356
|
|
|
# cheap inheritance, used to pass all other attribute |
357
|
|
|
# references to the underlying socket object. |
358
|
|
|
def __getattr__ (self, attr): |
359
|
|
|
return getattr (self.socket, attr) |
360
|
|
|
|
361
|
|
|
# log and log_info maybe overriden to provide more sophisitcated |
362
|
|
|
# logging and warning methods. In general, log is for 'hit' logging |
363
|
|
|
# and 'log_info' is for informational, warning and error logging. |
364
|
|
|
|
365
|
|
|
def log (self, message): |
366
|
|
|
sys.stderr.write ('log: %s\n' % str(message)) |
367
|
|
|
|
368
|
|
|
def log_info (self, message, type='info'): |
369
|
|
|
if __debug__ or type != 'info': |
|
|
|
|
370
|
|
|
print '%s: %s' % (type, message) |
371
|
|
|
|
372
|
|
|
def handle_read_event (self): |
373
|
|
|
if self.accepting: |
374
|
|
|
# for an accepting socket, getting a read implies |
375
|
|
|
# that we are connected |
376
|
|
|
if not self.connected: |
377
|
|
|
self.connected = 1 |
378
|
|
|
self.handle_accept() |
379
|
|
|
elif not self.connected: |
380
|
|
|
self.handle_connect() |
381
|
|
|
self.connected = 1 |
382
|
|
|
self.handle_read() |
383
|
|
|
else: |
384
|
|
|
self.handle_read() |
385
|
|
|
|
386
|
|
|
def handle_write_event (self): |
387
|
|
|
# getting a write implies that we are connected |
388
|
|
|
if not self.connected: |
389
|
|
|
self.handle_connect() |
390
|
|
|
self.connected = 1 |
391
|
|
|
self.handle_write() |
392
|
|
|
|
393
|
|
|
def handle_expt_event (self): |
394
|
|
|
self.handle_expt() |
395
|
|
|
|
396
|
|
|
def handle_error (self): |
397
|
|
|
(file,fun,line), t, v, tbinfo = compact_traceback() |
398
|
|
|
|
399
|
|
|
# sometimes a user repr method will crash. |
400
|
|
|
try: |
401
|
|
|
self_repr = repr (self) |
402
|
|
|
except: |
403
|
|
|
self_repr = '<__repr__ (self) failed for object at %0x>' % id(self) |
404
|
|
|
|
405
|
|
|
self.log_info ( |
406
|
|
|
'uncaptured python exception, closing channel %s (%s:%s %s)' % ( |
407
|
|
|
self_repr, |
408
|
|
|
t, |
409
|
|
|
v, |
410
|
|
|
tbinfo |
411
|
|
|
), |
412
|
|
|
'error' |
413
|
|
|
) |
414
|
|
|
self.close() |
415
|
|
|
|
416
|
|
|
def handle_expt (self): |
417
|
|
|
self.log_info ('unhandled exception', 'warning') |
418
|
|
|
|
419
|
|
|
def handle_read (self): |
420
|
|
|
self.log_info ('unhandled read event', 'warning') |
421
|
|
|
|
422
|
|
|
def handle_write (self): |
423
|
|
|
self.log_info ('unhandled write event', 'warning') |
424
|
|
|
|
425
|
|
|
def handle_connect (self): |
426
|
|
|
self.log_info ('unhandled connect event', 'warning') |
427
|
|
|
|
428
|
|
|
def handle_accept (self): |
429
|
|
|
self.log_info ('unhandled accept event', 'warning') |
430
|
|
|
|
431
|
|
|
def handle_close (self): |
432
|
|
|
self.log_info ('unhandled close event', 'warning') |
433
|
|
|
self.close() |
434
|
|
|
|
435
|
|
|
# --------------------------------------------------------------------------- |
436
|
|
|
# adds simple buffered output capability, useful for simple clients. |
437
|
|
|
# [for more sophisticated usage use asynchat.async_chat] |
438
|
|
|
# --------------------------------------------------------------------------- |
439
|
|
|
|
440
|
|
|
class dispatcher_with_send (dispatcher): |
441
|
|
|
def __init__ (self, sock=None): |
442
|
|
|
dispatcher.__init__ (self, sock) |
443
|
|
|
self.out_buffer = '' |
444
|
|
|
|
445
|
|
|
def initiate_send (self): |
446
|
|
|
num_sent = 0 |
447
|
|
|
num_sent = dispatcher.send (self, self.out_buffer[:512]) |
448
|
|
|
self.out_buffer = self.out_buffer[num_sent:] |
449
|
|
|
|
450
|
|
|
def handle_write (self): |
451
|
|
|
self.initiate_send() |
452
|
|
|
|
453
|
|
|
def writable (self): |
454
|
|
|
return (not self.connected) or len(self.out_buffer) |
455
|
|
|
|
456
|
|
|
def send (self, data): |
457
|
|
|
if self.debug: |
458
|
|
|
self.log_info ('sending %s' % repr(data)) |
459
|
|
|
self.out_buffer = self.out_buffer + data |
460
|
|
|
self.initiate_send() |
461
|
|
|
|
462
|
|
|
# --------------------------------------------------------------------------- |
463
|
|
|
# used for debugging. |
464
|
|
|
# --------------------------------------------------------------------------- |
465
|
|
|
|
466
|
|
|
def compact_traceback (): |
467
|
|
|
t,v,tb = sys.exc_info() |
468
|
|
|
tbinfo = [] |
469
|
|
|
while 1: |
470
|
|
|
tbinfo.append (( |
471
|
|
|
tb.tb_frame.f_code.co_filename, |
472
|
|
|
tb.tb_frame.f_code.co_name, |
473
|
|
|
str(tb.tb_lineno) |
474
|
|
|
)) |
475
|
|
|
tb = tb.tb_next |
476
|
|
|
if not tb: |
477
|
|
|
break |
478
|
|
|
|
479
|
|
|
# just to be safe |
480
|
|
|
del tb |
481
|
|
|
|
482
|
|
|
file, function, line = tbinfo[-1] |
483
|
|
|
info = '[' + string.join ( |
484
|
|
|
map ( |
485
|
|
|
lambda x: string.join (x, '|'), |
486
|
|
|
tbinfo |
487
|
|
|
), |
488
|
|
|
'] [' |
489
|
|
|
) + ']' |
490
|
|
|
return (file, function, line), t, v, info |
491
|
|
|
|
492
|
|
|
def close_all (map=None): |
493
|
|
|
if map is None: |
494
|
|
|
map=socket_map |
495
|
|
|
for x in map.values(): |
496
|
|
|
x.socket.close() |
497
|
|
|
map.clear() |
498
|
|
|
|
499
|
|
|
# Asynchronous File I/O: |
500
|
|
|
# |
501
|
|
|
# After a little research (reading man pages on various unixen, and |
502
|
|
|
# digging through the linux kernel), I've determined that select() |
503
|
|
|
# isn't meant for doing doing asynchronous file i/o. |
504
|
|
|
# Heartening, though - reading linux/mm/filemap.c shows that linux |
505
|
|
|
# supports asynchronous read-ahead. So _MOST_ of the time, the data |
506
|
|
|
# will be sitting in memory for us already when we go to read it. |
507
|
|
|
# |
508
|
|
|
# What other OS's (besides NT) support async file i/o? [VMS?] |
509
|
|
|
# |
510
|
|
|
# Regardless, this is useful for pipes, and stdin/stdout... |
511
|
|
|
|
512
|
|
|
import os |
513
|
|
|
if os.name == 'posix': |
514
|
|
|
import fcntl |
515
|
|
|
|
516
|
|
|
class file_wrapper: |
517
|
|
|
# here we override just enough to make a file |
518
|
|
|
# look like a socket for the purposes of asyncore. |
519
|
|
|
def __init__ (self, fd): |
520
|
|
|
self.fd = fd |
521
|
|
|
|
522
|
|
|
def recv (self, *args): |
523
|
|
|
return apply (os.read, (self.fd,)+args) |
|
|
|
|
524
|
|
|
|
525
|
|
|
def send (self, *args): |
526
|
|
|
return apply (os.write, (self.fd,)+args) |
|
|
|
|
527
|
|
|
|
528
|
|
|
read = recv |
529
|
|
|
write = send |
530
|
|
|
|
531
|
|
|
def close (self): |
532
|
|
|
return os.close (self.fd) |
533
|
|
|
|
534
|
|
|
def fileno (self): |
535
|
|
|
return self.fd |
536
|
|
|
|
537
|
|
|
class file_dispatcher (dispatcher): |
538
|
|
|
def __init__ (self, fd): |
539
|
|
|
dispatcher.__init__ (self) |
540
|
|
|
self.connected = 1 |
541
|
|
|
# set it to non-blocking mode |
542
|
|
|
flags = fcntl.fcntl (fd, fcntl.F_GETFL, 0) |
|
|
|
|
543
|
|
|
flags = flags | fcntl.O_NONBLOCK |
544
|
|
|
fcntl.fcntl (fd, fcntl.F_SETFL, flags) |
545
|
|
|
self.set_file (fd) |
546
|
|
|
|
547
|
|
|
def set_file (self, fd): |
548
|
|
|
self._fileno = fd |
549
|
|
|
self.socket = file_wrapper (fd) |
|
|
|
|
550
|
|
|
self.add_channel() |
551
|
|
|
|
552
|
|
|
|