黏包现象和HTTP协议
作者:互联网
黏包现象和HTTP协议
黏包现象
什么是黏包
- 当发送网络数据时,tcp协议会根据Nagle算法将时间间隔短,数据量小的多个数据包打包成一个数据包,先发送到自己操作系统的缓存中,然后操作系统将数据包发送到目标程序所对应操作系统的缓存中,最后将目标程序从缓存中取出,而第一个数据包的长度,应用程序并不知道,所以会直接取出数据或者取出部分数据,留部分数据在缓存中,取出的数据可能第一个数据包和第二个数据包粘到一起
- 下面通过代码来说明黏包现象
- 通过结果我们可以看到,客户端其实是发了两条信息,而服务端将这两条信息合并为了一条信息,这就是黏包现象,因为应用程序并不知道第一个数据的长度,是从缓存中直接抽取一部分数据,所以可能会出现这种,两次的信息黏在一起的现象。
解决方案
- 接下来讨论上述黏包现象的解决方案,在这之前先引入一个模块,叫做struct模块,struct模块中最重要的两个函数为pack和unpack
- pack函数:作用是将数据以一定的方式打包成二进制格式。
- unpack函数:作用是从写好的二进制文件中读出文件。
- 引入struct模块以后,我们解决黏包的思路就有了,因为黏包现象是因为不知道第一个数据的长度引起的,故而我们可以在发送真正的数据之前,先发送第一个数据的长度给服务端,而pack函数固定将数据打包成4个长度的二进制文件使得我们可以将第一次接收的长度设为固定的4,从而很好的阻止接收长度时出现黏包现象,有了长度之后不但可以解决黏包现象,还可以解决由于数据过大,超过接收的最大值导致数据一次不能接收完成的问题。
- 具体的代码如下:
- 客户端
import socket
import struct
def main():
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 7890))
# 1.先接收报头
header = client.recv(4)
# 2.从报头中解析出数据长度
data_size = struct.unpack('i', header)[0]
# 3.接收真实的数据
recv_size = 0
total_data = b''
while recv_size < data_size:
data_recv = client.recv(1024)
total_data += data_recv
recv_size += len(data_recv)
print(total_data.decode('utf-8'))
if __name__ == '__main__':
main()
- 服务端:
import socket
import struct
def main():
# 创建套接字
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('127.0.0.1', 7890))
server.listen(128)
new_socket, addr = server.accept()
content = """
Windows IP 配置
"""
res = struct.pack('i', len(content))
new_socket.send(res)
new_socket.send(content.encode('utf-8'))
if __name__ == '__main__':
main()
HTTP协议
HTTP协议概念
- HTTP 协议的全称是(HyperText Transfer Protocol),翻译过来就是超文本传输协议。
超文本是超级文本的缩写,是指超越文本限制或者超链接,比如:图片、音乐、视频、超链接等等都属于超文本。HTTP 协议的制作者是蒂姆·伯纳斯-李,1991年设计出来的,HTTP 协议设计之前目的是传输网页数据的,现在允许传输任意类型的数据。传输 HTTP 协议格式的数据是基于 TCP 传输协议的,发送数据之前需要先建立连接。 - 作用:它规定了浏览器和 Web 服务器通信数据的格式,也就是说浏览器和web服务器通信需要使用http协议。
URL的概念
- URL的英文全拼是(Uniform Resoure Locator),表达的意思是统一资源定位符,通俗理解就是网络资源地址,也就是我们常说的网址。
- URL的组成
URL的样子:https://news.163.com/18/1122/10/E178J2O4000189FH.html - URL的组成部分:
- 协议部分: https://、http://、ftp://
- 域名部分: news.163.com
- 资源路径部分: /18/1122/10/E178J2O4000189FH.html
- 域名:
域名就是IP地址的别名,它是用点进行分割使用英文字母和数字组成的名字,使用域名目的就是方便的记住某台主机IP地址。 - URL的扩展:
https://news.163.com/hello.html?page=1&count=10 - 查询参数部分: ?page=1&count=10
- 参数说明:
- ? 后面的 page 表示第一个参数,后面的参数都使用 & 进行连接
- 参数说明:
查看HTTP通信过程
-
首先需要安装Google Chrome浏览器,然后Windows和Linux平台按F12调出开发者工具, mac OS选择 视图 -> 开发者 -> 开发者工具或者直接使用 alt+command+i 这个快捷键,还有一个多平台通用的操作就是在网页右击选择检查。
-
开发者工具的标签选项说明:
- 元素(Elements):用于查看或修改HTML标签
- 控制台(Console):执行js代码
- 源代码(Sources):查看静态资源文件,断点调试JS代码
- 网络(Network):查看http协议的通信过程
-
开发者工具的使用说明:
- 点击Network标签选项
- 在浏览器的地址栏输入百度的网址,就能看到请求百度首页的http的通信过程
- 这里的每项记录都是请求+响应的一次过程
-
主要信息
-
请求头信息
-
响应头信息
-
响应体信息
HTTP请求报文分析
- HTTP请求报文是浏览器发送给服务器的数据
- 最常见的请求报文分两种:
- get方式的请求报文
- post方式的请求报文
- 说明:
- get:获取服务器数据
- post:向服务器提交数据
- get请求报文说明:
GET / HTTP/1.1 # GET请求方式 请求资源路径 HTTP协议版本
---- 请求头 -----
Host: www.baidu.com # 服务器的主机地址和端口号,默认是80
Connection: keep-alive # 和服务端保持长连接
Upgrade-Insecure-Requests: 1 # 让浏览器升级不安全请求,使用https请求
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 # 用户代理,也就是客户端的名称
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 # 可接受的数据类型
Accept-Encoding: gzip, deflate # 可接受的压缩格式
Accept-Language: zh-CN,zh;q=0.9 #可接受的语言
Cookie: pgv_pvi=1246921728; # 登录用户的身份标识
---- 空行 ----
- get请求原始报文说明:
Host: www.baidu.com\r\n
Connection: keep-alive\r\n
Upgrade-Insecure-Requests: 1\r\n
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\n
Accept-Encoding: gzip, deflate\r\n
Accept-Language: zh-CN,zh;q=0.9\r\n
Cookie: pgv_pvi=1246921728; \r\n
\r\n (请求头信息后面还有一个单独的’\r\n’不能省略)
- 概括来说,请求报文所有行后面加上\r\n就是原始请求报文
- post 请求报文说明:
POST /xmweb?host=mail.baidu.cn&_t=1542884567319 HTTP/1.1 # POST请求方式 请求资源路径 HTTP协议版本
---- 请求头 ----
Host: mail.itcast.cn # 服务器的主机地址和端口号,默认是80
Connection: keep-alive # 和服务端保持长连接
Content-Type: application/x-www-form-urlencoded # 告诉服务端请求的数据类型
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 # 客户端的名称
---- 空行 ----
---- 请求体 ----
username=hello&pass=hello # 请求参数
- 对比总结
- 一个HTTP请求报文可以由请求行、请求头、空行和请求体4个部分组成。
- 请求行是由三部分组成:
- 请求方式
- 请求资源路径
- HTTP协议版本
- GET方式的请求报文没有请求体,只有请求行、请求头、空行组成。
- POST方式的请求报文可以有请求行、请求头、空行、请求体四部分组成,注意:POST方式可以允许没有请求体,但是这种格式很少见。
HTTP响应报文分析
- 响应报文即是服务器向浏览器发送的数据
- 响应报文说明:
HTTP/1.1 200 OK # HTTP协议版本 状态码 状态描述
--- 响应头 ---
Server: Tengine # 服务器名称
Content-Type: text/html; charset=UTF-8 # 内容类型
Transfer-Encoding: chunked # 发送给客户端内容不确定内容长度,发送结束的标记是0\r\n, Content-Length表示服务端确定发送给客户端的内容大小,但是二者只能用其一。
Connection: keep-alive # 和客户端保持长连接
Date: Fri, 23 Nov 2018 02:01:05 GMT # 服务端的响应时间
--- 空行 ---
--- 响应体 ---
<!DOCTYPE html><html lang=“en”> …</html> # 响应给客户端的数据
- HTTP状态码介绍
状态码 | 说明 |
---|---|
200 | 请求成功 |
307 | 重定向 |
400 | 错误的请求,请求地址或者参数有误 |
404 | 请求资源在服务器不存在 |
500 | 服务器内部源代码出现错误 |
静态web服务器
什么是静态web服务器
- 可以为发出请求的浏览器提供静态文档的程序。平时我们浏览百度新闻数据的时候,每天的新闻数据都会发生变化,那访问的这个页面就是动态的,而我们开发的是静态的,页面的数据不会发生变化。
搭建python自带的静态web服务器
- 搭建Python自带的静态Web服务器使用 python -m http.server 端口号
- 通过浏览器访问搭建的静态服务器
- 它显示的目录,即为我们执行命令的当前文件夹下的文件目录
- 我们也可以将目录设定在python的文件夹下,使用python创建html文件,然后通过静态服务器来访问
搭建自己的静态web服务器
- HTTP协议是基于TCP协议的,所以第一步要搭建一个TCP协议的服务器,然后接受请求报文并返回响应报文给浏览器,接下来是实现的具体过程。
import socket
def main():
tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_server.bind(('192.168.0.104',8080))
tcp_server.listen(128)
new_socket,addr = tcp_server.accept()
recv_request = new_socket.recv(1024).decode('utf-8')
with open('index.html','r') as f:
response_body = f.read()
response_line = 'HTTP/1.1 200 OK\r\n'
response_header = "Server: PWS/1.1\r\n"
response_data = response_line + response_header + '\r\n' + str(response_body)
new_socket.send(response_data.encode('utf-8'))
运行程序,并通过浏览器对自定义的服务器进行访问,可以得到如下结果:
- 上述程序是自定义一个静态web服务器,返回一个固定页面,我们可以对代码进行一些优化,让其返回我们指定的页面,而当访问的页面不存在时,返回404,优化代码如下:
import socket
def main():
tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_server.bind(('192.168.0.104',8080))
tcp_server.listen(128)
while True:
new_socket, addr = tcp_server.accept()
recv_request = new_socket.recv(4096).decode('utf-8')
print('recv_request:',recv_request)
recv_request1 = recv_request.split(" ")
print('split:',recv_request1)
request_path = recv_request1[1]
print('path:',request_path)
try:
with open('static'+request_path,'r',encoding='utf-8') as f:
response_body = f.read()
except Exception as e:
print(e)
response_line = 'HTTP/1.1 404 Not Found\r\n'
response_header = "Server: PWS/1.1\r\n"
with open('static/404.html','r',encoding='utf-8') as b:
response_body = b.read()
response_data = response_line + response_header + '\r\n' + response_body
new_socket.send(response_data.encode('utf-8'))
else:
response_line = 'HTTP/1.1 200 OK\r\n'
response_header = "Server: PWS/1.1\r\n"
response_data = response_line + response_header + '\r\n' + str(response_body)
new_socket.send(response_data.encode('utf-8'))
finally:
new_socket.close()
if __name__ == '__main__':
main()
标签:协议,HTTP,socket,黏包,报文,服务器,response,请求 来源: https://blog.csdn.net/weixin_43973956/article/details/113680359