实现 TCPServer类:
- 功能:
- 启动 server_start
- 处理请求 get_request (获取客户端的TCP连接)、 process_request(处理请求)、close_request(关闭TCP连接)
- 正常关闭 shutdown
- 属性:
- 自己的套接字 --> 用于接收客户端请求
- 服务端地址
- 处理器类 Handler
server/myTCPServer
#! _*_ encoding=utf-8 _*_
import socket
import threading
from ThreadPool.myThreadPool import myThreadPool
from ThreadPool.myTask import AsyncTask
from ThreadPool.myTask import Task
class TCPServer:
def __init__(self, server_addr, handler_class):
self.server_addr = server_addr
self.HandlerClass = handler_class # 处理请求的类
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.is_shutdown = False # 关闭服务器标记
self.thread_pool = myThreadPool()
# 启动服务器
def server_start(self):
self.socket.bind(self.server_addr)
self.socket.listen(10)
# 启动线程池
self.thread_pool.start()
while not self.is_shutdown:
# 1. 接收请求
request, client_addr = self.get_request()
# 2. 处理请求 --> 可能有异常
try:
# self.process_request(request, client_addr)
# 开启一个新的线程处理请求
self.process_request_multithread(request, client_addr)
except Exception as e:
print(e)
# finally:
# # 3. 关闭连接
# self.close_request(request)
self.thread_pool.join()
# 接收请求
def get_request(self):
return self.socket.accept()
# 处理请求
def process_request(self, request, client_addr):
handler = self.HandlerClass(self, request, client_addr) # self需要传递进去:表示HandlerClass服务于TCPServer
handler.handle()
# 3. 关闭连接
self.close_request(request)
# 多线程处理请求
def process_request_multithread(self, request, client_addr):
# t = threading.Thread(target=self.process_request,
# args=(request, client_addr))
# t.start()
# 实现线程池处理请求
task = Task(self.process_request, request, client_addr)
self.thread_pool.put(task)
# 关闭请求
def close_request(self, request):
request.shutdown(socket.SHUT_WR)
request.close()
# 关闭服务器
def shutdown(self):
self.is_shutdown = True
实现网络请求处理器 Handler类
封装TCP连接处理逻辑
实现接口类 BaseHandler
为了更好的抽象与分离,定义父类:BaseRequestHandler(接口)
- BaseHandler定义属性:
- server 为哪个server服务
- request 具体的请求(socket object)
- client_address 客户端的socket
- 接口函数:
- handle() 留给子类实现
class BaseRequestHandler:
def __init__(self, server, request, client_addr):
# 为server服务
self.server = server
self.request = request
self.client_addr = client_addr
# 接口 具体由子类实现
def handle(self):
pass
字节流处理器 BaseStreamHandler
- 功能:
- 编码 encode / 解码 decode: 字节流 <==> 字符串
- 读 消息: read / readline
- 写 消息: write_content
- 发送 消息 send
- 关闭 close
- 属性:
- 写缓存:先将字节流(用encode转换)写入缓存,在send出去
读消息:通过request将消息读出来:
为了方便请求的读和写,将请求连接分离成 读 / 写 两个文件描述符
对于read:直接调用socket对文件描述符的方法:read
对于readline,调用readline
class StreamRequestHandler(BaseRequestHandler):
def __init__(self, server, request, client_addr):
BaseRequestHandler.__init__(self, server, request, client_addr)
"""
makefile(...) -> an I/O stream connected to the socket
The arguments are as for io.open() after the filename, except the only
supported mode values are 'r' (default), 'w' and 'b'.
"""
# XXX refactor to share code?
self.rfile = self.request.makefile('rb')
self.wfile = self.request.makefile('wb')
self.write_buffer = []
# 编码:将字符串编码为字节码:
def encode(self, msg):
if not isinstance(msg, bytes):
msg = bytes(msg, encoding='utf-8')
return msg
# 解码:将字节码->字符串
def decode(self, msg):
if isinstance(msg, bytes):
msg = msg.decode()
return msg
# 读消息
def read(self, length):
msg = self.rfile.read(length)
return self.decode(msg)
# 读取一行消息, 65536=HTTP请求报文最大长度
def readline(self, length=65536):
msg = self.rfile.readline(length).strip()
return self.decode(msg)
# 接收内容,并写入缓存
def write_content(self, msg):
msg = self.encode(msg)
self.write_buffer.append(msg)
# 发送消息:写完成以后,将buffer内容发送出去
def send(self):
for line in self.write_buffer:
self.wfile.write(line)
# Q: 怎么将消息发送出去? flush?
self.wfile.flush()
self.write_buffer = [] # 将缓存区清空
def close(self):
self.wfile.close()
self.rfile.close()
测试
#! _*_ encoding=utf-8 _*_
import threading, socket, time
from server.myTCPServer import TCPServer
from handler.BaseHandler import StreamRequestHandler
from server.myHTTPServer import BaseHTTPServer
from handler.BaseHTTPHandler import BaseHTTPRequestHandler
class TestBaseRequestHandler(StreamRequestHandler):
# 实现handle()接口的具体的处理逻辑
def handle(self):
# 实现 echo 功能
# 测试是否可以处理并发连接
msg = self.readline()
print('Server receive msg: ' + msg)
time.sleep(1) # 模拟每个客户端处理都需要花费1s的时间
self.write_content(msg)
self.send()
# 测试SocketServer(TCPServer)
class SocketServerTest:
def run_server(self):
tcp_server = TCPServer(('127.0.0.1', 8888), TestBaseRequestHandler)
tcp_server.server_start()
# 实现客户端的具体连接逻辑
def client_connect(self):
client = socket.socket()
client.connect(('127.0.0.1', 8888))
client.send(b'Hello TCPServer\r\n')
msg = client.recv(1024)
print('Client receive msg: ' + msg.decode())
# 生成客户端
def gen_clients(self, num):
clients = []
for i in range(num):
client_thread = threading.Thread(target=self.client_connect) # 每个线程内部都有自己的处理逻辑
clients.append(client_thread)
return clients
def run(self):
server_thread = threading.Thread(target=self.run_server)
server_thread.start()
clients = self.gen_clients(10)
for client in clients:
client.start()
server_thread.join()
for client in clients:
client.join()
class BaseHTTPRequestHandlerTest:
def run_server(self):
BaseHTTPServer(('127.0.0.1', 9999), BaseHTTPRequestHandler).server_start()
def run(self):
self.run_server()
if __name__ == '__main__':
SocketServerTest().run()
# BaseHTTPRequestHandlerTest().run()
-
问题: TCPServer只有一个主线程,可以接收、处理Client的请求,但只能串行,并发度不高:
-
解决:多线程模型,对每个请求创建一个新的线程处理
-
问题:创建和消耗线程由时间、资源的开销
-
优化:使用线程池模型:在启动TCPServer的同时启动线程池,每次新的请求提交给线程池处理
-
ERROR: server/myTCPServer/server_start()
要将关闭连接的操作,放到process_request中去,也就是新的线程执行逻辑中去,
并发:网络服务器的多线程处理模型
问题:新的Client连接会被阻塞
原因:TCPServer只有一个主线程进行处理请求
改进:
实现HTTP协议服务器
HTTP (HyperText Transfer Protocol)
地址格式:<协议>://<网站域名>:<端口>/<文件路径>
Web 2.0 操作方法:
GET 获取指定的服务端资源
POST 把数据提交给服务端
DELETE 删除指定的服务端资源
UPDATE 更新指定的服务端资源
- Web 服务器工作流程:
HTTP 请求报文
- 格式:
请求行:只有唯一一行
请求头: 请求的附加信息(请求设备信息),Key:Value 键值对格式
Content-Length: 请求内容长度(字节)
<空行>
请求内容(可选):发送数据(提交给服务器后台)
HTTP 应答报文
- 格式:
状态行:
应答头:
Content-Length: 应答内容长度
<空行>
应答内容:
- 状态行:状态码
- 请求头与应答头统称为消息头: 附加信息
实现基础的HTTP请求处理器
- 功能:
- 解析请求
- 处理请求 parse_request
- 返回结果 : write_header, write_response, write_error
编写自定义HTTP应用
GET方法
POST 方法
将数据提交到服务端进行处理
总结
展望
完善异常机制
状态码
日志
长连接
安全 https
性能
引入线程池
引入事件驱动引擎(epoll、select)IO复用