UDP接收GPS NMEA报文异常:VTG/GGA丢包而ZDA正常求助
UDP接收GPS NMEA报文丢包问题排查
问题描述
编写Python程序通过UDP接收GPS NMEA语句,GPS设备每秒发送ZDA、VTG、GGA序列报文,但代码仅能稳定接收ZDA,VTG和GGA频繁丢失。输出示例:
['$GPZDA,204622.00,16,10,2023,,*63', ''] ['$GPZDA,204623.00,16,10,2023,,*62', ''] ['$GPZDA,204624.00,16,10,2023,,*65', ''] ['$GPZDA,204625.00,16,10,2023,,*64', ''] ['$GPZDA,204626.00,16,10,2023,,*67', ''] ['$GPZDA,204627.00,16,10,2023,,*66', ''] ['$GPZDA,204628.00,16,10,2023,,*69', ''] ['$GPZDA,204629.00,16,10,2023,,*68', ''] ['$GPZDA,204630.00,16,10,2023,,*60', ''] ['$GPZDA,204631.00,16,10,2023,,*61', ''] ['$GPZDA,204632.00,16,10,2023,,*62', ''] ['$GPGGA,204632.00,2918.75039,N,09435.22487,W,5,26,0.6,33.64,M,-27.60,M,010,0481*53', ''] ['$GPZDA,204633.00,16,10,2023,,*63', ''] ['$GPZDA,204634.00,16,10,2023,,*64', ''] ['$GPZDA,204635.00,16,10,2023,,*65', ''] ['$GPZDA,204636.00,16,10,2023,,*66', ''] ['$GPZDA,204637.00,16,10,2023,,*67', ''] ['$GPZDA,204638.00,16,10,2023,,*68', ''] ['$GPZDA,204639.00,16,10,2023,,*69', ''] ['$GPZDA,204640.00,16,10,2023,,*67', ''] ['$GPZDA,204641.00,16,10,2023,,*66', ''] ['$GPZDA,204642.00,16,10,2023,,*65', ''] ['$GNVTG,286.931,T,286.931,M,0.198,N,0.367,K,D*3A', ''] ['$GPZDA,204643.00,16,10,2023,,*64', '']
现有代码
UDP_socket类
class UDP_socket(object): ''' mode = "utf8" or "binary" ''' def __init__(self, host='', port=0, broadcast=True, mode='utf8'): self.host = host self.port = port self.broadcast = broadcast self.mode = mode def read(self): with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: if self.broadcast: s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) s.bind((self.host, self.port)) if self.mode == 'binary': data = s.recv(1024).hex() else: data = s.recv(1024).decode('Utf8', 'replace').split('\r\n') print(data) return data def send(self, input, target): with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: if self.broadcast: s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) s.bind((self.host, self.port)) s.sendto(input.encode('Utf8'), target)
GPS类相关代码
def connect_socket(self): '''Create a socket UDP or TCP''' self.continuous_read = True self.button.configure(text="Disconnect", command=self.socket_stop) if self.tcpudp.get(): #0 for TCP self.socket = UDP_socket(self.gps_address.get(), int(self.gps_port.get()), True) else: self.socket = TCP_socket(self.gps_address.get(), int(self.gps_port.get()) ) self.gps_marker = Map_Marker(app.map, color = self.marker_color) readT = threading.Thread(target= lambda: self.socket_read(FALSE)) readT.start() def socket_read(self, terminal): telegram = nmea_oi() while self.continuous_read: received_msg = self.socket.read() del received_msg[-1] for j in range(len(received_msg)): if telegram.decode(received_msg[j], 'All', terminal)[0] == 'GGA': lat = telegram.decode(received_msg[j], 'All', terminal)[1][0] long = telegram.decode(received_msg[j], 'All', terminal)[1][1] self.gps_marker.update_marker(lat, long) self.update_displayed_lines(received_msg[j])
已知同一电脑其他软件可正常接收所有报文,已尝试增大缓冲区到2048,怀疑线程或时序问题。
问题根源与解决方案
1. 核心问题:每次read创建新Socket
UDP_socket.read()方法中,每次调用都会创建新UDP套接字并绑定端口,这会导致:
- 旧Socket在
with块结束后自动关闭,新Socket绑定前的间隙会丢失报文 - 频繁创建/销毁Socket引入延迟,无法及时接收连续报文
修复方案:在__init__中初始化并复用同一个Socket:
import socket class UDP_socket(object): ''' mode = "utf8" or "binary" ''' def __init__(self, host='', port=0, broadcast=True, mode='utf8'): self.host = host self.port = port self.broadcast = broadcast self.mode = mode # 初始化Socket并绑定 self.s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) if self.broadcast: self.s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) self.s.bind((self.host, self.port)) def read(self): if self.mode == 'binary': data = self.s.recv(2048).hex() else: # 用2048缓冲区确保接收完整多报文数据 data = self.s.recv(2048).decode('Utf8', 'replace').split('\r\n') print(data) return data def send(self, input, target): self.s.sendto(input.encode('Utf8'), target) def close(self): # 添加关闭方法清理Socket self.s.close()
2. 数据处理优化:避免误删有效数据
del received_msg[-1]会固定删除最后一条数据,若最后一条是有效报文则会丢失。改为过滤空字符串:
def socket_read(self, terminal): telegram = nmea_oi() while self.continuous_read: received_msg = self.socket.read() # 只过滤空字符串元素 received_msg = [msg for msg in received_msg if msg.strip()] for msg in received_msg: decode_result = telegram.decode(msg, 'All', terminal) if decode_result[0] == 'GGA': lat = decode_result[1][0] long = decode_result[1][1] self.gps_marker.update_marker(lat, long) self.update_displayed_lines(msg)
3. 线程与资源清理
- 修正线程启动时的关键字错误:
FALSE改为False - 添加断开连接时的Socket关闭逻辑:
def socket_stop(self): self.continuous_read = False if hasattr(self, 'socket'): self.socket.close() self.button.configure(text="Connect", command=self.connect_socket)
4. 额外优化
- 移除
read方法中不必要的print,减少IO延迟 - 保持
recv缓冲区为2048或更大,确保单次接收完整多报文数据
内容的提问来源于stack exchange,提问作者Kaj Ervo
相关产品推荐
相关产品推荐

