SocketServer模块

SocketServer内部使用 IO多路复用 以及 “多线程” 和 “多进程” ,从而实现并发处理多个客户端请求的Socket服务端。即:每个客户端请求连接到服务器时,Socket服务端都会在服务器是创建一个“线程”或者“进程” 专门负责处理当前客户端的所有请求。

Python全栈工程师(14:Socket编程3-socketserver并发)_服务器



Socketserver模块简化了写network servers的任务.

四个基本服务器类:


class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)


使用TCP协议,如果bind_and_activate=True, 构建器自动invoke server_bind() 和server_activate()

class socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)


使用 datagrams, 在运输过程中可能出现无序或丢失的离散信息包。

class socketserver.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate=True)

class socketserver.UnixDatagramServer(server_address, RequestHandlerClass,bind_and_activate=True)


使用 Unix domain sockets; 在Unix平台上不能使用. 

这四个类同步处理请求;每个请求必须在下一个请求开始之前完成。如果每个请求需要很长的时间才能完成,这是不合适的,因为它需要大量的计算,或者因为它返回了大量客户端缓慢处理的数据。

解决方案是创建一个单独的进程或线程来处理每个请求;类中的FordJixMin和THeulyMIXIN混合可以用来支持异步行为

继承图中有五个类,其中四个代表四种类型的同步服务器


+------------+
| BaseServer |
+------------+ | v +-----------+ +------------------+ | TCPServer |---生成---->| UnixStreamServer | +-----------+ +------------------+ | v +-----------+ +--------------------+ | UDPServer |---生成---->| UnixDatagramServer | +-----------+ +--------------------+


 IP和Unix treamserver最大的区别就是地址簇。


class socketserver.ForkingMixIn

class socketserver.ThreadingMixIn


每种服务器的Forking 和threading版本可以用mix-in类来创建.比如 

ThreadingUDPServer可以创建如下:



class ThreadingUDPServer(ThreadingMixIn, UDPServer):
    pass



mix-in class 放在第一位,因为它重写了在UDPServer中定义的方法。设置各种属性也会改变底层服务器机制的行为。

class socketserver.ForkingTCPServer

class socketserver.ForkingUDPServer

class socketserver.ThreadingTCPServer

class socketserver.ThreadingUDPServer


这些类在mix-in中预定义了 

Request Handler Objects

class socketserver.BaseRequestHandler

是所有request handler对象的超类. 定义了接口. 一个具体的 handler之类必须要定义一个新的 handle() 方法并且可以重写任何 其他的方法,为每个请求创建一个子类新的实例.

setup()  #在 handle()之前调用, 方法执行所需的任何初始化操作。默认实现不执行任何操作。
handle() #他的功能必须完成服务请求所需的所有工作。默认实现不执行任何操作。有几个实例属性可供使用;请求可作为Self.Read;客户端地址为Self.client地址;服务器实例为Self.Server,以防需要访问每个服务器信息。对于数据报或流服务,Self.Read的类型不同。对于流服务,Self.Read是Socket对象;对于数据报服务,Self.Read是一对字符串和套接字。

finish() #在handle()之后调用,方法执行所需的任何清理操作。默认实现不执行任何操作。如果SETUP()引发异常,则不调用此函数。



Socketserver.TCPServerExample



服务器端

import               socketserver             


                            


              class               MyTCPHandler(socketserver.BaseRequestHandler):             


                            """             


                            The request handler class for our server.             


                            


                            It is instantiated once per connection to the server, and must             


                            override the handle() method to implement communication to the             


                            client.             


                            """             


                            


                            def               handle(              self              ):             


                            # self.request is the TCP socket connected to the client             


                            self              .data               =               self              .request.recv(              1024              ).strip()             


                            print              (              "{} wrote:"              .              format              (              self              .client_address[              0              ]))             


                            print              (              self              .data)             


                            # just send back the same data, but upper-cased             


                            self              .request.sendall(              self              .data.upper())             


                            


              if               __name__               =              =               "__main__"              :             


                            HOST, PORT               =               "localhost"              ,               9999             


                            


                            # Create the server, binding to localhost on port 9999             


                            server               =               socketserver.TCPServer((HOST, PORT), MyTCPHandler)             


                            


                            # Activate the server; this will keep running until you             


                            # interrupt the program with Ctrl-C             


                            server.serve_forever()




客户端

import               socket             


              import               sys             


                            


              HOST, PORT               =               "localhost"              ,               9999             


              data               =               " "              .join(sys.argv[              1              :])             


                            


              # Create a socket (SOCK_STREAM means a TCP socket)             


              sock               =               socket.socket(socket.AF_INET, socket.SOCK_STREAM)             


                            


              try              :             


                            # Connect to server and send data             


                            sock.connect((HOST, PORT))             


                            sock.sendall(bytes(data               +               "\n"              ,               "utf-8"              ))             


                            


                            # Receive data from the server and shut down             


                            received               =               str              (sock.recv(              1024              ),               "utf-8"              )             


              finally              :             


                            sock.close()             


                            


              print              (              "Sent:     {}"              .              format              (data))             


              print              (              "Received: {}"              .              format              (received))



上面这个例子你会发现,依然不能实现多并发,在server端做一下更改就可以了

server               =               socketserver.TCPServer((HOST, PORT), MyTCPHandler)



更改为:

server               =               socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)

ThreadingTCPServer

ThreadingTCPServer实现的Soket服务器内部会为每个client创建一个 “线程”,该线程用来和客户端进行交互。

1、ThreadingTCPServer基础

使用ThreadingTCPServer:

  • 创建一个继承自 SocketServer.BaseRequestHandler 的类
  • 类中必须定义一个名称为 handle 的方法
  • 启动ThreadingTCPServer
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import SocketServer

class MyServer(SocketServer.BaseRequestHandler):

    def handle(self):
        # print self.request,self.client_address,self.server
        conn = self.request
        conn.sendall('欢迎致电 10086,请输入1xxx,0转人工服务.')
        Flag = True
        while Flag:
            data = conn.recv(1024)
            if data == 'exit':
                Flag = False
            elif data == '0':
                conn.sendall('通过可能会被录音.balabala一大推')
            else:
                conn.sendall('请重新输入.')


if __name__ == '__main__':
    server = SocketServer.ThreadingTCPServer(('127.0.0.1',8009),MyServer)
    server.serve_forever()
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket


ip_port = ('127.0.0.1',8009)
sk = socket.socket()
sk.connect(ip_port)
sk.settimeout(5)

while True:
    data = sk.recv(1024)
    print 'receive:',data
    inp = raw_input('please input:')
    sk.sendall(inp)
    if inp == 'exit':
        break

sk.close()

2、ThreadingTCPServer源码剖析

ThreadingTCPServer的类图关系如下:

Python全栈工程师(14:Socket编程3-socketserver并发)_Self_02

 

内部调用流程为:

  • 启动服务端程序
  • 执行 TCPServer.__init__ 方法,创建服务端Socket对象并绑定 IP 和 端口
  • 执行BaseServer.__init__ 方法,将自定义的继承自SocketServer.BaseRequestHandler 的类 MyRequestHandle赋值给 self.RequestHandlerClass
  • 执行 BaseServer.server_forever 方法,While 循环一直监听是否有客户端请求到达 ...
  • 当客户端连接到达服务器
  • 执行 ThreadingMixIn.process_request 方法,创建一个 “线程” 用来处理请求
  • 执行 ThreadingMixIn.process_request_thread 方法
  • 执行 BaseServer.finish_request 方法,执行 self.RequestHandlerClass()  即:执行 自定义 MyRequestHandler 的构造方法(自动调用基类BaseRequestHandler的构造方法,在该构造方法中又会调用 MyRequestHandler的handle方法)

ThreadingTCPServer相关源码:


 BaseServer

 TCPServer

 ThreadingMixIn

 ThreadingTCPServer

RequestHandler相关源码


class BaseRequestHandler:

    """Base class for request handler classes.

    This class is instantiated for each request to be handled.  The
    constructor sets the instance variables request, client_address
    and server, and then calls the handle() method.  To implement a
    specific service, all you need to do is to derive a class which
    defines a handle() method.

    The handle() method can find the request as self.request, the
    client address as self.client_address, and the server (in case it
    needs access to per-server information) as self.server.  Since a
    separate instance is created for each request, the handle() method
    can define arbitrary other instance variariables.

    """

    def __init__(self, request, client_address, server):
        self.request = request
        self.client_address = client_address
        self.server = server
        self.setup()
        try:
            self.handle()
        finally:
            self.finish()

    def setup(self):
        pass

    def handle(self):
        pass

    def finish(self):
        pass


实例:


#!/usr/bin/env python
# -*- coding:utf-8 -*-
import SocketServer

class MyServer(SocketServer.BaseRequestHandler):

    def handle(self):
        # print self.request,self.client_address,self.server
        conn = self.request
        conn.sendall('欢迎致电 10086,请输入1xxx,0转人工服务.')
        Flag = True
        while Flag:
            data = conn.recv(1024)
            if data == 'exit':
                Flag = False
            elif data == '0':
                conn.sendall('通过可能会被录音.balabala一大推')
            else:
                conn.sendall('请重新输入.')


if __name__ == '__main__':
    server = SocketServer.ThreadingTCPServer(('127.0.0.1',8009),MyServer)
    server.serve_forever()



#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket


ip_port = ('127.0.0.1',8009)
sk = socket.socket()
sk.connect(ip_port)
sk.settimeout(5)

while True:
    data = sk.recv(1024)
    print 'receive:',data
    inp = raw_input('please input:')
    sk.sendall(inp)
    if inp == 'exit':
        break

sk.close()


源码精简:


import socket
import threading
import select


def process(request, client_address):
    print request,client_address
    conn = request
    conn.sendall('欢迎致电 10086,请输入1xxx,0转人工服务.')
    flag = True
    while flag:
        data = conn.recv(1024)
        if data == 'exit':
            flag = False
        elif data == '0':
            conn.sendall('通过可能会被录音.balabala一大推')
        else:
            conn.sendall('请重新输入.')

sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk.bind(('127.0.0.1',8002))
sk.listen(5)

while True:
    r, w, e = select.select([sk,],[],[],1)
    print 'looping'
    if sk in r:
        print 'get request'
        request, client_address = sk.accept()
        t = threading.Thread(target=process, args=(request, client_address))
        t.daemon = False
        t.start()

sk.close()


如精简代码可以看出,SocketServer的ThreadingTCPServer之所以可以同时处理请求得益于 select 和 Threading

ForkingTCPServer

ForkingTCPServer和ThreadingTCPServer的使用和执行流程基本一致,只不过在内部分别为请求者建立 “线程”  和 “进程”。

基本使用:


#!/usr/bin/env python
# -*- coding:utf-8 -*-
import SocketServer

class MyServer(SocketServer.BaseRequestHandler):

    def handle(self):
        # print self.request,self.client_address,self.server
        conn = self.request
        conn.sendall('欢迎致电 10086,请输入1xxx,0转人工服务.')
        Flag = True
        while Flag:
            data = conn.recv(1024)
            if data == 'exit':
                Flag = False
            elif data == '0':
                conn.sendall('通过可能会被录音.balabala一大推')
            else:
                conn.sendall('请重新输入.')


if __name__ == '__main__':
    server = SocketServer.ForkingTCPServer(('127.0.0.1',8009),MyServer)
    server.serve_forever()


Python全栈工程师(14:Socket编程3-socketserver并发)_服务器_03

Python全栈工程师(14:Socket编程3-socketserver并发)_服务器_03

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket


ip_port = ('127.0.0.1',8009)
sk = socket.socket()
sk.connect(ip_port)
sk.settimeout(5)

while True:
    data = sk.recv(1024)
    print 'receive:',data
    inp = raw_input('please input:')
    sk.sendall(inp)
    if inp == 'exit':
        break

sk.close()


以上ForkingTCPServer只是将 ThreadingTCPServer 实例中的代码:

server = SocketServer.ThreadingTCPServer(('127.0.0.1',8009),MyRequestHandler)

          变更为:         

server = SocketServer.ForkingTCPServer(('127.0.0.1',8009),MyRequestHandler)


SocketServer的ThreadingTCPServer之所以可以同时处理请求得益于 select 和 os.fork

源码剖析参考 ThreadingTCPServer