用python编写http server(1)-简单服务器
对于wsgi来说,http server主要是处理了http请求,并提取了http请求的部分信息。http server最重要的还是通过多线程、多进程、协程等来处理并发请求。我们可以通过自己编写http server来熟悉整个请求处理流程,这里我们只关心tcp层。
先来个最简单的server
import socket
class SimpleServer():
def __init__(self, host, port):
self.connect = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP)
# 处理TIME_WAIT
self.connect.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.connect.bind((host, port))
def serve_forever(self):
self.connect.listen()
while 1:
try:
conn, addr = self.connect.accept()
except KeyboardInterrupt:
break
print('Connected by', addr)
self.handle_request(conn)
self.connect.close()
def handle_request(self, conn):
data = conn.recv(1024)
print(data)
conn.close()
if __name__ == "__main__":
server = SimpleServer("localhost", 8000)
server.serve_forever()
socket.socket可以创建多种类型的socket,这里主要测试ipv4的tcp。
可以看到创建完socket使用了setsockopt函数。这里有一个很有趣的现象是,如果去掉这函数,重新启动server时,会提示端口已绑定。这是因为tcp四次挥手时,首先关闭的一方有个TIME_WAIT2,设置了端口重用后就可以立刻重新启动了。
新开启个console:$ curl http://localhost:8000
可以看到server有了反应:
Connected by ('127.0.0.1', 59490)
b'GET / HTTP/1.1\r\nHost: localhost:8000\r\nUser-Agent: curl/7.58.0\r\nAccept: /\r\n\r\n'
为了模拟单进程单线程server的问题,我们在handle_request函数里增加个time.sleep(5)
def handle_request(self, conn):
data = conn.recv(1024)
print(data)
time.sleep(5)
conn.close()
同时启动多个console,输入curl http://localhost:8000
可以看到server在第一个curl 处理完后才开始处理第二个curl ,期间第二个curl一直被阻塞。
这里必须明确的是,其实第二个curl的请求已经发送到server端了,但因为curl一直在等待server结束连接,所以一直在阻塞。
程序调用socket的write函数时,数据从程序缓冲区复制至socket发送缓冲区,在对端ack返回确认发送成功后就从write函数返回。
可以写个简单的client验证:
import socket
class SimpleClient():
def __init__(self, host, port):
self.connect = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP)
self.connect.connect((host, port))
def sendall(self, _bytes):
self.connect.sendall(_bytes)
self.connect.close()
if __name__ == "__main__":
server = SimpleClient("localhost", 8000)
server.sendall(b"hello,word")
先启动curl,然后启动client,可以发现client发送完数据后直接结束。