1. 基础框架
def main():
pass
if __name__ == '__main__':
main()
复制代码
2. 创建服务器socket
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
tcp_server_socket.setopt()
local_addr = ("",7890)
tcp_server_socket.bind(local_addr)
tcp_server_socket.listen(128)
复制代码
socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
在tcp的四次挥手中,如果哪一方先调用了close,哪一方就会等待2*MSL(最大报文寿命),在此期间,占用的端口资源不会释放,如果是服务器主动close,会导致重启后报错“端口被占用”,这句话的作用,设置socket当close立即释放资源
3. 接受请求,处理请求
设置监听套接字为非阻塞
tcp_server_socket.setblocking(False)
复制代码
此时当监听套接字调用accept的时候有两种情况: 1. 正常接收端客户端链接,返回一个元组(clien_socket,client_addr) 2. 报错
client_socket_list = []
while True:
try:
client_socket,client_addr = tcp_server_socket.accept()
except:
pass
else:
client_socket_list.append(client_socket)
复制代码
由于服务器不止服务一个客户端,所以链接进行的socket可以保存在一个列表中,不停的轮询
for client_socket in client_socket_list:
recv_data = client_socket.recv(1024)
复制代码
此时,如果clent_socket是阻塞的方式,会导致程序卡在这里,后面的客户端无法进行服务,所以,必须把client_socket也设置成非阻塞,此时如果调用clent_socket.recv(1024)会产生一下两种情况:
1. 正常收到数据(有真实数据,空数据(客户端调用了close的情况))
2. 报错
复制代码
else:
client_socket.setblocking(False)
client_socket_list.append(client_socket)
for client_socket in client_socket_list:
try:
recv_data = client_socket.recv(1024)
except:
pass
else:
# 收到真实数据
if recv_data:
# 为客户端服务
else: # 远程的客户端已经关闭了
cliet_socket.close()
client_socket_list.remove(cliet_socket)
复制代码
在上述代码中,如果recv_data为空,表示客户端主动断开了链接,导致recv收到一个空数据包,此时对于服务器而言,没必要在保留和这个客户端通信的socket,直接关闭,并且从列表中移除
4. 为客户端服务
由于客户端是浏览器,和服务器交流采用的是http协议 http协议包含两大部分:
- 请求部分
- 请求行
GET /index.html HTTP/1.1
请求方法,资源路径,http协议版本 - 请求头
- 请求体
- 请求行
- 响应部分
- 响应行
HTTP/1.1 200 OK
- 响应头
- 响应体
- 响应行
响应体是真正浏览器需要的数据,而在响应头中,有该数据的相关描述,比如Content-Type: text/html; charset=utf-8
,表示响应体是html文本数据,并且采用utf-8编码,比如Content-Length: 128
,表示响应体中数据的长度,如果服务器希望做到长链接,必须添加这个字段,让浏览器能够清楚验证啥时候数据请求完毕
def server_client(clent_socket,recv_data):
recv_data = recv_data.decode("utf-8)
# 由于recv_data是HTTP协议的请求字符串,可以按行进行分割
request_lines = recv_data.splitlines()
# 在请求行中获取请求的资源路径
file_name = request_lines[0].split(" ")[1]
复制代码
当获得资源路径后,就可以在服务器中加载资源,由于客户端的不确定性,导致资源路径可能是错的,所以在读取资源的时,需要捕获异常
try:
with open(file_name,"rb") as f:
response_body = f.read()
# 一个完整的响应 = 响应行+响应头+'\r\n'+响应体
response_header = "HTTP/1.1 200 OK\r\n"
except:
response_body = b'file not found'
response_header = "HTTP/1.1 404 NOT FOUND\r\n"
# 为了实现长链接,需要添加Content-Length
response_header += "Content-Length: %d\r\n"%len(response_body)
response = response_header.encode() + "\r\n"+ response_body
client_socket.send(response)
复制代码
此时服务器不能调用client_socket.close()
方法,否则就是短链接,效率太低