Http协议:
超文本传输协议。规定了浏览器和服务器之间的通讯格式的协议。请求的时候需要按照什么格式请求,响应的时候需要按照什么格式返回。
请求头:
GET /zfca/login;jsessionid=C2DD29A2DE64053CFBE8D56E0C0BA868?yhlx=student&login=0122579031373493729&url=%23 HTTP/1.1 # 必须要有,如果没有就不可能成功的请求该服务器
Host: zfca.cqucc.com.cn
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36 Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=56719C5ECC4FD76FF49A9216E4213175
响应头:
HTTP/1.1 200 OK # 返回必须要有,不然浏览器不可以接收
Server: Apache-Coyote/1.1
Pragma: no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT Cache-Control: no-cache Cache-Control: no-store
Content-Type: text/html;charset=GBK
Content-Language: zh-CN
Content-Length: 8092
Date: Thu, 07 Nov 2019 13:38:33 GMT
这些都是http协议规定的,必须按照这种格式进行传输才会有相应的结果。
web服务器的简单实现:
单进程实现并发的服务器,服务多个客服端。但是由于是轮询的方式所以效率比较低。
import socket import time def client_server(conn,request): try: file_name = request.split()[1] # 提取访问的文件名 except IndexError: file_name = "/" # 如果没有该文件就返回首页 if file_name == "/index.html": file_name = "/index.html" # 如果直接访问没有文件名就直接访问首页 # print(file_name) try: f = open("./html" + file_name, "rb") # 打开文件 except: # 如果没有该文件就返回404 response = "HTTP/1.1 404 NOT FOUND " response += " " response += "NOT FOUND" conn.send(response.encode("utf-8")) else: # 如果有该文件,则读取该文件中的数据并返回 response_body = f.read() # 读取文件内容 f.close() response_header = "HTTP/1.1 200 OK " response_header += "Content-Length:%d "%len(response_body) response_header += " " response = response_header.encode("utf-8") + response_body conn.send(response) # 发送header def main(): server = socket.socket() # 创建套接字 # server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind(("127.0.0.1", 8082)) # 绑定端口 server.setblocking(False) #设置非阻塞模式
server.listen() # 监听 conn_list = list() while 1: # time.sleep(0.5) try: conn,addr = server.accept() # 接受客服端链接 except Exception as ret: pass # print("没有客服端连接") else: conn.setblocking(False) conn_list.append(conn) for conn in conn_list: try: request = conn.recv(1024).decode() # 接受数据,网络传送的数据为bytes需要解码 except Exception as ret: pass # print("没有数据传输") else: if request: client_server(conn,request) else: # print("关闭服务器") conn_list.remove(conn) conn.close() if __name__ == '__main__': main()
使用epoll来实现web服务器:
由于for循环的轮询的方式使自己写的单进程实现的web服务器的效率比较低。Nginx是使用epoll来实现的,epoll有自己的独立内存,每个新来的客服端的套接字都加入到了epoll的独立内存空间;且epoll采用的是事件通知,监听到了某个套接字需要接收数据或由新的客服端连接的时候,才会做相应的操作,节省了大量的时间,所以epoll的效率非常的高。
import socket import select import re def service_client(new_socket,request): """客服端需要什么我们就返回什么""" try: file_name = request.split()[1] except Exception as e: file_name = "" if file_name == "/": file_name = "/index.html" try: f = open("./html"+file_name, "rb") except Exception as e: response = "HTTP/1.1 404 NOT FOUND " response += " " response += "------file not found-----" new_socket.send(response.encode("utf-8")) else: html_content = f.read() f.close() response_body = html_content response_header = "HTTP/1.1 200 OK " response_header += "Content-Length:{} ".format(len(response_body)) response_header += " " response = response_header.encode("utf-8") + response_body new_socket.send(response) def main(): sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sk.bind(("127.0.0.1",9999)) sk.listen(128) sk.setblocking(False) epl = select.epoll() # 实例化一个内存空间 epl.register(sk.fileno(),select.EPOLLIN) # 注册监听套接字 fd_event_dict = dict() # 保存新的套接字,以便实现文件描述符和套接字的对应关系 while 1: fd_event_list = epl.poll() #默认阻塞,等待os检测到数据来,才解堵塞,有数据来就会接受到需要接受数据的套接字的文件描述符和事件列表[(fd,event)] for fd,event in fd_event_list: if fd == sk.fileno(): # 如果文件描述符是监听套接字,则表明有新的客服端到来,需要建立连接 new_socket,addr = sk.accept() # 建立新的套接字 epl.register(new_socket.fileno(),select.EPOLLIN) # 注册新的套接字 fd_event_dict[new_socket.fileno()] = new_socket # 将新套接字的fd和socket实现对应关系 elif event == select.EPOLLIN: # 表明是新的套接字要接受数据 request = fd_event_dict[fd].recv(1024).decode("utf-8") # 接收数据恩 if request: # 如果数据不为空 service_client(fd_event_dict[fd],request) else: # 如果数据为空 fd_event_dict[fd].close() # 关闭该套接字 epl.unregister(fd) #从epl中注销该套接字 del fd_event_dict[fd] # 从fd和套接字对应关系中删除该套接字 if __name__ == '__main__': main()