1. 写在前面
这里总结的并不够详细,有时间了再进行补充。
2. 设计思路
HTTP协议是建立在TCP上的
1. 建立服务器端TCP套接字(绑定ip,port),等待监听连接:listen
(2. 打开浏览器(client)访问这个(ip,port),服务器端接收连接:accept)
3. 获取浏览器的请求内容:data = recv(1024)
# 由于浏览器发送的request是HTTP格式的,需要解码
4. 将接收的报文节解码:decode
# 解析解码后的数据
5. 根据行分切数据
6. 解析首部行(header)为:方法,请求路径+文件名
7. 根据解析首部行获取的数据来查找并获取文件内容
8. 构建响应报文(也要是HTTP报文格式的),包括首部行响应信息(200 OK或是file cannot found)
9. 编码响应报文:encode
10. 关闭socket连接
3. 两个版本
3.1 多线程版本
这里采用多线程的方法对每一个请求连接本机的请求建立连接,缺点在于除非关闭服务器程序,否则已建立连接的套接字不会被释放,耗费资源
#!/usr/bin/env python3#-*- coding: UTF-8 -*-
importsocketimportthreadingdefhandleRequest(tcpSocket):#1. Receive request message from the client on connection socket
requestData = tcpSocket.recv(1024)#2. Extract the path of the requested object from the message (second part of the HTTP header)
requestList = requestData.decode().split("\r\n")
reqHeaderLine=requestList[0]print("request line:" +reqHeaderLine)
fileName= reqHeaderLine.split(" ")[1].replace("/", "")#3. Read the corresponding file from disk
try:
file= open("./" + fileName, 'rb') #read the corresponding file from disk
print("fileName:" +fileName)#4. Store in temporary buffer
content = file.read().decode() #store in temporary buffer
file.close()
resHeader= "HTTP/1.1 200 OK\r\n" +\"Server: 127.0.0.1\r\n" + "\r\n"response= (resHeader + content).encode(encoding="UTF-8") #send the correct HTTP response
exceptFileNotFoundError:
content= "404 NOT FOUND\n"resHeader= "HTTP/1.1 404 Not Found\r\n" +\"Server: 127.0.0.1\r\n" + "\r\n"response= (resHeader + content).encode(encoding="UTF-8") #send the correct HTTP response error
#5. Send the correct HTTP response error
tcpSocket.sendall(response)#6. Send the content of the file to the socket
else:
tcpSocket.sendall(response)#7. Close the connection socket
tcpSocket.close()defstartServer(serverAddress, serverPort):#1. Create server socket
serverSocket =socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)#2. Bind the server socket to server address and server port
serverSocket.bind((serverAddress, serverPort))#3. Continuously listen for connections to server socket
serverSocket.listen(0)#4. When a connection is accepted, call handleRequest function, passing new connection socket (see
#https://docs.python.org/3/library/socket.html#socket.socket.accept)
whileTrue:try:print("wait for connecting...")print("while true")
tcpSocket, clientAddr=serverSocket.accept()print("one connection is established,", end="")print("address is: %s" %str(clientAddr))
handleThread= threading.Thread(target=handleRequest, args=(tcpSocket,))
handleThread.start()exceptException as err:print(err)break
#5. Close server socket
serverSocket.close()if __name__ == '__main__':whileTrue:try:
hostPort= int(input("Input the port you want:"))
startServer("", hostPort)break
exceptException as e:print(e)continue
3.2 多进程版本
改进了多线程版本的“缺点”
importmultiprocessingimportsocket
defhandleReq(clientSocket):
requestData= clientSocket.recv(1024)
requestList= requestData.decode().split("\r\n")
reqHeaderLine=requestList[0]print("request line:" +reqHeaderLine)
fileName= reqHeaderLine.split(" ")[1].replace("/", "")try:
file= open("./" + fileName, 'rb') #read the corresponding file from disk
print("fileName:" + fileName) #查看文件名
exceptFileNotFoundError:
responseHeader= "HTTP/1.1 404 Not Found\r\n" +\"Server: 127.0.0.1\r\n" + "\r\n"responseData= responseHeader + "No such file\nCheck your input\n"content= (responseHeader + responseData).encode(encoding="UTF-8") #send the correct HTTP response error
else:
content= file.read() #store in temporary buffer
file.close()
resHeader= "HTTP/1.1 200 OK\r\n"fileContent01= "Server: 127.0.0.1\r\n"fileContent02=content.decode()
response= resHeader + fileContent01 + "\r\n" + fileContent02 #send the correct HTTP response
clientSocket.sendall(response.encode(encoding="UTF-8"))defstartServer(serverAddr, serverPort):
serverSocket=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
serverSocket.bind((serverAddr, serverPort))
serverSocket.listen(0)whileTrue:try:print("wait for connecting...")print("while true")
clientSocket, clientAddr=serverSocket.accept()print("one connection is established,", end="")print("address is: %s" %str(clientAddr))
handleProcess= multiprocessing.Process(target=handleReq, args=(clientSocket,))
handleProcess.start()#handle request
clientSocket.close()print("client close")exceptException as err:print(err)breakserverSocket.close()#while出错了就关掉
if __name__ == '__main__':ipAddr= "127.0.0.1"port= 8000startServer(ipAddr, port)
这个版本与多线程版本的区别:
1. 建立套接字时对套接字进行了相关设置【稍后解释】
2. 在开启新进程之后调用“clientSocket.close()”释放资源
对第一点不同的解释
python定义了setsockopt()和getsockopt(),一个是设置选项,一个是得到设置。这里主要使用setsockopt(),具体结构如下:
setsockopt(level,optname,value)
level定义了哪个选项将被使用。通常情况下是SOL_SOCKET,意思是正在使用的socket选项。它还可以通过设置一个特殊协议号码来设置协议选项,然而对于一个给定的操作系统,大多数协议选项都是明确的,所以为了简便,它们很少用于为移动设备设计的应用程序。
optname参数提供使用的特殊选项。关于可用选项的设置,会因为操作系统的不同而有少许不同。如果level选定了SOL_SOCKET,那么一些常用的选项见下表:
选项
意义
期望值
SO_BINDTODEVICE
可以使socket只在某个特殊的网络接口(网卡)有效。也许不能是移动便携设备
一个字符串给出设备的名称或者一个空字符串返回默认值
SO_BROADCAST
允许广播地址发送和接收信息包。只对UDP有效。如何发送和接收广播信息包
布尔型整数
SO_DONTROUTE
禁止通过路由器和网关往外发送信息包。这主要是为了安全而用在以太网上UDP通信的一种方法。不管目的地址使用什么IP地址,都可以防止数据离开本地网络
布尔型整数
SO_KEEPALIVE
可以使TCP通信的信息包保持连续性。这些信息包可以在没有信息传输的时候,使通信的双方确定连接是保持的
布尔型整数
SO_OOBINLINE
可以把收到的不正常数据看成是正常的数据,也就是说会通过一个标准的对recv()的调用来接收这些数据
布尔型整数
SO_REUSEADDR
当socket关闭后,本地端用于该socket的端口号立刻就可以被重用。通常来说,只有经过系统定义一段时间后,才能被重用。
布尔型整数
本节在学习时,用到了SO_REUSEADDR选项,具体写法是:
S.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 这里value设置为1,表示将SO_REUSEADDR标记为TRUE,操作系统会在服务器socket被关闭或服务器进程终止后马上释放该服务器的端口,否则操作系统会保留几分钟该端口。