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 -*- import socket import threading def handleRequest(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(" ") 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 " + "Server: 127.0.0.1 " + " " response = (resHeader + content).encode(encoding="UTF-8") # send the correct HTTP response except FileNotFoundError: content = "404 NOT FOUND " resHeader = "HTTP/1.1 404 Not Found " + "Server: 127.0.0.1 " + " " 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() def startServer(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) while True: 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() except Exception as err: print(err) break # 5. Close server socket serverSocket.close() if __name__ == '__main__': while True: try: hostPort = int(input("Input the port you want: ")) startServer("", hostPort) break except Exception as e: print(e) continue
3.2 多进程版本
改进了多线程版本的“缺点”
import multiprocessing import socket def handleReq(clientSocket): requestData = clientSocket.recv(1024) requestList = requestData.decode().split(" ") 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) # 查看文件名 except FileNotFoundError: responseHeader = "HTTP/1.1 404 Not Found " + "Server: 127.0.0.1 " + " " responseData = responseHeader + "No such file Check your input " 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 " fileContent01 = "Server: 127.0.0.1 " fileContent02 = content.decode() response = resHeader + fileContent01 + " " + fileContent02 # send the correct HTTP response clientSocket.sendall(response.encode(encoding="UTF-8")) def startServer(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) while True: 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") except Exception as err: print(err) break serverSocket.close() # while出错了就关掉 if __name__ == '__main__': ipAddr = "127.0.0.1" port = 8000 startServer(ipAddr, port)
这个版本与多线程版本的区别:
1. 建立套接字时对套接字进行了相关设置【稍后解释】
2. 在开启新进程之后调用“clientSocket.close()”释放资源
对第一点不同的解释
下面解释的来源:https://www.jb51.net/article/50858.htm
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被关闭或服务器进程终止后马上释放该服务器的端口,否则操作系统会保留几分钟该端口。