Python之路:(十六)socketserver模块

回顾原生socket模块,一次只能处理一个客户端请求,当这个客户端请求全部完成断开连接了,再去处理另外一个客户端得请求.这样服务端得资源其实还有很多是处于空闲状态的.然后我们又通过IO多路复用的方式解决了因为网络IO繁忙而造成浪费服务端资源得问题,通过select侦听网路IO,并以读写分离得形式实现了接受处理多个请求.

然而这样并不完善,如果同时来多个请求,还是会挂起堵塞.无法并发得处理客户端得请求.还好python给我们提供了一个并发处理socket得功能,那就是socketserver模块.

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

如下图:

第一次连接后,数据通讯就通过线程或进程进行数据交换(红色箭头)

socketserver拓扑图

ThreadingTCPServer

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

ThreadingTCPServer基础

使用ThreadingTCPServer:

  • 创建一个继承自 socketserver.BaseRequestHandler 的类
  • 类中必须定义一个名称为 handle 的方法
  • 启动ThreadingTCPServer

服务端

import socketserver


class MyServer(socketserver.BaseRequestHandler):

    def handle(self):
        conn = self.request  # 接受连接请求,获取客户端socket对象
        conn.sendall('欢迎致电 10086,请输入1xxx,0转人工服务.')  # 回一条消息给客户端
        while True:
            data = str(conn.recv(1024), encoding='utf-8')
            # 如果客户端没有输入内容则跳出本次循环什么都不执行
            if data == len(data):
                break
            # 根据用户输入的内容执行相应的操作
            elif data == '0':
                conn.sendall('通过可能会被录音.balabala一大推')
            else:
                conn.sendall('请重新输入.')


if __name__ == '__main__':
    # 实例话对象,传递一个元祖设置启动的IP/PORT,第二个参数把自己定义的类写上作为SocketServer.ThreadingTCPServer的构造函数
    server = socketserver.ThreadingTCPServer(('127.0.0.1',8000), MyServer)
    # 对象调用父类的启动方法
    server.serve_forever()

客户端

import socket


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

while True:
    data = sk.recv(1024)
    print('receive: %s',data)
    inp = input('please input:')
    sk.sendall(bytes(inp), encoding='utf-8')
    if inp == 'exit':
        break

sk.close()

ThreadingTCPServer源码剖析

ThreadingTCPServer的类图关系如下:

ThreadingTCPServer类结构

学会看源码非常重要!不能仅仅光会用!大赞~ 知道他的过程和实现~ 怎么学会看源码呢?多看然后画类图,如上图!!!

在理解的时候可以把他们想象为,把所有需要用的方法,都在ThreadingTCPServer中

内部调用流程为:

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

源码精简版

import socket
import threading
import select


def process(request, client_address):  # 模拟定义的handle()方法,这个方法内的代码是socket server与Client端交互代码
    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:  # 监控sk文件句柄,循环接受连接请求
    r, w, e = select.select([sk, ], [], [], 1)
    print('looping')
    if sk in r:  # 当sk文件句柄发生变化的时候说明是新的客户端连接过来了
        print('get request')
        request, client_address = sk.accept()
        t = threading.Thread(target=process, args=(request, client_address))  # 创建一个线程,并调用自己定义的process方法执行~然后样客户端与之交互
        t.daemon = False
        t.start()

sk.close()

如精简代码可以看出,SocketServer的ThreadingTCPServer之所以可以同时处理请求得益于 selectThreading 两个东西,其实本质上就是在服务器端为每一个客户端创建一个线程,当前线程用来处理对应客户端的请求,所以,可以支持同时n个客户端链接(长连接)。

ForkingTCPServer

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

基本使用

服务端

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__':
    # 这里使用socketserver ForkingTCPServer类创建对象
    server = socketserver.ForkingTCPServer(('127.0.0.1',8000),MyServer)
    server.serve_forever()

客户端

import socket


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

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

sk.close()

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

server = SocketServer.ThreadingTCPServer(('127.0.0.1',8000),MyRequestHandler)
变更为:
server = SocketServer.ForkingTCPServer(('127.0.0.1',8000),MyRequestHandler)

SocketServer的ThreadingTCPServer之所以可以同时处理请求得益于 selectos.fork 两个东西,其实本质上就是在服务器端为每一个客户端创建一个进程,当前新创建的进程用来处理对应客户端的请求,所以,可以支持同时n个客户端链接(长连接)。

源码剖析参考: ThreadingTCPServer

socketserver 模块练习

开发一个支持多用户在线的FTP程序

要求:

  1. 用户加密认证
  2. 允许同时多用户登录
  3. 每个用户有自己的家目录 ,且只能访问自己的家目录
  4. 对用户进行磁盘配额,每个用户的可用空间不同
  5. 允许用户在ftp server上随意切换目录
  6. 允许用户查看当前目录下文件
  7. 允许上传和下载文件,保证文件一致性
  8. 文件传输过程中显示进度条
  9. 附加功能:支持文件的断点续传

可参考: https://github.com/LiangXianSen/python_-study/tree/master/day9/FTP

文章目录
  1. 1. ThreadingTCPServer
    1. 1.1. ThreadingTCPServer基础
    2. 1.2. 服务端
    3. 1.3. 客户端
    4. 1.4. ThreadingTCPServer源码剖析
    5. 1.5. 源码精简版
  2. 2. ForkingTCPServer
    1. 2.1. 基本使用
      1. 2.1.1. 服务端
      2. 2.1.2. 客户端
  3. 3. socketserver 模块练习
|