# -*- 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 and most 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/O library (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. """importexceptionsimportselectimportsocketimportstringimportsysimportosifos.name=='nt':EWOULDBLOCK=10035EINPROGRESS=10036EALREADY=10037ECONNRESET=10054ENOTCONN=10057ESHUTDOWN=10058else:fromerrnoimportEALREADY,EINPROGRESS,EWOULDBLOCK,ECONNRESET,ENOTCONN,ESHUTDOWNtry:socket_mapexceptNameError:socket_map={}classExitNow(exceptions.Exception):passDEBUG=0defpoll(timeout=0.0,map=None):globalDEBUGifmapisNone:map=socket_mapifmap:r=[];w=[];e=[]forfd,objinmap.items():ifobj.readable():r.append(fd)ifobj.writable():w.append(fd)r,w,e=select.select(r,w,e,timeout)ifDEBUG:printr,w,eforfdinr:try:obj=map[fd]try:obj.handle_read_event()exceptExitNow:raiseExitNowexcept:obj.handle_error()exceptKeyError:passforfdinw:try:obj=map[fd]try:obj.handle_write_event()exceptExitNow:raiseExitNowexcept:obj.handle_error()exceptKeyError:passdefpoll2(timeout=0.0,map=None):importpollifmapisNone:map=socket_map# timeout is in millisecondstimeout=int(timeout*1000)ifmap:l=[]forfd,objinmap.items():flags=0ifobj.readable():flags=poll.POLLINifobj.writable():flags=flags|poll.POLLOUTifflags:l.append((fd,flags))r=poll.poll(l,timeout)forfd,flagsinr:try:obj=map[fd]try:if(flags&poll.POLLIN):obj.handle_read_event()if(flags&poll.POLLOUT):obj.handle_write_event()exceptExitNow:raiseExitNowexcept:obj.handle_error()exceptKeyError:passdefloop(timeout=30.0,use_poll=0,map=None):ifuse_poll:poll_fun=poll2else:poll_fun=pollifmapisNone:map=socket_mapwhilemap:poll_fun(timeout,map)classdispatcher:debug=0connected=0accepting=0closing=0addr=Nonedef__init__(self,sock=None,map=None):ifsock:self.set_socket(sock,map)# I think it should inherit this anywayself.socket.setblocking(0)self.connected=1def__repr__(self):try:status=[]ifself.acceptingandself.addr:status.append('listening')elifself.connected:status.append('connected')ifself.addr:status.append('%s:%d'%self.addr)return'<%s%s at %x>'%(self.__class__.__name__,string.join(status,' '),id(self))except:try:ar=repr(self.addr)except:ar='no self.addr!'return'<__repr__ (self) failed for object at %x (addr=%s)>'%(id(self),ar)defadd_channel(self,map=None):#self.log_info ('adding channel %s' % self)ifmapisNone:map=socket_mapmap[self._fileno]=selfdefdel_channel(self,map=None):fd=self._filenoifmapisNone:map=socket_mapifmap.has_key(fd):#self.log_info ('closing channel %d:%s' % (fd, self))delmap[fd]defcreate_socket(self,family,type):self.family_and_type=family,typeself.socket=socket.socket(family,type)self.socket.setblocking(0)self._fileno=self.socket.fileno()self.add_channel()defset_socket(self,sock,map=None):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)except:pass# ==================================================# predicates for select()# these are used as filters for the lists of sockets# to pass to select().# ==================================================defreadable(self):return1ifos.name=='mac':# The macintosh will select a listening socket for# write if you let it. What might this mean?defwritable(self):returnnotself.acceptingelse:defwritable(self):return1# ==================================================# socket object methods.# ==================================================deflisten(self,num):self.accepting=1ifos.name=='nt'andnum>5:num=1returnself.socket.listen(num)defbind(self,addr):self.addr=addrreturnself.socket.bind(addr)defconnect(self,address):self.connected=0try:self.socket.connect(address)exceptsocket.error,why:ifwhy[0]in(EINPROGRESS,EALREADY,EWOULDBLOCK):returnelse:raisesocket.error,whyself.connected=1self.handle_connect()defaccept(self):try:conn,addr=self.socket.accept()returnconn,addrexceptsocket.error,why:ifwhy[0]==EWOULDBLOCK:passelse:raisesocket.error,whydefsend(self,data):try:result=self.socket.send(data)returnresultexceptsocket.error,why:ifwhy[0]==EWOULDBLOCK:return0else:raisesocket.error,whyreturn0defrecv(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[0]in[ECONNRESET,ENOTCONN,ESHUTDOWN]:self.handle_close()return''else:raisesocket.error,whydefclose(self):self.del_channel()self.socket.close()# cheap inheritance, used to pass all other attribute# references to the underlying socket object.def__getattr__(self,attr):returngetattr(self.socket,attr)# log and log_info maybe overriden to provide more sophisitcated# 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'):if__debug__ortype!='info':print'%s: %s'%(type,message)defhandle_read_event(self):ifself.accepting:# for an accepting socket, getting a read implies# that we are connectedifnotself.connected:self.connected=1self.handle_accept()elifnotself.connected:self.handle_connect()self.connected=1self.handle_read()else:self.handle_read()defhandle_write_event(self):# getting a write implies that we are connectedifnotself.connected:self.handle_connect()self.connected=1self.handle_write()defhandle_expt_event(self):self.handle_expt()defhandle_error(self):(file,fun,line),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.close()defhandle_expt(self):self.log_info('unhandled exception','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):dispatcher.__init__(self,sock)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=[]while1:tbinfo.append((tb.tb_frame.f_code.co_filename,tb.tb_frame.f_code.co_name,str(tb.tb_lineno)))tb=tb.tb_nextifnottb:break# just to be safedeltbfile,function,line=tbinfo[-1]info='['+string.join(map(lambdax:string.join(x,'|'),tbinfo),'] [')+']'return(file,function,line),t,v,infodefclose_all(map=None):ifmapisNone:map=socket_mapforxinmap.values():x.socket.close()map.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 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...importosifos.name=='posix':importfcntlimportFCNTLclassfile_wrapper:# here we override just enough to make a file# look like a socket for the purposes of asyncore.def__init__(self,fd):self.fd=fddefrecv(self,*args):returnapply(os.read,(self.fd,)+args)defsend(self,*args):returnapply(os.write,(self.fd,)+args)read=recvwrite=senddefclose(self):returnos.close(self.fd)deffileno(self):returnself.fdclassfile_dispatcher(dispatcher):def__init__(self,fd):dispatcher.__init__(self)self.connected=1# set it to non-blocking modeflags=fcntl.fcntl(fd,FCNTL.F_GETFL,0)flags=flags|FCNTL.O_NONBLOCKfcntl.fcntl(fd,FCNTL.F_SETFL,flags)self.set_file(fd)defset_file(self,fd):self._fileno=fdself.socket=file_wrapper(fd)self.add_channel()