# -*- Mode: Python -*-# Id: asyncore.py,v 2.51 2000/09/07 22:29:26 rushing Exp# Author: Sam Rushing <rushing@nightmare.com># ======================================================================# Copyright 1996 by Sam Rushing## All Rights Reserved## Permission to use, copy, modify, and distribute this software and# its documentation for any purpose and without fee is hereby# granted, provided that the above copyright notice appear in all# copies and that both that copyright notice and this permission# notice appear in supporting documentation, and that the name of Sam# Rushing not be used in advertising or publicity pertaining to# distribution of the software without specific, written prior# permission.## SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.# ======================================================================"""Basic infrastructure for asynchronous socket service clients and servers.There are only two ways to have a program on a single processor do "morethan one thing at a time". Multi-threaded programming is the simplest andmost popular way to do it, but there is another very different technique,that lets you have nearly all the advantages of multi-threading, withoutactually using multiple threads. it's really only practical if your programis largely I/O bound. If your program is CPU bound, then pre-emptivescheduled threads are probably what you really need. Network servers arerarely CPU-bound, however.If your operating system supports the select() system call in its I/Olibrary (and nearly all do), then you can use it to juggle multiplecommunication channels at once; doing other work while your I/O is takingplace in the "background." Although this strategy can seem strange andcomplex, especially at first, it is in many ways easier to understand andcontrol than multi-threaded programming. The module documented here solvesmany of the difficult problems for you, making the task of buildingsophisticated high-performance network servers and clients a snap."""importselectimportsocketimportsysimporttimeimportwarningsimportosfromerrnoimportEALREADY,EINPROGRESS,EWOULDBLOCK,ECONNRESET,EINVAL, \
ENOTCONN,ESHUTDOWN,EINTR,EISCONN,EBADF,ECONNABORTED,EPIPE,EAGAIN, \
errorcode_DISCONNECTED=frozenset((ECONNRESET,ENOTCONN,ESHUTDOWN,ECONNABORTED,EPIPE,EBADF))try:socket_mapexceptNameError:socket_map={}def_strerror(err):try:returnos.strerror(err)except(ValueError,OverflowError,NameError):iferrinerrorcode:returnerrorcode[err]return"Unknown error %s"%errclassExitNow(Exception):pass_reraised_exceptions=(ExitNow,KeyboardInterrupt,SystemExit)defread(obj):try:obj.handle_read_event()except_reraised_exceptions:raiseexcept:obj.handle_error()defwrite(obj):try:obj.handle_write_event()except_reraised_exceptions:raiseexcept:obj.handle_error()def_exception(obj):try:obj.handle_expt_event()except_reraised_exceptions:raiseexcept:obj.handle_error()defreadwrite(obj,flags):try:ifflags&select.POLLIN:obj.handle_read_event()ifflags&select.POLLOUT:obj.handle_write_event()ifflags&select.POLLPRI:obj.handle_expt_event()ifflags&(select.POLLHUP|select.POLLERR|select.POLLNVAL):obj.handle_close()exceptsocket.error,e:ife.args[0]notin_DISCONNECTED:obj.handle_error()else:obj.handle_close()except_reraised_exceptions:raiseexcept:obj.handle_error()defpoll(timeout=0.0,map=None):ifmapisNone:map=socket_mapifmap:r=[];w=[];e=[]forfd,objinmap.items():is_r=obj.readable()is_w=obj.writable()ifis_r:r.append(fd)# accepting sockets should not be writableifis_wandnotobj.accepting:w.append(fd)ifis_roris_w:e.append(fd)if[]==r==w==e:time.sleep(timeout)returntry:r,w,e=select.select(r,w,e,timeout)exceptselect.error,err:iferr.args[0]!=EINTR:raiseelse:returnforfdinr:obj=map.get(fd)ifobjisNone:continueread(obj)forfdinw:obj=map.get(fd)ifobjisNone:continuewrite(obj)forfdine:obj=map.get(fd)ifobjisNone:continue_exception(obj)defpoll2(timeout=0.0,map=None):# Use the poll() support added to the select module in Python 2.0ifmapisNone:map=socket_mapiftimeoutisnotNone:# timeout is in millisecondstimeout=int(timeout*1000)pollster=select.poll()ifmap:forfd,objinmap.items():flags=0ifobj.readable():flags|=select.POLLIN|select.POLLPRI# accepting sockets should not be writableifobj.writable()andnotobj.accepting:flags|=select.POLLOUTifflags:# Only check for exceptions if object was either readable# or writable.flags|=select.POLLERR|select.POLLHUP|select.POLLNVALpollster.register(fd,flags)try:r=pollster.poll(timeout)exceptselect.error,err:iferr.args[0]!=EINTR:raiser=[]forfd,flagsinr:obj=map.get(fd)ifobjisNone:continuereadwrite(obj,flags)poll3=poll2# Alias for backward compatibilitydefloop(timeout=30.0,use_poll=False,map=None,count=None):ifmapisNone:map=socket_mapifuse_pollandhasattr(select,'poll'):poll_fun=poll2else:poll_fun=pollifcountisNone:whilemap:poll_fun(timeout,map)else:whilemapandcount>0:poll_fun(timeout,map)count=count-1classdispatcher:debug=Falseconnected=Falseaccepting=Falseclosing=Falseaddr=Noneignore_log_types=frozenset(['warning'])def__init__(self,sock=None,map=None):ifmapisNone:self._map=socket_mapelse:self._map=mapself._fileno=Noneifsock:# Set to nonblocking just to make sure for cases where we# get a socket from a blocking source.sock.setblocking(0)self.set_socket(sock,map)self.connected=True# The constructor no longer requires that the socket# passed be connected.try:self.addr=sock.getpeername()exceptsocket.error,err:iferr.args[0]==ENOTCONN:# To handle the case where we got an unconnected# socket.self.connected=Falseelse:# The socket is broken in some unknown way, alert# the user and remove it from the map (to prevent# polling of broken sockets).self.del_channel(map)raiseelse:self.socket=Nonedef__repr__(self):status=[self.__class__.__module__+"."+self.__class__.__name__]ifself.acceptingandself.addr:status.append('listening')elifself.connected:status.append('connected')ifself.addrisnotNone:try:status.append('%s:%d'%self.addr)exceptTypeError:status.append(repr(self.addr))return'<%s at %#x>'%(' '.join(status),id(self))__str__=__repr__defadd_channel(self,map=None):#self.log_info('adding channel %s' % self)ifmapisNone:map=self._mapmap[self._fileno]=selfdefdel_channel(self,map=None):fd=self._filenoifmapisNone:map=self._mapiffdinmap:#self.log_info('closing channel %d:%s' % (fd, self))delmap[fd]self._fileno=Nonedefcreate_socket(self,family,type):self.family_and_type=family,typesock=socket.socket(family,type)sock.setblocking(0)self.set_socket(sock)defset_socket(self,sock,map=None):self.socket=sock## self.__dict__['socket'] = sockself._fileno=sock.fileno()self.add_channel(map)defset_reuse_addr(self):# try to re-use a server port if possibletry:self.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,self.socket.getsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR)|1)exceptsocket.error:pass# ==================================================# predicates for select()# these are used as filters for the lists of sockets# to pass to select().# ==================================================defreadable(self):returnTruedefwritable(self):returnTrue# ==================================================# socket object methods.# ==================================================deflisten(self,num):self.accepting=Trueifos.name=='nt'andnum>5:num=5returnself.socket.listen(num)defbind(self,addr):self.addr=addrreturnself.socket.bind(addr)defconnect(self,address):self.connected=Falseerr=self.socket.connect_ex(address)iferrin(EINPROGRESS,EALREADY,EWOULDBLOCK) \
orerr==EINVALandos.namein('nt','ce'):returniferrin(0,EISCONN):self.addr=addressself.handle_connect_event()else:raisesocket.error(err,errorcode[err])defaccept(self):# XXX can return either an address pair or Nonetry:conn,addr=self.socket.accept()exceptTypeError:returnNoneexceptsocket.erroraswhy:ifwhy.args[0]in(EWOULDBLOCK,ECONNABORTED,EAGAIN):returnNoneelse:raiseelse:returnconn,addrdefsend(self,data):try:result=self.socket.send(data)returnresultexceptsocket.error,why:ifwhy.args[0]==EWOULDBLOCK:return0elifwhy.args[0]in_DISCONNECTED:self.handle_close()return0else:raisedefrecv(self,buffer_size):try:data=self.socket.recv(buffer_size)ifnotdata:# a closed connection is indicated by signaling# a read condition, and having recv() return 0.self.handle_close()return''else:returndataexceptsocket.error,why:# winsock sometimes throws ENOTCONNifwhy.args[0]in_DISCONNECTED:self.handle_close()return''else:raisedefclose(self):self.connected=Falseself.accepting=Falseself.del_channel()try:self.socket.close()exceptsocket.error,why:ifwhy.args[0]notin(ENOTCONN,EBADF):raise# cheap inheritance, used to pass all other attribute# references to the underlying socket object.def__getattr__(self,attr):try:retattr=getattr(self.socket,attr)exceptAttributeError:raiseAttributeError("%s instance has no attribute '%s'"%(self.__class__.__name__,attr))else:msg="%(me)s.%(attr)s is deprecated. Use %(me)s.socket.%(attr)s " \
"instead."%{'me':self.__class__.__name__,'attr':attr}warnings.warn(msg,DeprecationWarning,stacklevel=2)returnretattr# log and log_info may be overridden to provide more sophisticated# logging and warning methods. In general, log is for 'hit' logging# and 'log_info' is for informational, warning and error logging.deflog(self,message):sys.stderr.write('log: %s\n'%str(message))deflog_info(self,message,type='info'):iftypenotinself.ignore_log_types:print'%s: %s'%(type,message)defhandle_read_event(self):ifself.accepting:# accepting sockets are never connected, they "spawn" new# sockets that are connectedself.handle_accept()elifnotself.connected:self.handle_connect_event()self.handle_read()else:self.handle_read()defhandle_connect_event(self):err=self.socket.getsockopt(socket.SOL_SOCKET,socket.SO_ERROR)iferr!=0:raisesocket.error(err,_strerror(err))self.handle_connect()self.connected=Truedefhandle_write_event(self):ifself.accepting:# Accepting sockets shouldn't get a write event.# We will pretend it didn't happen.returnifnotself.connected:#check for errorserr=self.socket.getsockopt(socket.SOL_SOCKET,socket.SO_ERROR)iferr!=0:raisesocket.error(err,_strerror(err))self.handle_connect_event()self.handle_write()defhandle_expt_event(self):# handle_expt_event() is called if there might be an error on the# socket, or if there is OOB data# check for the error condition firsterr=self.socket.getsockopt(socket.SOL_SOCKET,socket.SO_ERROR)iferr!=0:# we can get here when select.select() says that there is an# exceptional condition on the socket# since there is an error, we'll go ahead and close the socket# like we would in a subclassed handle_read() that received no# dataself.handle_close()else:self.handle_expt()defhandle_error(self):nil,t,v,tbinfo=compact_traceback()# sometimes a user repr method will crash.try:self_repr=repr(self)except:self_repr='<__repr__(self) failed for object at %0x>'%id(self)self.log_info('uncaptured python exception, closing channel %s (%s:%s%s)'%(self_repr,t,v,tbinfo),'error')self.handle_close()defhandle_expt(self):self.log_info('unhandled incoming priority event','warning')defhandle_read(self):self.log_info('unhandled read event','warning')defhandle_write(self):self.log_info('unhandled write event','warning')defhandle_connect(self):self.log_info('unhandled connect event','warning')defhandle_accept(self):self.log_info('unhandled accept event','warning')defhandle_close(self):self.log_info('unhandled close event','warning')self.close()# ---------------------------------------------------------------------------# adds simple buffered output capability, useful for simple clients.# [for more sophisticated usage use asynchat.async_chat]# ---------------------------------------------------------------------------classdispatcher_with_send(dispatcher):def__init__(self,sock=None,map=None):dispatcher.__init__(self,sock,map)self.out_buffer=''definitiate_send(self):num_sent=0num_sent=dispatcher.send(self,self.out_buffer[:512])self.out_buffer=self.out_buffer[num_sent:]defhandle_write(self):self.initiate_send()defwritable(self):return(notself.connected)orlen(self.out_buffer)defsend(self,data):ifself.debug:self.log_info('sending %s'%repr(data))self.out_buffer=self.out_buffer+dataself.initiate_send()# ---------------------------------------------------------------------------# used for debugging.# ---------------------------------------------------------------------------defcompact_traceback():t,v,tb=sys.exc_info()tbinfo=[]ifnottb:# Must have a tracebackraiseAssertionError("traceback does not exist")whiletb:tbinfo.append((tb.tb_frame.f_code.co_filename,tb.tb_frame.f_code.co_name,str(tb.tb_lineno)))tb=tb.tb_next# just to be safedeltbfile,function,line=tbinfo[-1]info=' '.join(['[%s|%s|%s]'%xforxintbinfo])return(file,function,line),t,v,infodefclose_all(map=None,ignore_all=False):ifmapisNone:map=socket_mapforxinmap.values():try:x.close()exceptOSError,x:ifx.args[0]==EBADF:passelifnotignore_all:raiseexcept_reraised_exceptions:raiseexcept:ifnotignore_all:raisemap.clear()# Asynchronous File I/O:## After a little research (reading man pages on various unixen, and# digging through the linux kernel), I've determined that select()# isn't meant for doing asynchronous file i/o.# Heartening, though - reading linux/mm/filemap.c shows that linux# supports asynchronous read-ahead. So _MOST_ of the time, the data# will be sitting in memory for us already when we go to read it.## What other OS's (besides NT) support async file i/o? [VMS?]## Regardless, this is useful for pipes, and stdin/stdout...ifos.name=='posix':importfcntlclassfile_wrapper:# Here we override just enough to make a file# look like a socket for the purposes of asyncore.# The passed fd is automatically os.dup()'ddef__init__(self,fd):self.fd=os.dup(fd)defrecv(self,*args):returnos.read(self.fd,*args)defsend(self,*args):returnos.write(self.fd,*args)defgetsockopt(self,level,optname,buflen=None):if(level==socket.SOL_SOCKETandoptname==socket.SO_ERRORandnotbuflen):return0raiseNotImplementedError("Only asyncore specific behaviour ""implemented.")read=recvwrite=senddefclose(self):os.close(self.fd)deffileno(self):returnself.fdclassfile_dispatcher(dispatcher):def__init__(self,fd,map=None):dispatcher.__init__(self,None,map)self.connected=Truetry:fd=fd.fileno()exceptAttributeError:passself.set_file(fd)# set it to non-blocking modeflags=fcntl.fcntl(fd,fcntl.F_GETFL,0)flags=flags|os.O_NONBLOCKfcntl.fcntl(fd,fcntl.F_SETFL,flags)defset_file(self,fd):self.socket=file_wrapper(fd)self._fileno=self.socket.fileno()self.add_channel()