我的开发环境是macos mojave。我用名为selectors.DefaultSelector()
的类封装了Server
。此类用于处理客户端并发请求。如果该类继承自multiprocessing.Process
并尝试在子进程中运行错误,则会发生以下错误:
Self.sel1.register(self.listenSock, selectors.EVENT_READ, data=None)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/selectors.py", line 522, in register
Self._selector.control([kev], 0, 0)
OSError: [Errno 9] Bad file descriptor
但是它在主进程中运行(不继承multiprocessing.Process
),那么如何解决此问题?
import types
import time
import multiprocessing as mp
Socket=socket.socket
request_que=mp.Queue()
def finditer(data,sub:str,count:int):
#Continuous traversal to find the specified character
pos=0
while count>0:
i=data[pos+1:].find(sub)
if i>=0:
pos+=(i+1)
count-=1
#end-while
return pos
#end-def
def get_host_info(req:bytes):
'''
Get information about the requesting server from the Socket
request byte stream
'''
host,port,url,is_https='','','',False
def extract_ip_port(req,method=b'GET'):
ed=req.index(b' HTTP/1.1')
url=req[len(method)+1:ed]
st=finditer(url,b'/',2)+1
ed=finditer(url,b'/',3)
server=url[st:ed]
pos=server.rfind(b':')
if pos>0:
port=int(server[pos+1:])
host=server[:pos]
if host.startswith(b'['): #ipv6
host=host[1:-1]
else:
port=80
host=server
#end-if
return host,port,url
#end-def
if req.startswith(b'GET'):
host,port,url=extract_ip_port(req)
elif req.startswith(b'POST'):
host,port,url=extract_ip_port(req,method=b'POST')
elif req.startswith(b'CONNECT'):
ed=req.index(b' HTTP/1.1')
req=req[8:ed]
req=req.split(b':')
host,port=req[0],req[1]
is_https=True
return host,port,url,is_https
#end-def
class Server(mp.Process):
def __init__(self,locIP,locPort,reqQue):
super(Server, self).__init__()
self.locIP=locIP
self.locPort=locPort
self.listenSock=None
self.reqQue=reqQue
self.sel1=selectors.DefaultSelector()
def accept_wrapper(self,sock: socket.socket):
'''
Since the listened socket is registered to selectors.EVENT_READ, it can now be read, and immediately call sock.accept and conn.setblocking(False)
to get the socket into non-blocking mode.
'''
conn, addr = sock.accept()
print('accepted connection from ', addr)
conn.setblocking(False)
conn.settimeout(6)
#Dynamically create a temporary object class, data is used to track
#the data sent and received by each socket object
data = types.SimpleNamespace(addr=addr, inb=b'', outb=b'')
events = selectors.EVENT_READ | selectors.EVENT_WRITE
self.sel1.register(conn, events, data=data)
# end-def
def run(self):
try:
self.listenSock =Socket(socket.AF_INET, socket.SOCK_STREAM)
self.listenSock.setblocking(False)
self.listenSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.listenSock.bind((self.locIP,self.locPort))
self.listenSock.listen(5)
print(f'Server Process:{os.getpid()}')
print(f'Server is running & listen on {self.locIP}:{self.locPort}')
#Selector.register() Registers socket monitoring for the event you
#are interested in using selector.select() . For listening sockets,
#we want to use selectors.EVENT_READ
self.sel1.register(self.listenSock, selectors.EVENT_READ, data=None)
except BlockingIOError as e:
raise e
except OSError as e:
raise e
try:
while True:
events = self.sel1.select(timeout=None)
for key, mask in events:
# If None indicates a socket for listening
if key.data is None:
self.accept_wrapper(key.fileobj)
else:
self.service_connection(key, mask)
# end-for
# end-while
except KeyboardInterrupt:
print('keyboard interrupt by user,server exit')
except OSError as e:
print(e)
finally:
self.sel1.close()
# end-def
def service_connection(self,key, mask):
sock = key.fileobj
data = key.data
if mask & selectors.EVENT_READ:
request = sock.recv(MAX_RECV_BUF)
if request:
data.inb += request
host,port,url,is_https=get_host_info(data.inb)
request_que.put((host,port,url,is_https,data.inb))
else:
# This means that no data has been received and the client's
# socket object has been closed.
print('closing connection to ', data.addr)
# Undo monitoring of the current sock object
self.sel1.unregister(sock)
sock.close()
if mask & selectors.EVENT_WRITE:
if data.outb:
print(f'Main-Thread {sock}:{data.outb}')
print('echoing', repr(data.outb), ' to ', data.addr)
sent = sock.send(data.outb)
data.outb = data.outb[sent:]
# end-if
# end-def
#end-class
if __name__=='__main__':
server=Server(args.host,args.port,request_que)
server.start()