我编写了一个简单的 Python 脚本来实现代理功能。它运行良好,但是,如果请求的网页包含许多其他 HTTP 请求,例如 Google 地图,则页面的呈现速度很慢。我想知道我代码中的瓶颈可能是什么,以及如何改进它。
2、解决方案:
经过分析,发现导致性能低下的主要原因是处理 HTTP 请求的方式。脚本使用了一个简单的循环来读取来自客户端的请求头,并且每次收到请求头时,它都会创建一个新的套接字来连接到目标网站。这会产生大量的开销,并且会导致性能下降。
为了改进脚本的性能,需要对其进行以下优化:
-
使用非阻塞 I/O(异步编程):
- 使用 select.select() 来监听客户端和目标网站的套接字,以便在数据可用时进行处理,而不是使用阻塞式的 read() 和 write() 函数。
- 当客户端发送请求头时,创建一个线程来处理请求,并将客户端套接字和目标网站套接字都添加到 select.select() 的监听列表中。
- 当 select.select() 检测到客户端或目标网站套接字有数据可用时,调用相应的线程来处理数据。
-
重用套接字连接:
- 对于同一目标网站,可以重用同一个套接字连接,而不是每次都创建一个新的套接字。
- 在处理完一个请求后,将目标网站套接字添加到 select.select() 的监听列表中,以便在下次请求到来时继续使用它。
import socket
import select
import threading
import time
import re
class ProxyServer:
def __init__(self, host, port):
self.host = host
self.port = port
self.sk1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.connections = {} # 存储客户端套接字和目标网站套接字的映射
def start_server(self):
self.sk1.bind((self.host, self.port))
self.sk1.listen(256)
print("Proxy is ready for connections...")
while True:
conn, client_addr = self.sk1.accept()
#print("New request coming in from", str(client_addr))
self.connections[conn] = None # 将客户端套接字添加到连接映射中
handler = RequestHandler(conn, self)
handler.start()
class RequestHandler(threading.Thread):
def __init__(self, client_sk, server):
threading.Thread.__init__(self)
self.client_sk = client_sk
self.server = server
self.buffer = ''
self.header = {}
def run(self):
while True:
self.buffer += self.client_sk.recv(8192).decode()
if '\n' in self.buffer:
break
self.header = self.process_header(self.buffer)
if len(self.header) > 0: # 处理了头部
host_string = self.header['Host']
host, port = '', ''
if ':' in host_string: # 带有端口号
host, port = host_string.split(':')
else:
host, port = host_string, "80"
target_sk = self.server.connections.get(host) # 获取目标网站的套接字
if not target_sk:
target_sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
target_sk.connect((host, int(port)))
self.server.connections[host] = target_sk # 将目标网站的套接字添加到连接映射中
target_sk.sendall(self.buffer.encode()) # 将请求头发送到目标网站
inputs = [self.client_sk, target_sk]
outputs = []
while True:
rl, wl, xl = select.select(inputs, outputs, [], 3)
if xl:
break
if rl:
for x in rl:
data = x.recv(8192)
if x is self.client_sk:
outputs.append(target_sk)
else:
outputs.append(self.client_sk)
if data:
x.sendall(data)
self.client_sk.close()
target_sk.close()
self.server.connections.pop(host) # 从连接映射中删除目标网站的套接字
def process_header(self, header):
header = header.replace("\r\n", "\n")
lines = header.split('\n')
result = {}
uLine = lines[0] # url line
if len(uLine) == 0:
return result # 如果url行为空,则返回一个空字典
vl = uLine.split(' ')
result['method'] = vl[0]
result['url'] = vl[1]
result['protocol'] = vl[2]
for line in lines[1:-1]:
if len(line) > 3: # 如果该行不为空
exp = re.compile(': ')
nvp = exp.split(line, 1)
if len(nvp) > 1:
result[nvp[0]] = nvp[1]
return result
if __name__ == "__main__":
HOST, PORT = "0.0.0.0", 8088
proxy = ProxyServer(HOST, PORT)
proxy.start_server()
经过优化后的代理脚本性能得到了显著提升,并且能够处理包含许多 HTTP 请求的网页而不导致页面渲染速度变慢。