目录
前言
多线程Web代理服务器
过程
解释
代码
前置知识
(1)进程 与 线程
进程与线程的一个简单解释 - 阮一峰的网络日志 (ruanyifeng.com)
(2) 多线程
撬开多线程的大门——学习多线程必须掌握的基本概念 - 姜承轩 - 博客园 (cnblogs.com)
注意!先用原端口(就是初始能上网的端口),先不要切8899端口,打开 gaia.cs 网页
然后,对应目录下打开 cmd,py .py 代码,出现ready to serve... 后
再切 8899 端口,然后刷新(打乱步骤无法出现预期结果)
(1)配置浏览器
这里最好把端口改成8899或者其他的,如果按你原来的端口来改代码中的 port ,很可能出现端口被占用的情况,那么打开网页后,cmd中没有反应
(2)运行对应目录的 .py 代码
(3)打开网页
http://gaia.cs.umass.edu/wireshark-labs/INTRO-wireshark-file1.html
与此同时,cmd中显示
卡在了 hostname
Ready to serve...
Received a connection from: ('127.0.0.1', 50213)
File Exist: false
Creating socket on proxyserver
Host Name:
Illegal request
貌似 Edge 不行,得切 IE 浏览器(不切貌似也能用)
a.
Microsoft Edge
b. edge浏览器internet选项灰色怎么办?-edge浏览器internet选项灰色无法使用的解决方法 - 极光下载站 (xz7.com)
2次 Host Nameillegal request 后成功了
(4)同一目录下出现缓存的 网页文件,记事本打开
内容包括 响应头 和 正文
注意,切 8899 端口后,浏览器打开其他网页都连接不上,因为默认占用原来的端口号来打开网页,需要重新切回去才能上网( (5)开始前,可以切回 8899 端口 )
(5)重新打开个 cmd,运行 .py,然后刷新 gaia.cs 网址,cmd显示
进一步注释,看代码
(1)
socket()
函数的两个参数
AF_INET
:指定使用IPv4地址族,用于TCP/IP网络SOCK_STREAM
:指定使用流式套接字,用于可靠的、基于连接的通信
# 创建TCP套接字
tcpSerSock = socket(AF_INET, SOCK_STREAM)
(2)
bind()
函数的两个参数解释如下:
''
:表示将服务器绑定到所有可用的网络接口上tcpSerPort
:指定要绑定的端口号
# 将套接字绑定到指定的地址和端口
tcpSerSock.bind(('', tcpSerPort))
(3) 浏览器是客户端,而
.py
在cmd中扮演服务器的角色的原因是,浏览器是一个HTTP客户端,负责发送HTTP请求给服务器,并接收服务器的响应。而代码中创建的服务器是一个TCP服务器,它监听指定的端口,接受来自客户端(例如浏览器)的连接请求,并向客户端发送响应(4)
tcpCliSock
是一个新的套接字对象,用于与客户端进行通信。addr
是客户端的地址信息。
tcpCliSock, addr = tcpSerSock.accept()
(5)
message.split()[1]
:将接收到的消息按空格分割,取第二部分,即HTTP请求中的路径部分.partition("//")[2]
:将路径部分按"//"分割,取第三部分,即主机名部分.replace('/', '_')
:将主机名中的斜杠替换为下划线
filename = message.split()[1].partition("//")[2].replace('/', '_')
(6)
fileExist = "false"
用于标记文件是否存在。在后续的判断中,如果该值为"false",则说明文件不存在(7)
try
和except IOError
用于异常处理。在try
代码块中尝试执行某个操作,如果在执行过程中发生了IOError
异常(文件不存在等),则跳转到except
代码块中进行相关处理(8)
open()
函数用于打开文件,并返回一个文件对象。参数解释如下
filename
:要打开的文件名。"r"
:以只读模式打开文件。(9)
.readlines()
方法用于从文件对象中读取所有行,并返回一个包含所有行的列表(10)
.send()
方法用于发送数据。参数是要发送的数据(11)
.connect((hostn, 80))
与远程服务器建立连接。参数是远程服务器的IP地址和端口号(12)
sendall(buff)
方法用于将数据发送给客户端。buff
是要发送的数据(13)
open('./' + filename, "w")
用于创建并打开一个文件,以便写入数据。filename
是文件名,"w"
表示以写入模式打开文件(14)
.writelines()
方法用于将字符串列表中的所有元素写入文件中。参数是要写入的字符串列表
#coding:utf-8
from socket import *
# 创建socket, 绑定到端口, 开始监听
tcpSerPort = 8899
tcpSerSock = socket(AF_INET, SOCK_STREAM)
# AF_INET, IPv4地址簇, 用于TCP/IP
# SOCK_STREAM, 流式socket, 用于TCP
# Prepare a server socket
tcpSerSock.bind(('', tcpSerPort)) # Server地址和端口
tcpSerSock.listen(3) # 最大连接数
while True:
# 开始从客户端接受请T求
print('Ready to serve...baga!')
tcpCliSock, addr = tcpSerSock.accept() # 新的套接字 和 客户端地址
print('Received a connection from: ', addr) # 客户端地址 = IP地址 + 端口号 = 元组
message = tcpCliSock.recv(4096).decode() # 接收字符串并解码
# 从请求中解析出filename
filename = message.split()[1].partition("//")[2].replace('/', '_')
# 按空格分隔取第2部分; 按//分隔取第3部分; 把/替换为_
fileExist = "false"
# try 执行中遇到异常, 就执行except
try:
# 检查缓存中是否存在该文件
f = open(filename, "r") # 只读方式打开文件
outputdata = f.readlines() # 读取所有行并返回列表
fileExist = "true"
print('File Exists!')
# 缓存中存在该文件,把它向客户端发送
for i in range(0, len(outputdata)):
tcpCliSock.send(outputdata[i].encode())
print('Read from cache')
# 缓存中不存在该文件,异常处理
except IOError:
print('File Exist: ', fileExist)
if fileExist == 'false':
# 在代理服务器上创建一个tcp socket
print('Creating socket on proxyserver')
c = socket(AF_INET, SOCK_STREAM)
# 解析请求消息中的主机名
hostn = message.split()[1].partition("//")[2].partition("/")[0]
# 按空格分隔取第2部分; 按//分隔取第3部分; 按/分隔取第1部分
print('Host Name: ', hostn)
try:
# 连接到远程服务器80端口
c.connect((hostn, 80)) # ip 和 端口
print('Socket connected to port 80 of the host')
# 请求信息发送到远程服务器
c.sendall(message.encode())
# Read the response into buffer
# 从远程服务器读取响应信息
buff = c.recv(4096)
# 发送响应信息到客户端socket
tcpCliSock.sendall(buff)
# 缓存cache中创建并写入请求文件的副本
tmpFile = open("./" + filename, "w") # 只写方式打开文件
# 解码后替换\r\n为\n
tmpFile.writelines(buff.decode().replace('\r\n', '\n'))
tmpFile.close() # 关闭文件
except:
# HTTP response message for file not found
print("Illegal request")
# Close the client and the server sockets
tcpCliSock.close() # 关闭 Client socket
tcpSerSock.close() # 关闭 Server socket