我正在PC基站和十二个机器人之间建立实时无线链接,每个机器人都以60Hz的频率传输。
为了进行测试,我编写了一个Python脚本,该脚本通过WiFi网络发送UDP多播数据包。然后在另一个多播组上,我有两个ESP8266发送回复。一个以60Hz进行发送以模拟实际设置,另一个以10倍的速率发送垃圾邮件以模拟其他设备的拥塞。
首先,我只查看了数据包丢失的情况,这与预期的差不多。但是后来我开始考虑往返时间。我让我的Python脚本编写当前时间,并使“真实”节点回显它们。 (垃圾邮件消息将被忽略)
关闭垃圾邮件发送者节点后,我会往返10毫秒,而使用垃圾邮件发送者,它始于100毫秒,然后慢慢增长到一秒钟。
如何调试此问题?
我尝试使用Wireshark,但是由于它们是单独的UDP流,其中包含自定义数据,因此我无法找出任何明智的分析方法。
我认为数据包可能卡在队列中的某个地方,因此建议使用以下命令查看一下队列长度。当然,该队列不是空的,但是如果我关闭垃圾邮件发送者节点,则队列长度没有太大变化,而往返传输回落到10ms。
netstat -c --udp -an
Proto Recv-Q Send-Q Local Address Foreign Address State
udp 40256 0 0.0.0.0:8080 0.0.0.0:*
我认为也许我的Python代码太慢而无法每秒处理600条消息。在PyPy上运行绝对没有区别,因此我有点犹豫要用C编写整个内容。每个数据包几乎有2ms的时间,几乎什么都不做。
基站代码:
import socket
import struct
import sched, time
import threading
base_multicast = '239.255.0.34'
robot_multicast = '239.255.0.35'
port = 8080
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', port))
mreq = struct.pack("4sl", socket.inet_aton(robot_multicast), socket.INADDR_ANY)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
# schedule base station packets
send_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
send_sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
rate = 1/60
#sock.settimeout(rate)
s = sched.scheduler(time.time, time.sleep)
def send(now):
nxt = now+rate
s.enterabs(nxt, 1, send, argument=(nxt,))
tstr = struct.pack("d", time.time())
send_sock.sendto(tstr, (base_multicast, port))
print(".", end='')
s.enter(1, 1, send, argument=(time.time(),))
t = threading.Thread(target=s.run)
t.daemon = True
t.start()
start = time.time()
avg_dur = 0
avg_ping = 0
while True:
try:
data = sock.recv(10240)
except socket.timeout:
continue
now = time.time()
duration = now-start
start = now
avg_dur = 0.99*avg_dur + 0.01*duration
try:
(t,) = struct.unpack("d", data)
ping = now-t
avg_ping = 0.99*avg_ping + 0.01*ping
print(len(data), 1/avg_dur, avg_ping)
except struct.error: # spammer message
pass
机器人节点:(垃圾邮件发送者是相同的,除了它在循环中发送replyPacket
10次)
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
const char* ssid = "mywifi";
const char* password = "password";
IPAddress base_multicast(239, 255, 0, 34);
IPAddress robot_multicast(239, 255, 0, 35);
WiFiUDP Udp;
unsigned int port = 8080; // local port to listen on
char incomingPacket[255]; // buffer for incoming packets
char replyPacket[] = "Hi there! Got the message :-))))"; // a reply string to send back
int rate = 1000/60;
unsigned long mytime;
unsigned long count;
float avg_diff = 0;
void setup()
{
Serial.begin(115200);
Serial.println();
Serial.printf("Connecting to %s ", ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
}
Serial.println(" connected");
//Udp.begin(localUdpPort);
Udp.beginMulticast(WiFi.localIP(), base_multicast, port);
Serial.printf("Now listening at IP %s, UDP port %d\n", WiFi.localIP().toString().c_str(), port);
mytime = millis();
}
void loop()
{
int packetSize = Udp.parsePacket();
if (packetSize)
{
// receive incoming UDP packets
unsigned long diff = micros() - mytime;
avg_diff = 0.9*avg_diff + 0.1*diff;
mytime = micros();
int rcv_rate = 1000000/avg_diff;
Serial.println(rcv_rate);
int len = Udp.read(incomingPacket, 255);
delay(random(rate));
//Serial.printf("UDP packet contents: %s\n", incomingPacket);
Udp.beginPacketMulticast(robot_multicast, port, WiFi.localIP());
Udp.write(incomingPacket, len);
Udp.endPacket();
}
}