HTML是一种用来定义网页的文本。http是在网络上传输·html的协议,用于浏览器和服务器的通信。
请求的发送都包含header和body。
浏览器--->服务器发送的请求如下:
GET / HTTP/1.1
Host: www.baidu.com
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: "Google Chrome";v="95", "Chromium";v="95", ";Not A Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: ······
服务器--->浏览器回送的数据格式:header是告诉浏览器的,body是浏览器显示的内容。
HTTP/1.1 200 OK
Bdpagetype: 2
Bdqid: 0xbf90656d00086de4
Cache-Control: private
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html;charset=utf-8
Date: Fri, 29 Oct 2021 19:34:43 GMT
Expires: Fri, 29 Oct 2021 19:34:43 GMT
Server: BWS/1.1
Set-Cookie: BDSVRTM=314; path=/
Set-Cookie: BD_HOME=1; path=/
Set-Cookie: H_PS_PSSID=34883_34444_34919_34067_31660_34861_34584_34505_34916_34812_26350_34971_34867; path=/; domain=.baidu.com
Strict-Transport-Security: max-age=172800
Traceid: 1635536083037869799413803644376716963300
X-Frame-Options: sameorigin
X-Ua-Compatible: IE=Edge,chrome=1
Transfer-Encoding: chunked
#空一格 下面是<body>
实现简单的http协议(python充当服务器):
import socket
def send_data(new_socket):#为这个客户端返回数据
#1 接受浏览器发送过来的请求 即http请求
# GET / HTTP/1.1
#.....
request = new_socket.recv(1024)
print(request)
#2 返回http格式的数据 给浏览器
#2.1 准备发送给浏览器的数据---header
response = "HTTP/1.1 200 OK\r\n"
response += '\r\n'#空行加body
response += '<b1>hehehe</b1>'#2.2 发送给浏览器的数据body
new_socket.send(response.encode('utf-8'))
new_socket.close()
def main():
#1 创建套接字
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#设置套接字 让套接字可以重复利用这个资源
tcp_server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
#2 绑定
tcp_server_socket.bind(('',7878))
#3 默认套接字主动改为被动
tcp_server_socket.listen(128)
while True:
#4 等待客户端连接
new_socket,client_add = tcp_server_socket.accept()
#5 为这个客户端服务
send_data(new_socket)
#6 关闭监听套接字
tcp_server_socket.close()
if __name__ == "__main__":
main()
返回浏览器需要的页面http服务器:
import socket
import re
def send_data(new_socket):#为这个客户端返回数据
#1 接受浏览器发送过来的请求 即http请求
# GET / HTTP/1.1
#.....
request = new_socket.recv(1024).decode('utf-8')
print(request)
request_line = request.splitlines()
print(request_line)#返回的是一个列表
ret = re.match(r'[^/]+(/[^ ]*)',request_line[0])#GET /index.html HTTP.1.1
if ret:
file_name = ret.group(1)#提取客户端想要的html文件名
if file_name == '/':
file_name = '/index.html'#如果没有输入页面 就默认index.html当作页面
#2 返回http格式的数据 给浏览器
try:
#打开想要的文件
f = open(file_name,'rb')#二进制的形式直接读取 后面就不用编码了
except:#找不到文件就返回NOT FOUND
response = 'HTTP/1.1 404 NOT FOUND\r\n'
response += '\r\n'
response += '-----not found---'
new_socket.send(response.encode('utf-8'))
else:#如果能打开文件就读取并发送
html_contend= f.read()
f.close()
# 2.1 准备发送给浏览器的数据---header
response = "HTTP/1.1 200 OK\r\n"
response += '\r\n' # 空行加body
# response += '<b1>hehehe</b1>'#2.2 发送给浏览器的数据body
new_socket.send(response.encode('utf-8'))
new_socket.send(html_contend) # 不能和response一起发过去 因为是二进制的
new_socket.close()
def main():
#1 创建套接字
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#设置套接字 让套接字可以重复利用这个资源
tcp_server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
#2 绑定
tcp_server_socket.bind(('',7878))
#3 默认套接字主动改为被动
tcp_server_socket.listen(128)
while True:
#4 等待客户端连接
new_socket,client_add = tcp_server_socket.accept()
#多进程
#不用多进程的时候 需要等待服务完之后才能服务下一个
#可以在接受连接的时候开一个进程 让进程里面的线程服务
#accept还是一个 分配任务的时候多个人去做
p = multiprocessing.Process(target=send_data,args=(new_socket,))#5 为这个客户端服务
p.start()
new_socket.close()#需要再次关闭 因为子进程会复制父进程的资源 在创建进程之前的父进程的所有变量都会被复制一份
#所以子进程中也有new_socket 但是都标记的一个客户端
##5 为这个客户端服务
#send_data(new_socket)
#6 关闭监听套接字
tcp_server_socket.close()
if __name__ == "__main__":
main()
以上案例 如果使用多线程 因为是共享变量 所以不需要再次close。
协程实现:
import gevent
from gevent import monkey
monkey.patch_all()
def send_data(new_socket):
......
def main():
...
gevent.spawn(send_data,new_socket)
...
单进程 单线程 非堵塞实现并发:
import socket
def main():
tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_socket.bind(('',7890))
tcp_socket.listen(128)
tcp_socket.setblocking(False)#设置套接字为非堵塞的方式
client_socket_list = list()
while True:#循环监听有没有客户端到来 和循环接受客户端有没有发送数据过来
try:
new_socket,new_add = tcp_socket.accept()
except Exception as ret:
print('没有新的客户端到来')
else:
print('没有产生异常 意味着 来了一个新的客户端')
new_socket.setblocking(False)#设置新的套接字为非堵塞的方式
client_socket_list.append(new_socket)#将得到的客户端添加到客户端列表
for client_socket in client_socket_list:
try:
recv_data = client_socket.recv(1024)
except Exception as ret:
print('这个客户端没有发送来数据')
else:
if recv_data:
#对方发送过来数据
print('客户端发送的数据')
else:#对方调用了close 导致recv返回
client_socket_list.remove(client_socket)
client_socket.close()
if __name__ == '__main__':
main()
长连接和短连接
短连接的操作步骤是:
建立连接--数据传输--关闭连接--建立连接--数据传输--关闭连接(就相当于每次请求数据前都要建立连接)
长连接操作步骤是:
建立连接--数据传输--(保持连接)--数据传输(可以多次数据传输)--关闭连接
单线程 进程 非堵塞 长连接的http服务器:长连接:浏览器不断开的话 就会一直用一个套接字发送请求 可以减少服务器资源。
import socket
import re
def send_data(new_socket,request): # 为这个客户端返回数据
# 1 接受浏览器发送过来的请求 即http请求
# GET / HTTP/1.1
# .....
# request = new_socket.recv(1024).decode('utf-8')
# print(request)
request_line = request.splitlines()
print(request_line) # 返回的是一个列表
ret = re.match(r'[^/]+(/[^ ]*)', request_line[0]) # GET /index.html HTTP.1.1
if ret:
file_name = ret.group(1) # 提取客户端想要的html文件名
if file_name == '/':
file_name = '/index.html' # 如果没有输入页面 就默认index.html当作页面
# 2 返回http格式的数据 给浏览器
try:
# 打开想要的文件
f = open(file_name, 'rb') # 二进制的形式直接读取 后面就不用编码了
except: # 找不到文件就返回NOT FOUND
response = 'HTTP/1.1 404 NOT FOUND\r\n'
response += '\r\n'
response += '-----not found---'
new_socket.send(response.encode('utf-8'))
else: # 如果能打开文件就读取并发送
html_contend = f.read()
f.close()
# 2.1 准备发送给浏览器的数据---header
response_body = html_contend
response_header = "HTTP/1.1 200 OK\r\n"
response_header += 'Content-Length:%d\r\n' % len(response_body)#告诉浏览器body有多长
response_header += '\r\n' # 空行加body
response = response_header.encode('utf-8') + response_body #response_header是字符串 要变成二进制的才能和body相加
# response += '<b1>hehehe</b1>'#2.2 发送给浏览器的数据body
new_socket.send(response)
#new_socket.close() 如果在这里close的话 还是短连接
def main():
# 1 创建套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置套接字 让套接字可以重复利用这个资源
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 2 绑定
tcp_server_socket.bind(('', 7878))
# 3 默认套接字主动改为被动
tcp_server_socket.listen(128)
tcp_server_socket.setblocking(False)#套接字变为非堵塞
client_lists = list()
while True:
# 4 等待客户端连接
try:
new_socket, client_add = tcp_server_socket.accept()
except Exception as ret:
pass
else:
new_socket.setblocking(False)#新的套接字变为非堵塞
client_lists.append(new_socket)
for client_list in client_lists:
try:#套接字为非堵塞 没有数据到来 会有异常
recv_data = client_list.recv(1024).decode('utf-8')
except Exception as ret:
pass
else:#有数据到来
if recv_data:
send_data(client_list,recv_data)#因为之前已经收过数据了 所以函数里不需要再次收取
else:
client_list.close()
client_lists.remove(client_list)
# 6 关闭监听套接字
tcp_server_socket.close()
if __name__ == "__main__":
main()
epoll实现服务器(Linux服务器里使用的方式):单进程 单线程 实现高并发
epoll就是linux里有一个特殊的内存,这个内存是kernel(内核)和操作系统共享的,在这个内存里面所有添加的 要监听的 要判断数据是否到来的套接字对应的文件描述符,我在检测的时候不是轮询(我们之前的用的非阻塞的方式)而是事件通知 我什么时候收到数据了 看一下这个内存中对应数据是谁的 这样就不用遍历套接字。