一、TCP协议介绍
网上对TCP协议介绍众多,本人按照自己的理解简单介绍一下。
TCP(Transmission Control Protocol, 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输控制层通信协议。
1.1 协议机制
1.1.1 三次握手
(图片来自https://baijiahao.baidu.com/s?id=1790500638479044426&wfr=spider&for=pc,侵权删)
TCP三次握手的过程如下:
1.客户端发送SYN(seq=x)报文给服务器端,进入SYN_SENT状态。
2.服务器端收到SYN报文,回应一个SYN (seq=y)ACK(ack=x+1)报文,进入SYN_RECV状态。
3.客户端收到服务器端的SYN报文,回应一个ACK(ack=y+1)报文,进入Established状态。
附:TCP三次握手,抓包报文,如下:
1.1.2 四次挥手
(图片来自:https://cloud.tencent.com/developer/article/2094245)
第一次挥手: 主动关闭的一方(客户端或服务器)发送一个TCP段,其中包含一个FIN标记(Finish),表示它没有数据要发送给对方,但它仍能接收数据。
第二次挥手: 接收到FIN的一方发送一个ACK(Acknowledgement),确认收到FIN。
第三次挥手: 接收到FIN的一方发送一个FIN,表示它也没有数据要发送了,但它仍能接收数据。
第四次挥手: 主动关闭的一方发送一个ACK,确认收到FIN。
在经过2MSL(Maximum Segment Lifetime,最大报文存活时间)后,TCP连接正式结束
附:挥手抓包报文
注:挥手报文也可以只有三次哦,合并了2、3步骤,具体可以移步https://blog.csdn.net/m0_67698950/article/details/126960476一探究竟。
1.1.3 拥塞控制
推荐视频:https://www.bilibili.com/video/BV1L4411a7RN/?spm_id_from=333.999.0.0
(1)慢启动:在连接刚建立时,发送方将初始拥塞窗口设置为一个较小的值(通常是一个报文段的大小)。然后,每当发送方收到一个确认应答时,拥塞窗口就会加倍(指数增长)。这样,发送方会逐渐增加发送的数据量,从而实现慢慢地探测网络的容量。一但出现丢包,就将拥塞窗口减半,进入拥塞避免阶段。
(2)拥塞避免:一旦拥塞窗口达到一个阈值(通常是网络的带宽和延迟的函数),发送方将进入拥塞避免阶段。在拥塞避免阶段,发送方每收到一个确认应答时,拥塞窗口的增长将变慢(线性增长),谨慎地增加数据发送量,以防止过度拥塞网络。
(3)快速重传:发送方连续收到3个重复的确认应答时,它会推断某个报文段丢失,并立即重传该报文段,而不等待超时重传的时间。这样可以更快地恢复丢失的报文段,提高传输效率。
(4)快速恢复:当发送方接收到重复确认应答时,它会将拥塞窗口减半,并进入快速恢复阶段。在快速恢复阶段,发送方每收到一个确认应答时,拥塞窗口的增长将变得更慢,并且不再执行慢启动算法,而是执行拥塞避免算法。
1.2 协议报文
1、端口号: 用来标识同一台计算机的不同的应用进程。
1)源端口:源端口和IP地址的作用是标识报文的返回地址。
2)目的端口:端口指明接收方计算机上的应用程序接口。
TCP报头中的源端口号和目的端口号同IP数据报中的源IP与目的IP唯一确定一条TCP连接。
2、序号和确认号: 是TCP可靠传输的关键部分。序号是本报文段发送的数据组的第一个字节的序号。在TCP传送的流中,每一个字节一个序号。e.g.一个报文段的序号为300,此报文段数据部分共有100字节,则下一个报文段的序号为400。所以序号确保了TCP传输的有序性。确认号,即ACK,指明下一个期待收到的字节序号,表明该序号之前的所有数据已经正确无误的收到。确认号只有当ACK标志为1时才有效。比如建立连接时,SYN报文的ACK标志位为0。
3、数据偏移/首部长度: 4bits。由于首部可能含有可选项内容,因此TCP报头的长度是不确定的,报头不包含任何任选字段则长度为20字节,4位首部长度字段所能表示的最大值为1111,转化为10进制为15,15*32/8 = 60,故报头最大长度为60字节。首部长度也叫数据偏移,是因为首部长度实际上指示了数据区在报文段中的起始偏移值。
4、保留: 为将来定义新的用途保留,现在一般置0。
5、控制位: URG ACK PSH RST SYN FIN,共6个,每一个标志位表示一个控制功能。
1)URG:紧急指针标志,为1时表示紧急指针有效,为0则忽略紧急指针。
2)ACK:确认序号标志,为1时表示确认号有效,为0表示报文中不含确认信息,忽略确认号字段。
3)PSH:push标志,为1表示是带有push标志的数据,指示接收方在接收到该报文段以后,应尽快将这个报文段交给应用程序,而不是在缓冲区排队。
4)RST:重置连接标志,用于重置由于主机崩溃或其他原因而出现错误的连接。或者用于拒绝非法的报文段和拒绝连接请求。
5)SYN:同步序号,用于建立连接过程,在连接请求中,SYN=1和ACK=0表示该数据段没有使用捎带的确认域,而连接应答捎带一个确认,即SYN=1和ACK=1。
6)FIN:finish标志,用于释放连接,为1时表示发送方已经没有数据发送了,即关闭本方数据流。
6、窗口: 滑动窗口大小,用来告知发送端接受端的缓存大小,以此控制发送端发送数据的速率,从而达到流量控制。窗口大小时一个16bit字段,因而窗口大小最大为65535。
7、校验和: 奇偶校验,此校验和是对整个的 TCP 报文段,包括 TCP 头部和 TCP 数据,以 16 位字进行计算所得。由发送端计算和存储,并由接收端进行验证。
8、紧急指针: 只有当 URG 标志置 1 时紧急指针才有效。紧急指针是一个正的偏移量,和顺序号字段中的值相加表示紧急数据最后一个字节的序号。 TCP 的紧急方式是发送端向另一端发送紧急数据的一种方式。
9、选项和填充: 最常见的可选字段是最长报文大小,又称为MSS(Maximum Segment Size),每个连接方通常都在通信的第一个报文段(为建立连接而设置SYN标志为1的那个段)中指明这个选项,它表示本端所能接受的最大报文段的长度。选项长度不一定是32位的整数倍,所以要加填充位,即在这个字段中加入额外的零,以保证TCP头是32的整数倍。
10、数据部分: TCP 报文段中的数据部分是可选的。在一个连接建立和一个连接终止时,双方交换的报文段仅有 TCP 首部。如果一方没有数据要发送,也使用没有任何数据的首部来确认收到的数据。在处理超时的许多情况中,也会发送不带任何数据的报文段。
(参考:https://cloud.tencent.com/developer/article/2054909)
二、物联网中实战
不同物联网平台对协议中所承载的业务内容均有自己的规范,但原理基本一致,本文基于中国电信AIoT物联网平台进行实战。
2.1 在物联网平台创建产品设备
具体操作流程可见平台操作手册
重点关注:
- 创建产品时,通信协议选择TCP
- 创建产品时,“是否透传”,选择透传或者物模型,将对应不同的上下行报文格式。透传:平台对报文不进行解析,将报文透传给应用或终端;物模型:按照在平台定义的服务与属性,对上下行报文进行解析。
2.2 交互流程
2.3 报文格式
2.4 代码
2.4.1 代码总体框架
每一次进行业务交互,主要是对client.sendall(payload)
中payload
部分和client.recv(1024)
的内容进行编辑与解析。
import socket
# 创建一个socket对象
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 获取服务端主机与端口,可在设备管理->产品->产品概况中查看,如果是域名,可在本地ping一下获得IP
host = "180.109.255.252“
# 设置端口与服务器端一致
port = 8996
# 连接服务器
client.connect((host, port))
# 发送报文数据
# client.sendall(payload)
# 接收小于 1024 字节的数据
# client.recv(1024)
# 关闭socket连接
client.close()
2.4.1 TCP透传设备
2.4.1.1. 登录
1.登录报文
消息格式:0x01 +deviceId_length(2字节) +deviceId +password_length(2字节) +password +version_length(2字节) +version
注:字符串转十六进制可用在线工具:https://www.sojson.com/hexadecimal.html,也可以直接使用python中的bytes(data, "utf-8").hex()
方式。
deviceId:17081879tcpdeviceNo
# 可在物联网平台,设备管理->设备->设备详情中查看
password:l8SCsoL26wzNmXh7qZDlYDJWvKyyt1NCEXk2c_aoleM
version:1.0
登录请求编码为(16进制):
• 登录报文标识符:0x01
• deviceId_length:0x0013
• deviceId:0x31373038313837397463706465766963654E6F
• password_length:0x002B
• password:0x6C385343736F4C3236777A4E6D586837715A446C59444A57764B797974314E4345586B32635F616F6C654D
• version_length:0x0003
• version:0x312e30
登录请求的完整报文为:
01 0013 31373038313837397463706465766963654E6F 002B 6C385343736F4C3236777A4E6D586837715A446C59444A57764B797974314E4345586B32635F616F6C654D 0003 312E30
2. 登录代码全貌
import socket
import binascii
# 创建一个socket对象
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 获取服务端主机与端口,可在设备管理->产品->产品概况中查看,如果是域名,可在本地ping一下获得IP
host = "180.109.255.252"
# 设置端口与服务器端一致
port = 8996
# 连接服务器
client.connect((host, port))
payload="01001331373038313837397463706465766963654E6F002B6C385343736F4C3236777A4E6D586837715A446C59444A57764B797974314E4345586B32635F616F6C654D0003312E30"
# 发送报文数据
client.sendall(binascii.unhexlify(payload)) # 将消息编码后发送给服务器
# 接收小于 1024 字节的数据
msg = client.recv(1024)
print(binascii.hexlify(msg).decode("utf-8"))
# 关闭socket连接
client.close()
根据平台定义的登录响应格式,TCP模拟器收到“050000”的报文,则登录成功,在物联网平台可查看设备状态也应该为在线。
2.4.1.2 数据上报
1.上报报文
消息格式:0x02 +数据长度(2字节) +业务数据
业务数据:"hello"
上行数据编码为(16进制):
• 上行报文标识符:0x02
• payload_length:0x0005
• payload:0x68656c6c6f
上行数据报文完整报文为:02 0005 68656c6c6f
2. 上行代码全貌
根据业务规则,设备需先登录,才能进行数据上报。
import socket
import binascii
# 创建一个socket对象
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 获取服务端主机与端口,可在设备管理->产品->产品概况中查看,如果是域名,可在本地ping一下获得IP
host = "180.109.255.252"
# 设置端口与服务器端一致
port = 8996
# 连接服务器
client.connect((host, port))
payload="01001331373038313837397463706465766963654E6F002B6C385343736F4C3236777A4E6D586837715A446C59444A57764B797974314E4345586B32635F616F6C654D0003312E30"
# 发送报文数据
client.sendall(binascii.unhexlify(payload)) # 将消息编码后发送给服务器
# 接收小于 1024 字节的数据
msg = client.recv(1024)
print(binascii.hexlify(msg).decode("utf-8"))
payload2 = "02000568656c6c6f"
client.sendall(binascii.unhexlify(payload2))
# 关闭socket连接
client.close()
可在物联网平台中看到我们的上报的数据,此处对报文进行了base64编码。
经过登录和上行报文的实战,有没有发现TCP协议在物联网领域中,实质是将业务数据封装在了TCP报文的data部分。data部分按照业务规则进行”拼接“即可。
(原先上传的代码,发现只有VIP才能下载,故将代码中重要部分进行了提取,直接附在正文,如需代码可留言!)