Socket网络编程-SocketServer

            Socket网络编程-SocketServer

                                   作者:尹正杰

版权声明:原创作品,谢绝转载!否则将追究法律责任。

 

 

一.SocketServer概述

  socket编程过于底层,编程虽然有套路,但是想要写出健壮的代码还是比较困难的,所以很多语言都对 socket底层API进行封装,Python的封装就是socketserver模块。它是网络服务编程框架,便于企业级 快速开发。

  类的继承关系如下所示: 
    +------------+
    | BaseServer |
    +------------+
    |
    |
          v
    +-----------+        +--------------------+
    | UDPServer |------->| UnixDatagramServer |
    +-----------+        +--------------------+
          v
    +-----------+
                         +------------------+
    | TCPServer |------->| UnixStreamServer |
    +-----------+        +------------------+
 
  SocketServer简化了网络服务器的编写。 它有4个同步类:
    TCPServer
    UDPServer 
    UnixStreamServer 
    UnixDatagramServer。

  2个Mixin类:ForkingMixIn 和 ThreadingMixIn 类,用来支持异步。由此得到
    class ForkingUDPServer(ForkingMixIn, UDPServer): pass 
    class ForkingTCPServer(ForkingMixIn, TCPServer): pass
    class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass 
    class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass

  fork是创建多进程,thread是创建多线程。 

  fork需要操作系统支持,Windows不支持。

 

二.编程接口

1>.创建服务器需要几个步骤

  从BaseRequestHandler类派生出子类,并覆盖其handle()方法来创建请求处理程序类,此方法将 处理传入请求
  
  实例化一个服务器类,传参服务器的地址和请求处理类
  调用服务器实例的handle_request()或serve_forever()方法
  调用server_close()关闭套接字

2>.案例展示

 1 #!/usr/bin/env python
 2 #_*_conding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:http://www.cnblogs.com/yinzhengjie
 5 
 6 
 7 import threading
 8 import socketserver
 9 import logging
10 
11 FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s"
12 logging.basicConfig(format=FORMAT, level=logging.INFO)
13 
14 """
15 BaseRequestHandler:
16     def __init__(self, request, client_address, server):
17         self.request = request
18         self.client_address = client_address
19         self.server = server
20         self.setup()
21         try:
22             self.handle()
23         finally:
24             self.finish()
25 
26 参数说明:
27     它是和用户连接的用户请求处理类的基类
28     服务端Server实例接收用户请求后,最后会实例化这个类。
29     它被初始化时,送入3个构造参数:request, client_address, server自身 
30     以后就可以在BaseRequestHandler类的实例上使用以下属性:
31         self.request是和客户端的连接的socket对象 
32         self.server是TCPServer实例本身 
33         self.client_address是客户端地址
34     这个类在初始化的时候,它会依次调用3个方法。子类可以覆盖这些方法。
35 """
36 class MyHandler(socketserver.BaseRequestHandler):
37     def handle(self):
38         # super().handle()                                        #可以不调用,父类handle什么都没有做
39         print('-'*30)
40         print(self.server)                                       #服务
41         print(self.request)                                      #服务端负责客户端连接请求的socket对象
42         print(self.client_address)                               #客户端地址
43         print(self.__dict__)
44         print(self.server.__dict__)                              #能看到负责accept的socket
45         print(threading.enumerate())
46         print(threading.current_thread())
47         print('-'*30)
48         for i in range(3):
49             data = self.request.recv(1024)
50             logging.info(data)
51         logging.info('====end====')
52 
53 addr = ('172.30.1.2', 9999)
54 
55 """
56     将ThreadingTCPServer换成TCPServer,同时连接2个客户端观察效果。 ThreadingTCPServer是异步的,可以同时处理多个连接。
57     TCPServer是同步的,一个连接处理完了,即一个连接的handle方法执行完了,才能处理另一个连接, 且只有主线程。
58 """
59 server = socketserver.ThreadingTCPServer(addr, MyHandler)       #注意参数是MyHandler类
60 server.serve_forever()                                          #永久循环执行

  

三.实现EchoServer(顾名思义,Echo,来什么消息回显什么消息 客户端发来什么信息,返回什么信息) 

 1 #!/usr/bin/env python
 2 #_*_conding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:http://www.cnblogs.com/yinzhengjie
 5 
 6 
 7 import threading
 8 import socketserver
 9 
10 class Handler(socketserver.BaseRequestHandler):
11     def setup(self):
12         super().setup()
13         self.event = threading.Event()
14         
15     def finish(self):
16         super().finish()
17         self.event.set()
18         
19     def handle(self):
20         super().handle()
21         print('-' * 30)
22         while not self.event.is_set():
23             data = self.request.recv(1024).decode()
24             print(data)
25             msg = '{} {}'.format(self.client_address, data).encode()
26             self.request.send(msg)
27 
28 server = socketserver.ThreadingTCPServer(('172.30.1.2', 9999), Handler)
29 print(server)
30 threading.Thread(target=server.serve_forever, name='EchoServer', daemon=True).start()
31 
32 while True:
33     cmd = input('>>')
34     if cmd == 'quit':
35         server.server_close()
36         break
37     print(threading.enumerate())

 

四.实战—改写ChatServer 

 1 #!/usr/bin/env python
 2 #_*_conding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:http://www.cnblogs.com/yinzhengjie
 5 
 6 
 7 import datetime
 8 import threading
 9 from socketserver import ThreadingTCPServer, StreamRequestHandler
10 import logging
11 
12 FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s"
13 logging.basicConfig(format=FORMAT, level=logging.INFO)
14 
15 """
16     注意:此程序线程不安全
17 """
18 class ChatHandler(StreamRequestHandler):
19     clients = {}
20 
21     def setup(self):
22         super().setup()
23         self.event = threading.Event()
24         self.clients[self.client_address] = self.wfile
25 
26     def handle(self):
27         super().handle()
28         # for k,v in self.__dict__.items():
29         #     print(k, type(v), v)
30 
31         while not self.event.is_set():
32             data = self.rfile.read1(1024) # 可以读取到数据
33             data = data.decode().rstrip()
34             print(data, '~~~~~~~~~~~~~')
35 
36             if data == 'quit' or data == '': # 主动退出和断开
37                 break
38 
39             msg = '{} {}:{} {}'.format(datetime.datetime.now(), *self.client_address,data)
40 
41             for f in self.clients.values():
42                 f.write(msg.encode())
43                 f.flush()
44 
45     def finish(self):
46         self.clients.pop(self.client_address)
47         super().finish()
48         self.event.set()
49 
50 server = ThreadingTCPServer(('172.30.1.2', 9999), ChatHandler)
51 server.daemon_threads = True # 让所有启动线程都为daemon
52 
53 threading.Thread(target=server.serve_forever, name='chatserver', daemon=True).start()
54 
55 while True:
56     cmd = input('>>')
57     if cmd.strip() == 'quit':
58         server.server_close()
59         break
60     print(threading.enumerate())

 

五.总结

  为每一个连接提供RequestHandlerClass类实例,依次调用setup、handle、finish方法,且使用了try...finally结构保证finish方法一定能被调用。这些方法依次执行完成,如果想维持这个连接和客户端 通信,就需要在handle函数中使用循环。

  socketserver模块提供的不同的类,但是编程接口是一样的,即使是多进程、多线程的类也是一样,大 大减少了编程的难度。 将socket编程简化,只需要程序员关注数据处理本身,实现Handler类就行了。这种风格在Python十分常见。

 

posted @ 2019-12-03 23:39  尹正杰  阅读(413)  评论(0编辑  收藏  举报