一、背景
最近在处理公司的一设备,内置的DTU通过UDP向服务器发送16进制的数据报文,由于第一次接触此类数据解析方式,在这里做总结与反省,避免大家走弯路
二、总结内容
2.1 UDP通信服务端创建方式
步骤
- 创建UDP的socket通信方式。
- 绑定具体的端口。
- 设置端口复用等待(这一步可以省略)
- 获取数据。
- 向客户端发送数据。
- 解析储存数据。
- 关闭UDP的socket链接。
下面分别讲解并总结,通过MVP的方式完成以上所有步骤,不做过多延展:
1. 创建UDP的socket通信方式。
import socket
Server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
在任何类型的通信开始之前,网络应用程序都必须创建套接字。
套接字最初是为同一主机上的应用程序所创建,使得主机上运行的一个程序(又名一个进程)与另一个运行的程序进行通信。这就是所谓的进程间通信(Inter Process Communication,IPC)
嵌套字有两种类型:
- 基于文件的:AF_UNIX
- 面向网络的:AF_INET(面向IPv4)、AF_INET6(面向IPv6)
DUP采用的是无连接的套接字
- 特点:不可靠(局网内还是比较可靠的),开销小。为了创建UDP套接字,必须使用SOCK_DGRAM作为套接字类型。UDP套接字的SOCK_DGRAM名字来自于单词“datagram”(数据报)。
2. 绑定具体的端口
Server.bind("", 8600)
bind 表示将创建好的Server绑定到具体的端口,注意:
当作为UDP的服务端时,前面的IP地址是可以省略的,后面是端口号有效的端口号范围为0~65535(小于1024的端口号预留给了系统)
**3.设置端口复用等待(这一步可以省略) **
Server.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1)
如果端口被使用过,并且利用Socket.close()关闭了端口链接,但是端口还没有释放,可以用上述函数来进行等待端口的重调用。
4. 获取数据
Msg, ClientAddr = Server.recvfrom(1024)
在UDP中使用recvform返回的是客户端发送过来的**字节流(10进制、ASCII码、16进制等)**与客户端的IP地址,1024表示缓存数据的大小。
5. 向客户端发送数据
Server.sendto(Data)
一般UDP服务器在接收到数据后会向客户端发送心跳包确认,可以通过此函数发送数据。
6. 解析数据
Data = binascii.b2a_hex(Msg)
此函数是将传送过来的字节流报文数据解析成16进制数据。相关用法见字节流转换成ASCII码
7. 关闭UDP的socket链接
Server.close()
没什么好讲的,一定要有这个就行,不然下一次没法继续用这个端口。
2.2 16进制数据解析
1. 报文时间解析
Time = datetime.datetime.strptime(Data[26:38], "%y%m%d%H%M%S") # 解析时间
由于时间是十进制数据,这里不用做16进制转换,直接通过datetime的strptime方法进行数据转换。
2. 16进制转换成10进制,再转换成ASCII码
chr(int(Data[224 + (i - 2):224 + i],16))
int(num, 16) 将16进制转换成10进制
chr(十进制)解析出对应的ASCII码
2.3 文件创建与数据储存分析
不得不说在储存数据文件的时候遇到了很多的坑,现在总结:
坑1:如何自动创建 年->月->日的文件夹结构?
在自己创建文件夹的时候有想到过使用
makedirs()
函数链式创建文件夹,但是在判断文件夹是否存在的时候一直报错,一直以为是在makedirs()使用不当造成的,有想过联合使用chdir
函数与mkdir()
函数来进行逐级的文件夹创建,最后效果也不是特别好。后来仔细查了好久的资料发现:
if