Minimizing ZeroMQ round trip latency - zeromq

My question is about minimizing the latency between a ZMQ client and server
I have the following modified Hello World ZMQ (JeroMQ 0.5.1)
import org.zeromq.SocketType;
import org.zeromq.ZContext;
import org.zeromq.ZMQ;
public class server {
public static void main(String[] args) {
try (ZContext context = new ZContext()) {
// Socket to talk to clients
ZMQ.Socket socket = context.createSocket(SocketType.REP);
socket.bind("tcp://*:5555");
while (!Thread.currentThread().isInterrupted()) {
byte[] reply = socket.recv(0);
System.out.println(
"Received " + ": [" + reply.length+ "]"
);
String response = "world";
socket.send(response.getBytes(ZMQ.CHARSET), 0);
}
}
}
}
and client:
import org.zeromq.SocketType;
import org.zeromq.ZContext;
import org.zeromq.ZMQ;
public class client {
public static void main(String[] args)
{
try (ZContext context = new ZContext()) {
// Socket to talk to server
System.out.println("Connecting to hello world server" + args[0] + args[1] + args[2] );
ZMQ.Socket socket = context.createSocket(SocketType.REQ);
socket.connect("tcp://"+args[0]+":"+args[1]);
for (int requestNbr = 1; requestNbr != 10; requestNbr++) {
byte[] request = new byte[requestNbr*(Integer.parseInt(args[2]))];
System.out.println("Sending Hello " + requestNbr);
long time = System.nanoTime();
socket.send(request, 0);
byte[] reply = socket.recv(0);
double restime = (System.nanoTime() - time)/1000000.0;
System.out.println(
"Received " + new String(reply, ZMQ.CHARSET) + " " +
requestNbr + " " + restime
);
}
}
}
}
I'm running the server and the client over a network with latency (160ms round trip). I create the latency using tc on both the client and the server:
tc qdisc del dev eth0 root
tc class add dev eth0 parent 1: classid 1:155 htb rate 1000mbit
tc filter add dev eth0 parent 1: protocol ip prio 1 u32 flowid 1:155 match ip dst 192.168.181.1/24
tc qdisc add dev eth0 parent 1:155 handle 155: netem delay $t1 $dt1 distribution normal
Now when I run java -jar client.jar 192.168.181.3 5555 100000 I get the following output:
Sending Hello 1
Received world 1 1103.392783
Sending Hello 2
Received world 2 322.553512
Sending Hello 3
Received world 3 478.10143
Sending Hello 4
Received world 4 606.396567
Sending Hello 5
Received world 5 641.465041
Sending Hello 6
Received world 6 772.961712
Sending Hello 7
Received world 7 910.848674
Sending Hello 8
Received world 8 966.694224
Sending Hello 9
Received world 9 940.645636
which means that as we increase the size of the message, it takes more round trips to send the message and receive the ack (you can play with the message size to see for yourself). I was wondering what I need to do to prevent that from happening, that is: send everything in one go and minimize the latency to the roundtrip time.
Note: In my original application, I'm using a REQ-ROUTER pattern as I have multiple clients, but the issue with the latency and large messages lingers on

Related

Basic WebSocket Server for Arduino Uno with W5500 Ethernet Shield

I am fairly new to Arduino and I am trying to construct a generic WebSocket server which send simple ASCII values to a WebSocket client running via a python program connected to the Arduino via an Ethernet cable. I don't want to construct a full WebServer, rather I just want to use WebSocket, in order to avoid using HTTP.
I am unsure of what library I should use for a W5500 shield with an Arduino Uno. Nor can I find any comprehensive and simple examples of how a basic WebSocket Server is constructed in Arduino. Can any one provide some recommendations for appropriate libraries or with good pieces of example code?
Thanks in advance
Edit: Here is a basic attempt at a generic WebSocket server I have taken from the WebSockets_Generic.h library, just to outline what I would like to make:
#include <Ethernet2.h>
#include <WebSockets_Generic.h>
WebSocketsServer webSocket = WebSocketsServer(81);
// Enter a MAC address and IP address for your controller below.
byte mac[] ={0xA8, 0x61, 0x0A, 0xAE, 0x69, 0x13};
// Select the IP address according to your local network
IPAddress ip(198, 162, 1, 177);
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length)
{
switch (type)
{
case WStype_DISCONNECTED:
//Serial.println( "[" + String(num) + "] Disconnected!");
break;
case WStype_CONNECTED:
{
Serial.println( "[" + String(num) + "] Connected!");
//IPAddress ip = webSocket.remoteIP(num);
//Serial.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
// send message to client
webSocket.sendTXT(num, "Connected");
}
break;
case WStype_TEXT:
Serial.println( "[" + String(num) + "] get Text: " + String((char *) payload));
// send message to client
webSocket.sendTXT(num, "message here");
// send data to all connected clients
webSocket.broadcastTXT("message here");
break;
case WStype_BIN:
Serial.println( "[" + String(num) + "] get binary length: " + String(length));
//hexdump(payload, length);
// send message to client
// webSocket.sendBIN(num, payload, length);
break;
default:
break;
}
}
void setup()
{
// Serial.begin(921600);
Serial.begin(115200);
while (!Serial);
// start the ethernet connection and the server:
// Use DHCP dynamic IP and random mac
uint16_t index = millis();
// Use Static IP
//Ethernet.begin(mac[index], clientIP);
Ethernet.begin(mac, ip);
// server address, port and URL
Serial.print("WebSockets Server # IP address: ");
Serial.println(Ethernet.localIP());
webSocket.begin();
webSocket.onEvent(webSocketEvent);
}
void loop()
{
webSocket.loop();
}
This does not compile and instead produces an error:
In file included from /home/user/Data/Code-pieces/Arduino-Flowmeter/test/test.ino:2:0:
/home/user/Arduino/libraries/WebSockets_Generic/src/:69:4: error: #error Version 2.x.x currently does not support Arduino with AVR since there is no support for std namespace of c++.
#error Version 2.x.x currently does not support Arduino with AVR since there is no support for std namespace of c++.
^~~~~
/home/user/Arduino/libraries/WebSockets_Generic/src/WebSockets_Generic.h:70:4: error: #error Use Version 1.x.x. (ATmega branch)
#error Use Version 1.x.x. (ATmega branch)
^~~~~
Multiple libraries were found for "Ethernet.h"
Used: /home/user/Data/Code-pieces/arduino-1.8.14/libraries/Ethernet
Not used: /home/user/Arduino/libraries/UIPEthernet
exit status 1
Error compiling for board Arduino Uno.
WebSockets_Generic.h
This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.

How to correctly receive data using ZeroMQ?

I have two machines in the same network :
The first machine binds to a socket on its own IP address (120.0.0.1) and receives any data coming to the socket .bind()-ed on port 5555:
zmq::context_t context{1};
zmq::socket_t socket{context, ZMQ_SUB};
socket.setsockopt(ZMQ_SUBSCRIBE, "lidar");
socket.bind("tcp://120.0.0.1:5555");
while(true)
{
zmq::message_t message;
auto recv = socket.recv(message);
ROS_INFO("Value: %d", recv.value());
}
The second machine, having an IP address 120.0.0.248, connects to the first machine and sends the messages to it:
sock.connect("tcp://120.0.0.1:5555");
while (1) {
double nodes[8192];
sock.send(zmq::buffer("lidar") , zmq::send_flags::sndmore);
sock.send(zmq::buffer(nodes, (((int)(count)) * 8)));
}
But for some reason, I cannot receive any messages on the first machine and it gets stuck on auto recv = socket.recv(message);.
What is a correct way for such communication?

Strange behavior of the ZeroMQ PUB/SUB pattern with TCP as transport layer

In order to design our API/messages, I've made some preliminary tests with our data:
Protobuf V3 Message:
message TcpGraphes {
uint32 flowId = 1;
repeated uint64 curTcpWinSizeUl = 2; // max 3600 elements
repeated uint64 curTcpWinSizeDl = 3; // max 3600 elements
repeated uint64 retransUl = 4; // max 3600 elements
repeated uint64 retransDl = 5; // max 3600 elements
repeated uint32 rtt = 6; // max 3600 elements
}
Message build as multipart message in order to add the filter functionality for the client
Tested with 10 python clients: 5 running on the same PC (localhost), 5 running on an external PC.
Protocol used was TCP. About 200 messages were sent every second.
Results:
Local client are working: they get every messages
Remote clients are missing some messages (throughput seems to be limited by the server to 1Mbit/s per client)
Server code (C++):
// zeroMQ init
zmq_ctx = zmq_ctx_new();
zmq_pub_sock = zmq_socket(zmq_ctx, ZMQ_PUB);
zmq_bind(zmq_pub_sock, "tcp://*:5559");
every second, about 200 messages are sent in a loop:
std::string serStrg;
tcpG.SerializeToString(&serStrg);
// first part identifier: [flowId]tcpAnalysis.TcpGraphes
std::stringstream id;
id << It->second->first << tcpG.GetTypeName();
zmq_send(zmq_pub_sock, id.str().c_str(), id.str().length(), ZMQ_SNDMORE);
zmq_send(zmq_pub_sock, serStrg.c_str(), serStrg.length(), 0);
Client code (python):
ctx = zmq.Context()
sub = ctx.socket(zmq.SUB)
sub.setsockopt(zmq.SUBSCRIBE, '')
sub.connect('tcp://x.x.x.x:5559')
print ("Waiting for data...")
while True:
message = sub.recv() # first part (filter part, eg:"134tcpAnalysis.TcpGraphes")
print ("Got some data:",message)
message = sub.recv() # second part (protobuf bin)
We have looked at the PCAP and the server don't use the full bandwidth available, I can add some new subscribers, remove some existing ones, every remote subscriber gets "only" 1Mbit/s.
I've tested an Iperf3 TCP connection between the two PCs and I reach 60Mbit/s.
The PC who runs the python clients has about 30% CPU last.
I've minimized the console where the clients are running in order to avoid the printout but it has no effect.
Is it a normal behavior for the TCP transport layer (PUB/SUB pattern) ? Does it means I should use the EPGM protocol ?
Config:
windows xp for the server
windows 7 for the python remote clients
zmq version 4.0.4 used
A performance motivated interest ?
Ok, let's first use the resources a bit more adequately :
// //////////////////////////////////////////////////////
// zeroMQ init
// //////////////////////////////////////////////////////
zmq_ctx = zmq_ctx_new();
int aRetCODE = zmq_ctx_set( zmq_ctx, ZMQ_IO_THREADS, 10 );
assert( 0 == aRetCODE );
zmq_pub_sock = zmq_socket( zmq_ctx, ZMQ_PUB );
aRetCODE = zmq_setsockopt( zmq_pub_sock, ZMQ_AFFINITY, 1023 );
// ^^^^
// ||||
// (:::::::::::)-------++++
// >>> print ( "[{0: >16b}]".format( 2**10 - 1 ) ).replace( " ", "." )
// [......1111111111]
// ||||||||||
// |||||||||+---- IO-thread 0
// ||||||||+----- IO-thread 1
// |......+------ IO-thread 2
// :: : :
// |+------------ IO-thread 8
// +------------- IO-thread 9
//
// API-defined AFFINITY-mapping
Non-windows platforms with a more recent API can touch also scheduler details and tweak O/S-side priorities even better.
Networking ?
Ok, let's first use the resources a bit more adequately :
aRetCODE = zmq_setsockopt( zmq_pub_sock, ZMQ_TOS, <_a_HIGH_PRIORITY_ToS#_> );
Converting the whole infrastructure into epgm:// ?
Well, if one wishes to experiment and gets warranted resources for doing that E2E.

FTP Arduino issue with ESP8266

Trying to do FTP with my router from an ESP8266 WiFi-board and using the Arduino-IDE, I keep getting the following error message:
331 Password required for anonymous.
My code looks like this:
if (client.connect(server, 21)) { // 21 = FTP server
Serial.println(F("Command connected FIRST TIME"));
} else {
Serial.println(F("Command connection failed FIRST TIME"));
}
eRcv();
Serial.println("OUTPUT BUFFER 1");
Serial.println(outBuf);
client.println(F("USER anonymous"));
eRcv();
Serial.println("OUTPUT BUFFER 2");
Serial.println(outBuf);
client.println(F("PASS anonymous"));
eRcv();
Serial.println("OUTPUT BUFFER 3");
Serial.println(outBuf);
client.println(F("SYST"));
eRcv();
Serial.println("OUTPUT BUFFER 4");
Serial.println(outBuf);
client.println(F("Type I"));
eRcv();
My log looks like that:
WiFi connected; IP address: 192.168.178.33
Command connected FIRST TIME
OUTPUT BUFFER 1
220 FRITZ!Box7490 FTP server ready.
OUTPUT BUFFER 2
331 Password required for anonymous.
As you can see, the error message I receive (i.e. err 331) happens already at cmd nr 2 (i.e. "PASS anonymous2).
The router is set to accept an anonymous FTP (that should not be the problem). The router, of course, is set to allow FTP.
I read something about a "passive mode" (client.println(F("PASV"));) but it seems to me that the "PASS anonymous" should go through independent of PASV-mode ore not. Is this correct ?
Are there any other suggestions of what to do here ?
Much appreciated!
P.S. For completion, the FTP-receive (delivering the "outBuf" from the example-code above) looks like this:
//-------------- FTP receive
byte eRcv() {
byte respCode;
byte thisByte;
long StartTimeoutTime = millis();
while (!client.available() && (millis() - StartTimeoutTime < 1000))
{ // wait for answer with 1 second timeout
delay(1);
}
if (millis() - StartTimeoutTime >= 1000)
{
efail();
return 0;
}
respCode = client.peek();
outCount = 0;
while (client.available()) {
thisByte = client.read();
//Serial.write(thisByte);
if (outCount < 127) {
outBuf[outCount] = thisByte;
outCount++;
outBuf[outCount] = 0;
}
}
if (respCode >= '4') {
efail();
return 0;
}
return 1;
} // eRcv()
Anonymous authentication with FTP still requires that you send a username and a password. Traditionally the username is anonymous and an email address is used as a password. Something like user#test.com works fine. Here is a link to RFC 959, File Transfer Protocol.
From here it looks like you might not be waiting long enough for the server to send the 220 message before you send the USER. After you connect, wait for the server to finish sending its welcome message. Then send your USER, wait for the 331, then send your PASS. The server might also be sending multiple strings for the first message. Try logging into the FTP server with the commandline client for your o/s and see exactly what it's sending you, and adjust your code for that.

ZeroMQ's EPGM not working in weather PUB-SUB demo

I have compiled libzmq with openpgm with no changes under windows. Code here is taken from ZeroMQ Guide ("weather publisher" server/client). But if i change "tcp" to "epgm" it doesn't work any more (data is not received, but connection is established).
void test_serv()
{
// Prepare our context and publisher
void *context = zmq_ctx_new();
void *publisher = zmq_socket(context, ZMQ_PUB);
int rc = zmq_bind(publisher, "epgm://127.0.0.1:5556");
assert(rc == 0);
// Initialize random number generator
srandom((unsigned)time(NULL));
while (!stop_server)
{
// Get values that will fool the boss
int zipcode, temperature, relhumidity;
zipcode = randof(1000) + 600;
temperature = randof(215) - 80;
relhumidity = randof(50) + 10;
// Send message to all subscribers
char update[20];
sprintf(update, "%d %d %d", zipcode, temperature, relhumidity);
s_send(publisher, update);
}
LOG("END Server shutdown");
Sleep(500);
zmq_close(publisher);
zmq_ctx_destroy(context);
}
void test_sock()
{
// Socket to talk to server
LOG("Collecting updates from weather server...");
void *context = zmq_ctx_new();
void *subscriber = zmq_socket(context, ZMQ_SUB);
int rc = zmq_connect(subscriber, "epgm://127.0.0.1:5556");
assert(rc == 0);
// Subscribe to zipcode, default is NYC, 10001
char *filter = "1001 ";
rc = zmq_setsockopt(subscriber, ZMQ_SUBSCRIBE,
filter, strlen(filter));
assert(rc == 0);
// Process 100 updates
int update_nbr;
long total_temp = 0;
for (update_nbr = 0; update_nbr < 10; update_nbr++) {
char *string = s_recv(subscriber);
int zipcode, temperature, relhumidity;
sscanf(string, "%d %d %d",
&zipcode, &temperature, &relhumidity);
total_temp += temperature;
LOG(">> " << string);
free(string);
}
LOG("Average temperature for zipcode "<< filter << "was " << (int)(total_temp / update_nbr) << 'F');
zmq_close(subscriber);
zmq_ctx_destroy(context);
}
I run two functions in different threads, with tcp anything works as expected.
I have tried doing "route print 0.0.0.0" with cmd.exe and using interface IP (192.168.137.64) as prefix instead of "eth0" like shown in RFC: epgm://192.168.137.64;127.0.0.1:5556 on connect and/or bind, but this brokes my socket and raises error.
Also "PGM" requires administrator rights and i cannot test it now.
The error IS NOT "protocol not supported" errno is set to B (11) and i don't understand what does it mean (no docs on it).
EPGM is a bit finicky. According to this list post, if you're using EPGM your publisher and subscriber must be on separate hosts. More details here, it looks like this was a deliberate choice by the ZMQ team.
So, try it by spinning up your PUB and SUB on separate machines (changing the network addresses accordingly, of course).
The reason might be that windows does not support loopback capture interface. I tried weather example with protocol changed to epgm on linux and it works fine (well, shows some warnings about loopback, but the messages are transfered correctly)

Resources