目录
socketserver模块:... 1
编程接口:... 2
总结,创建服务器步骤:... 4
例,实现EchoServer:... 4
例,改写ChatServer:... 5
socket过于底层,编程虽有套路,但想要写出健壮的代码比较困难,所以很多语言都对socket底层API进行封装,py的封装就是socketserver模块,网络服务编程框架,全球企业级快速开发;
socketserver简化了网络服务器的编写;
+------------+
| BaseServer |
+------------+
|
v
+-----------+ +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+ +------------------+
|
v
+-----------+ +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+ +--------------------+
4个sync同步类:
TCPServer、UDPServer、UnixStreamServer、UnixDatagramServer;
很少用;
2个mixin类:
ForkingMixIn、ThreadingMixIn;
4个async异步类,生产中常用:
ForkingTCPServer(ForkingMixIn,TCPServer)、ForkingUDPServer(ForkingMixIn,UDPServer) #创建多进程
ThreadingTCPServer(ThreadingMixIn,TCPServer)、ThreadingUDPServer(ThreadingMixIn,UDPServer) #创建多线程
注:
一般ThreadingTCPServer够用;
如果并发很高可考虑用ForkingTCPServer;
ThreadingUDPServer甚至也很少用,尽管在LAN中,如果忙起来时接收到的包的顺序是乱的;
class BaseServer:
def __init__(self, server_address, RequestHandlerClass): #服务器绑定的地址信息;用于处理请求,该类必须是BaseRequestHandler类的子类
def finish_request(self, request, client_address): #处理请求的方法
"""Finish one request by instantiating RequestHandlerClass."""
self.RequestHandlerClass(request, client_address, self) #实例化,RequesthandlerClass的构造
查看源码,写框架的思想:
class BaseRequestHandler: #和用户连接的用户请求处理类,server实例接收用户请求后,最后会实例化这个类;它会一次调用三个函数setup()(每一个连接初始化)、handler()(每一次请求处理,必须覆盖)、finish()(每一个连接清理),子类可覆盖
def __init__(self, request, client_address, server): #初始化时送入3个构造参数,request、client_address、server(TCPServer),以后可在BaseRequestHandler类的实例上使用self.request(和client连接的socket对象)、self.cleint_address(是客户端地址)、self.server(是TCPServer本身)
self.request = request
self.client_address = client_address
self.server = server
self.setup()
try:
self.handle()
finally:
self.finish()
def setup(self): #每一个连接初始化,初始化工作,如ChatServer中维护的数据结构放到此段;实现了这三个方法,只不过是空操作,而raise NotImplementedError称为抽象,不实现
pass
def handle(self): #每一次请求处理,必须覆盖;handle()和sock.accept()对应,用户连接请求过来后,建立连接并生成一个socket对象(保存在self.request中)和客户端地址(保存在self.client_address中),之后的操作就和socket编程一样了
pass
def finish(self): #每一个连接清理,清理工作
pass
注:
setup()和finish()只执行一次;
handler()在不加锁情况下,也是执行一次;
例:
class MyHandler(socketserver.BaseRequestHandler): #右键MyHandler,Generate-->Overwrite Methods,可快速生成要覆盖的方法
def handle(self):
super().handle() #此句可不写,因为父类中的handler()为空操作;但如果是StreamRequestHandler则必须要写,该类中实现了handler()方法
print(self.request, self.client_address, self.server)
print('{} handler'.format(self.__class__))
print(self.__dict__)
print(type(self).__dict__)
print(self.__class__.__bases__[0].__dict__)
print(threading.enumerate(), threading.current_thread())
# pass #TODO #提醒自己还没写完
print('come')
for i in range(3): #client和server端长时间连接,在handler里循环;分布式服务之间需传递心跳包(传递事务、节点信息等),服务之间要长连接,不能断;数据库连接池不应用长连接,传完数据就可断开,有很多连接等着连DB
data = self.request.recv(1024)
print(data)
addr = ('127.0.0.1', 9998)
server = socketserver.ThreadingTCPServer(addr, MyHandler) #用多client连接测
# server = socketserver.TCPServer(addr, MyHandler) #同步,等前一个连接断开后,才能接收并处理下一个连接的请求
server.serve_forever() #启动大循环,类似while
server.shutdown()
server.server_close() #建议关闭连接前先server.shutdown()
输出:
<socket.socket fd=232, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9998), raddr=('127.0.0.1', 7576)> ('127.0.0.1', 7576) <socketserver.ThreadingTCPServer object at 0x0000000000B656A0>
<class '__main__.MyHandler'> handler
{'request': <socket.socket fd=232, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9998), raddr=('127.0.0.1', 7576)>, 'client_address': ('127.0.0.1', 7576), 'server': <socketserver.ThreadingTCPServer object at 0x0000000000B656A0>}
{'__doc__': None, '__module__': '__main__', 'handle': <function MyHandler.handle at 0x0000000001231C80>}
(<class 'socketserver.BaseRequestHandler'>,)
{'setup': <function BaseRequestHandler.setup at 0x0000000001477A60>, '__init__': <function BaseRequestHandler.__init__ at 0x00000000014779D8>, '__dict__': <attribute '__dict__' of 'BaseRequestHandler' objects>, '__module__': 'socketserver', '__doc__': 'Base class for request handler classes.\n\n This class is instantiated for each request to be handled. The\n constructor sets the instance variables request, client_address\n and server, and then calls the handle() method. To implement a\n specific service, all you need to do is to derive a class which\n defines a handle() method.\n\n The handle() method can find the request as self.request, the\n client address as self.client_address, and the server (in case it\n needs access to per-server information) as self.server. Since a\n separate instance is created for each request, the handle() method\n can define other arbitrary instance variables.\n\n ', 'handle': <function BaseRequestHandler.handle at 0x0000000001477AE8>, '__weakref__': <attribute '__weakref__' of 'BaseRequestHandler' objects>, 'finish': <function BaseRequestHandler.finish at 0x0000000001477B70>}
[<_MainThread(MainThread, started 4136)>, <Thread(Thread-1, started 4372)>] <Thread(Thread-1, started 4372)>
come
1、class MyHandler(socketserver.BaseRequestHandler):,通过对BaseRequestHandler类进行子类化并覆盖其handle()方法,来创建请求处理程序类,此方法处理传入请求;
2、server=socketserver.ThreadingTCPServer(addr,MyHandler),必须实例化一个服务器类,并向其传入服务器的地址和请求处理程序类;
3、server.serve_forever()或server.handle_request(),调用服务器对象的serve_forever()(一直启动)或server.handle_request()(一次性的)方法;
4、server.shutdown()、server.close(),调用server.close()(关闭套接字)前先server.shutdown()等待停止server.serve_forever();
为每一个连接提供RequestHandlerClass类实例,一次调用setup()、handler()、finish()方法,且使用了try...finally结构(查看BaseRequestHandler源码)保证finish()方法一定能被调用,这些方法一次执行完成;
如果想维持这个连接与客户端通信,需要在handler()中使用循环;
socketserver模块提供不同的类,但编程接口是一样的,即使是多进程、多线程的类也是一样,大大减少了编程的难度;
client发来什么,就返回什么消息;
class EchoHandler(socketserver.BaseRequestHandler):
def setup(self):
super().setup()
self.event = threading.Event()
def handle(self):
super().handle()
while not self.event.is_set():
data = self.request.recv(1024)
data = data.decode()
msg = 'ack: {} {}'.format(self.client_address, data)
msg = msg.encode()
self.request.send(msg)
print('end')
def finish(self):
super().finish()
self.event.set()
addr = ('127.0.0.1', 9998)
server = socketserver.ThreadingTCPServer(addr, EchoHandler)
# server.serve_forever()
server_thread = threading.Thread(target=server.serve_forever, daemon=True)
server_thread.start()
# server.shutdown()
# server.server_close()
try:
while True:
cmd = input('>>> ')
if cmd.strip() == 'quit': #只有在client都断开,与server端没有连接时才正常退出
break
except Exception as e:
print(e)
except KeyboardInterrupt:
print('exit')
finally:
server.shutdown()
server.server_close()
如果使用文件处理,使用StreamRequestHandler;
可用心跳机制;
class ChatHandler(socketserver.BaseRequestHandler):
clients = {}
def setup(self):
super().setup()
self.event = threading.Event()
print(self.client_address, threading.current_thread(), self.clients)
def handle(self):
super().handle()
while not self.event.is_set():
try: #缓冲区异常、连接异常最好自己捕获到,虽然父类中有try,但最好自己捕获
data = self.request.recv(1024).decode().strip()
if len(data) == 0: #同if not data,解决client主动断开后产生的异常,20180901追加尚未测试
raise BrokenPipeError('client broken')
except Exception as e:
logging.info(e)
data = 'quit' #技巧,某个连接一旦有问题,会有各种异常,此处直接断开
logging.info(data)
if data == 'quit':
break
self.clients[self.client_address] = self.request
msg = 'ack: {}'.format(data)
for c in self.clients.values():
c.send(msg.encode())
def finish(self):
super().finish()
self.clients.pop(self.client_address)
self.event.set()
addr = ('127.0.0.1', 9998)
server = socketserver.ThreadingTCPServer(addr, ChatHandler)
server_thread = threading.Thread(target=server.serve_forever, daemon=True)
server_thread.start()
myutils.show_threads() #在主线程中就可,没必要放到工作线程中
try:
while True:
cmd = input('>>> ').strip()
if cmd == 'quit':
break
except Exception as e:
print(e)
except KeyboardInterrupt:
print('exit')
finally:
server.shutdown()
server.server_close()
输出:
>>> [<Thread(Thread-1, started daemon 9552)>, <Thread(show_threads, started daemon 9644)>, <_MainThread(MainThread, started 9820)>]
('127.0.0.1', 8000) <Thread(Thread-2, started 4008)> {}
[<Thread(Thread-1, started daemon 9552)>, <Thread(show_threads, started daemon 9644)>, <Thread(Thread-2, started 4008)>, <_MainThread(MainThread, started 9820)>]
('127.0.0.1', 8003) <Thread(Thread-3, started 9456)> {}
[<Thread(Thread-1, started daemon 9552)>, <Thread(show_threads, started daemon 9644)>, <Thread(Thread-2, started 4008)>, <_MainThread(MainThread, started 9820)>, <Thread(Thread-3, started 9456)>]
[<Thread(Thread-1, started daemon 9552)>, <Thread(show_threads, started daemon 9644)>, <Thread(Thread-2, started 4008)>, <_MainThread(MainThread, started 9820)>, <Thread(Thread-3, started 9456)>]
2018-08-24-09:33:36 Thread info: 9456 Thread-3 test
[<Thread(Thread-1, started daemon 9552)>, <Thread(show_threads, started daemon 9644)>, <Thread(Thread-2, started 4008)>, <_MainThread(MainThread, started 9820)>, <Thread(Thread-3, started 9456)>]
2018-08-24-09:33:41 Thread info: 4008 Thread-2 test
[<Thread(Thread-1, started daemon 9552)>, <Thread(show_threads, started daemon 9644)>, <Thread(Thread-2, started 4008)>, <_MainThread(MainThread, started 9820)>, <Thread(Thread-3, started 9456)>]
2018-08-24-09:33:48 Thread info: 9456 Thread-3 test2
[<Thread(Thread-1, started daemon 9552)>, <Thread(show_threads, started daemon 9644)>, <Thread(Thread-2, started 4008)>, <_MainThread(MainThread, started 9820)>, <Thread(Thread-3, started 9456)>]
2018-08-24-09:33:51 Thread info: 4008 Thread-2 test1
[<Thread(Thread-1, started daemon 9552)>, <Thread(show_threads, started daemon 9644)>, <Thread(Thread-2, started 4008)>, <_MainThread(MainThread, started 9820)>, <Thread(Thread-3, started 9456)>]
2018-08-24-09:33:53 Thread info: 4008 Thread-2
2018-08-24-09:33:53 Thread info: 4008 Thread-2 [WinError 10053] 您的主机中的软件中止了一个已建立的连接。
2018-08-24-09:33:53 Thread info: 4008 Thread-2 quit
2018-08-24-09:33:55 Thread info: 9456 Thread-3
2018-08-24-09:33:55 Thread info: 9456 Thread-3 [WinError 10053] 您的主机中的软件中止了一个已建立的连接。
2018-08-24-09:33:55 Thread info: 9456 Thread-3 quit
[<Thread(Thread-1, started daemon 9552)>, <Thread(show_threads, started daemon 9644)>, <_MainThread(MainThread, started 9820)>]
quit
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。