文章目錄
- 一、背景
- 二、總結(jié)內(nèi)容
- 2.1 UDP通信服務(wù)端創(chuàng)建方式
- **DUP采用的是無連接的套接字**
- 2.2 16進(jìn)制數(shù)據(jù)解析
- 2.3 文件創(chuàng)建與數(shù)據(jù)儲存分析
一、背景
最近在處理公司的一設(shè)備,內(nèi)置的DTU通過UDP向服務(wù)器發(fā)送16進(jìn)制的數(shù)據(jù)報文,由于第一次接觸此類數(shù)據(jù)解析方式,在這里做總結(jié)與反省,避免大家走彎路
二、總結(jié)內(nèi)容
2.1 UDP通信服務(wù)端創(chuàng)建方式
步驟
- 創(chuàng)建UDP的socket通信方式。
- 綁定具體的端口。
- 設(shè)置端口復(fù)用等待(這一步可以省略)
- 獲取數(shù)據(jù)。
- 向客戶端發(fā)送數(shù)據(jù)。
- 解析儲存數(shù)據(jù)。
- 關(guān)閉UDP的socket鏈接。
下面分別講解并總結(jié),通過MVP的方式完成以上所有步驟,不做過多延展:
1. 創(chuàng)建UDP的socket通信方式。
import
socket
Server
=
socket
.
socket
(
socket
.
AF_INET
,
socket
.
SOCK_DGRAM
)
在任何類型的通信開始之前,網(wǎng)絡(luò)應(yīng)用程序都必須創(chuàng)建套接字。
套接字最初是為同一主機(jī)上的應(yīng)用程序所創(chuàng)建,使得主機(jī)上運行的一個程序(又名一個進(jìn)程)與另一個運行的程序進(jìn)行通信。這就是所謂的進(jìn)程間通信(Inter Process Communication,IPC)
嵌套字有兩種類型:
- 基于文件的:AF_UNIX
- 面向網(wǎng)絡(luò)的:AF_INET(面向IPv4)、AF_INET6(面向IPv6)
DUP采用的是無連接的套接字
- 特點:不可靠(局網(wǎng)內(nèi)還是比較可靠的),開銷小。為了創(chuàng)建UDP套接字,必須使用SOCK_DGRAM作為套接字類型。UDP套接字的SOCK_DGRAM名字來自于單詞“datagram”(數(shù)據(jù)報)。
2. 綁定具體的端口
Server
.
bind
(
""
,
8600
)
bind 表示將創(chuàng)建好的Server綁定到具體的端口,注意:
當(dāng)作為UDP的服務(wù)端時,前面的IP地址是可以省略的,后面是端口號有效的端口號范圍為0~65535(小于1024的端口號預(yù)留給了系統(tǒng))
**3.設(shè)置端口復(fù)用等待(這一步可以省略) **
Server
.
setsockopt
(
socket
.
IPPROTO_IP
,
socket
.
SO_REUSEADDR
,
1
)
如果端口被使用過,并且利用Socket.close()關(guān)閉了端口鏈接,但是端口還沒有釋放,可以用上述函數(shù)來進(jìn)行等待端口的重調(diào)用。
4. 獲取數(shù)據(jù)
Msg
,
ClientAddr
=
Server
.
recvfrom
(
1024
)
在UDP中使用recvform返回的是客戶端發(fā)送過來的**字節(jié)流(10進(jìn)制、ASCII碼、16進(jìn)制等)**與客戶端的IP地址,1024表示緩存數(shù)據(jù)的大小。
5. 向客戶端發(fā)送數(shù)據(jù)
Server
.
sendto
(
Data
)
一般UDP服務(wù)器在接收到數(shù)據(jù)后會向客戶端發(fā)送心跳包確認(rèn),可以通過此函數(shù)發(fā)送數(shù)據(jù)。
6. 解析數(shù)據(jù)
Data
=
binascii
.
b2a_hex
(
Msg
)
此函數(shù)是將傳送過來的字節(jié)流報文數(shù)據(jù)解析成16進(jìn)制數(shù)據(jù)。相關(guān)用法見字節(jié)流轉(zhuǎn)換成ASCII碼
7. 關(guān)閉UDP的socket鏈接
Server
.
close
(
)
沒什么好講的,一定要有這個就行,不然下一次沒法繼續(xù)用這個端口。
2.2 16進(jìn)制數(shù)據(jù)解析
1. 報文時間解析
Time
=
datetime
.
datetime
.
strptime
(
Data
[
26
:
38
]
,
"%y%m%d%H%M%S"
)
# 解析時間
由于時間是十進(jìn)制數(shù)據(jù),這里不用做16進(jìn)制轉(zhuǎn)換,直接通過datetime的strptime方法進(jìn)行數(shù)據(jù)轉(zhuǎn)換。
2. 16進(jìn)制轉(zhuǎn)換成10進(jìn)制,再轉(zhuǎn)換成ASCII碼
chr
(
int
(
Data
[
224
+
(
i
-
2
)
:
224
+
i
]
,
16
)
)
int(num, 16) 將16進(jìn)制轉(zhuǎn)換成10進(jìn)制
chr(十進(jìn)制)解析出對應(yīng)的ASCII碼
2.3 文件創(chuàng)建與數(shù)據(jù)儲存分析
不得不說在儲存數(shù)據(jù)文件的時候遇到了很多的坑,現(xiàn)在總結(jié):
坑1:如何自動創(chuàng)建 年->月->日的文件夾結(jié)構(gòu)?
在自己創(chuàng)建文件夾的時候有想到過使用
makedirs()
函數(shù)鏈?zhǔn)絼?chuàng)建文件夾,但是在判斷文件夾是否存在的時候一直報錯,一直以為是在makedirs()使用不當(dāng)造成的,有想過聯(lián)合使用chdir
函數(shù)與mkdir()
函數(shù)來進(jìn)行逐級的文件夾創(chuàng)建,最后效果也不是特別好。后來仔細(xì)查了好久的資料發(fā)現(xiàn):
if
not
os
.
path
.
exists
(
NewPath
)
:
os
.
makedirs
(
NewPath
)
在默認(rèn)的IDLE中判斷文件夾是否存在時,一直是有問題會報錯的,折騰了好久,后來改為:
if
not
os
.
path
.
isdir
(
FileDir
)
:
os
.
makedirs
(
FileDir
)
才沒有報錯,這里做筆記好好提醒一下自己。
以上是我總結(jié)的所有錯誤,希望看到這篇帖子的你能夠避開這些坑。下面附上我完整的UDP報文接收以及解析的代碼:
#!/usr/local/bin/python3
# coding:utf-8
import
socket
import
binascii
import
datetime
import
os
import
csv
'''
作者:Zflyee
Mailto: zflyee@126.com
'''
Server
=
socket
.
socket
(
socket
.
AF_INET
,
socket
.
SOCK_DGRAM
)
# 創(chuàng)建UDP傳輸數(shù)據(jù)
# Server.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1) # 快速實現(xiàn)端口復(fù)用
Server
.
bind
(
(
""
,
8600
)
)
print
(
u
"已綁定本機(jī)端口:8600,正在監(jiān)聽數(shù)據(jù)..."
)
def
DataParsing
(
Data
)
:
Time
=
datetime
.
datetime
.
strptime
(
Data
[
26
:
38
]
,
"%y%m%d%H%M%S"
)
# 解析時間
Voltage
=
int
(
Data
[
44
:
46
]
,
16
)
/
10.0
# 電壓數(shù)據(jù)
Temp
=
int
(
Data
[
60
:
64
]
,
16
)
/
10.0
# 實時溫度數(shù)據(jù)
Temp_1h_max
=
int
(
Data
[
70
:
74
]
,
16
)
/
10.0
# 溫度小時最大值
Temp_1h_min
=
int
(
Data
[
80
:
84
]
,
16
)
/
10.0
# 溫度小時最小值
Hum
=
int
(
Data
[
90
:
94
]
,
16
)
/
10.0
# 濕度數(shù)據(jù)
Hum_1h_max
=
int
(
Data
[
100
:
104
]
,
16
)
/
10.0
# 濕度小時最大值
Hum_1h_min
=
int
(
Data
[
110
:
114
]
,
16
)
/
10.0
# 濕度小時最小值
Pa
=
int
(
Data
[
120
:
124
]
,
16
)
/
10.0
# 氣壓數(shù)據(jù)
Pa_1h_max
=
int
(
Data
[
130
:
134
]
,
16
)
/
10.0
# 氣壓小時最大值
Pa_1h_min
=
int
(
Data
[
150
:
154
]
,
16
)
/
10.0
# 氣壓小時最小值
WindSpd
=
int
(
Data
[
140
:
144
]
,
16
)
/
10.0
# 瞬時風(fēng)速值
WindSpd_10min_avg
=
int
(
Data
[
160
:
164
]
,
16
)
/
10.0
# 10分鐘風(fēng)速平均值
WindSpd_max
=
int
(
Data
[
170
:
174
]
,
16
)
/
10.0
# 當(dāng)前風(fēng)速最大值
WindSpd_min
=
int
(
Data
[
180
:
184
]
,
16
)
/
10.0
# 當(dāng)前風(fēng)速最小值
WindDir
=
int
(
Data
[
190
:
194
]
,
16
)
/
10.0
# 當(dāng)前風(fēng)向瞬時值
WindDir_10min_avg
=
int
(
Data
[
200
:
204
]
,
16
)
/
10.0
# 10分鐘風(fēng)向平均值
Rain_1day
=
int
(
Data
[
210
:
214
]
,
16
)
/
10.0
# 當(dāng)日降雨量
# 推算緯度數(shù)據(jù)長度
Lati_len
=
int
(
Data
[
220
:
224
]
,
16
)
# 轉(zhuǎn)換為10進(jìn)制
Lati
=
""
for
i
in
range
(
2
,
Lati_len
*
2
,
2
)
:
Lati
+=
(
chr
(
int
(
Data
[
224
+
(
i
-
2
)
:
224
+
i
]
,
16
)
)
)
# 推算維度長度與數(shù)據(jù)
Long_len
=
int
(
Data
[
250
:
252
]
,
16
)
Long
=
""
for
i
in
range
(
2
,
Long_len
*
2
,
2
)
:
Long
+=
(
chr
(
int
(
Data
[
254
+
(
i
-
2
)
:
254
+
i
]
,
16
)
)
)
# 組裝數(shù)據(jù)
DataList
=
[
Time
,
WindSpd
,
WindSpd_10min_avg
,
WindSpd_max
,
WindSpd_min
,
WindDir
,
WindDir_10min_avg
,
Temp
,
Temp_1h_max
,
Temp_1h_min
,
Hum
,
Hum_1h_max
,
Hum_1h_min
,
Pa
,
Pa_1h_max
,
Pa_1h_min
,
Rain_1day
,
Voltage
]
# 創(chuàng)建文件路徑
Path
=
"E:\\富奧通\\data\\SY_FWS600\\"
Text
=
os
.
path
.
join
(
Path
,
Time
.
strftime
(
"%Y"
)
,
Time
.
strftime
(
"%m"
)
)
+
"\\"
+
Time
.
strftime
(
"%d"
)
+
".csv"
FileDir
=
os
.
path
.
split
(
Text
)
[
0
]
if
not
os
.
path
.
isdir
(
FileDir
)
:
os
.
makedirs
(
FileDir
)
# 寫入數(shù)據(jù)
with
open
(
Text
,
"a+"
,
newline
=
""
)
as
f
:
Writer
=
csv
.
writer
(
f
)
Writer
.
writerow
(
DataList
)
# 儲存提示
print
(
u
"{}時刻數(shù)據(jù)已成功儲存。"
.
format
(
Time
)
)
while
True
:
try
:
Msg
,
ClientAddr
=
Server
.
recvfrom
(
1024
)
Data
=
binascii
.
b2a_hex
(
Msg
)
# 將數(shù)據(jù)轉(zhuǎn)換成16進(jìn)制數(shù)據(jù)
Data
=
str
(
Data
,
encoding
=
"utf-8"
)
print
(
u
"從{0}的{1}端口得到如下數(shù)據(jù):\n{2}"
.
format
(
ClientAddr
[
0
]
,
ClientAddr
[
1
]
,
Data
)
)
if
Data
[
0
:
2
]
==
"7b"
:
Content
=
"7B810010"
+
Data
[
8
:
30
]
+
"7B"
Response
=
binascii
.
a2b_hex
(
Content
)
Server
.
sendto
(
Response
,
ClientAddr
)
# 回復(fù)客戶端數(shù)據(jù)
print
(
u
"已回復(fù)客戶端"
)
elif
Data
[
0
:
2
]
==
'01'
:
DataParsing
(
Data
)
else
:
print
(
u
"%%%%%%%%%%%%%%%數(shù)據(jù)報錯,重新獲取%%%%%%%%%%%%%%%"
)
continue
except
:
print
(
u
"%%%%%%%%%%%%%%%數(shù)據(jù)報錯,重新獲取%%%%%%%%%%%%%%%"
)
continue
Server
.
close
(
)
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
