I've tried to program sending messages from one server to multiple clients.
I have to use C# on client side, C++ on server side.
I took example from http://zguide.zeromq.org/page:all#toc8 for server:
#define within(num) (int) ((float) num * rand () / (RAND_MAX + 1.0))
int main () {
// Prepare our context and publisher
zmq::context_t context (1);
zmq::socket_t publisher (context, ZMQ_PUB);
publisher.bind("tcp://*:5556");
//publisher.bind("ipc://weather.ipc");
// Initialize random number generator
srand ((unsigned) time (NULL));
while (1) {
int zipcode, temperature, relhumidity;
// Get values that will fool the boss
zipcode = within (100000);
temperature = within (215) - 80;
relhumidity = within (50) + 10;
// Send message to all subscribers
zmq::message_t message(20);
_snprintf ((char *) message.data(), 20 ,
"%05d %d %d", zipcode, temperature, relhumidity);
publisher.send(message);
}
return 0;
}
And for client:
namespace ZMQGuide
{
internal class Program
{
public static void Main(string[] args) {
Console.WriteLine("Collecting updates from weather server…");
// default zipcode is 10001
string zipcode = "10001 "; // the reason for having a space after 10001 is in case of the message would start with 100012 which we are not interested in
if (args.Length > 0)
zipcode = args[1] + " ";
using (var context = new Context(1))
{
using (Socket subscriber = context.Socket(SocketType.SUB))
{
subscriber.Subscribe(zipcode, Encoding.Unicode);
subscriber.Connect("tcp://localhost:5556");
const int updatesToCollect = 100;
int totalTemperature = 0;
for (int updateNumber = 0; updateNumber < updatesToCollect; updateNumber++)
{
string update = subscriber.Recv(Encoding.Unicode);
totalTemperature += Convert.ToInt32(update.Split()[1]);
}
Console.WriteLine("Average temperature for zipcode {0} was {1}F", zipcode, totalTemperature / updatesToCollect);
}
}
}
}
}
They don't communicate with each other.
On the client side (C++) I commented line with ipc interaction because on Windows client with ipc is failed.
C# - C#, C++ - C++ interactions works correctly in this case.
I use clrzmq 2.2.5.
I would appreciate any help.
C# client is using Encoding.Unicode, which is a 2-byte unicode representation (UTF-16). C++ server is using ASCII.
ZMQ subscription matching works on the byte level and does not convert between character encodings, so this is where my issue is. Switching to Encoding.ASCII or Encoding.UTF8 in C# client solves this issue.
Related
The transmitter I am using is an Arduino nano every, and I have it working just fine with an Arduino Uno as the receiver, however using the same nrf module with a raspberry pi does not work.
I am using a Pi Model 3B V1.2, with the nrf24l01+ and the 5v power adapter. The nrf also has a 100uf capacitor attached to the vcc/grnd.
I am using the GettingStarted example for the arduino nrf, with different pin configs for either the nano every or the uno, and the addresses changed to hardcoded hex:
/*
* See documentation at https://nRF24.github.io/RF24
* See License information at root directory of this library
* Author: Brendan Doherty (2bndy5)
*/
/**
* A simple example of sending data from 1 nRF24L01 transceiver to another.
*
* This example was written to be used on 2 devices acting as "nodes".
* Use the Serial Monitor to change each node's behavior.
*/
#include <SPI.h>
#include "printf.h"
#include "RF24.h"
// instantiate an object for the nRF24L01 transceiver
RF24 radio(7, 8); // using pin 7 for the CE pin, and pin 8 for the CSN pin, for the uno
//RF24 radio(10, A0); // this pin setup for the nano every
// Let these addresses be used for the pair
//uint8_t address[][6] = {"1Node", "2Node"};
uint8_t address[][5] = {{0xE0, 0xE0, 0xE0, 0xE0, 0xE0}, {0xF0, 0xF0, 0xF0, 0xF0, 0xF0}};
// It is very helpful to think of an address as a path instead of as
// an identifying device destination
// to use different addresses on a pair of radios, we need a variable to
// uniquely identify which address this radio will use to transmit
bool radioNumber = 0; // 0 uses address[0] to transmit, 1 uses address[1] to transmit
// Used to control whether this node is sending or receiving
bool role = false; // true = TX role, false = RX role
// For this example, we'll be using a payload containing
// a single float number that will be incremented
// on every successful transmission
float payload = 0.0;
void setup() {
Serial.begin(115200);
while (!Serial) {
// some boards need to wait to ensure access to serial over USB
}
// initialize the transceiver on the SPI bus
if (!radio.begin()) {
Serial.println(F("radio hardware is not responding!!"));
while (1) {} // hold in infinite loop
}
// print example's introductory prompt
Serial.println(F("RF24/examples/GettingStarted"));
// To set the radioNumber via the Serial monitor on startup
Serial.println(F("Which radio is this? Enter '0' or '1'. Defaults to '0'"));
while (!Serial.available()) {
// wait for user input
}
char input = Serial.parseInt();
radioNumber = input == 1;
Serial.print(F("radioNumber = "));
Serial.println((int)radioNumber);
// role variable is hardcoded to RX behavior, inform the user of this
Serial.println(F("*** PRESS 'T' to begin transmitting to the other node"));
// Set the PA Level low to try preventing power supply related problems
// because these examples are likely run with nodes in close proximity to
// each other.
radio.setPALevel(RF24_PA_LOW); // RF24_PA_MAX is default.
// save on transmission time by setting the radio to only transmit the
// number of bytes we need to transmit a float
radio.setPayloadSize(sizeof(payload)); // float datatype occupies 4 bytes
//radio.setAutoAck(false);
// set the TX address of the RX node into the TX pipe
radio.openWritingPipe(address[radioNumber]); // always uses pipe 0
// set the RX address of the TX node into a RX pipe
radio.openReadingPipe(1, address[!radioNumber]); // using pipe 1
//radio.setAutoAck(false);
// additional setup specific to the node's role
if (role) {
radio.stopListening(); // put radio in TX mode
} else {
radio.startListening(); // put radio in RX mode
}
// For debugging info
//These dont work for the nano every:
//printf_begin(); // needed only once for printing details
//radio.printDetails(); // (smaller) function that prints raw register values
//radio.printPrettyDetails(); // (larger) function that prints human readable data
} // setup
void loop() {
if (role) {
// This device is a TX node
unsigned long start_timer = micros(); // start the timer
bool report = radio.write(&payload, sizeof(float)); // transmit & save the report
unsigned long end_timer = micros(); // end the timer
if (report) {
Serial.print(F("Transmission successful! ")); // payload was delivered
Serial.print(F("Time to transmit = "));
Serial.print(end_timer - start_timer); // print the timer result
Serial.print(F(" us. Sent: "));
Serial.println(payload); // print payload sent
payload += 0.01; // increment float payload
} else {
Serial.println(F("Transmission failed or timed out")); // payload was not delivered
}
// to make this example readable in the serial monitor
delay(1000); // slow transmissions down by 1 second
} else {
// This device is a RX node
uint8_t pipe;
if (radio.available(&pipe)) { // is there a payload? get the pipe number that recieved it
uint8_t bytes = radio.getPayloadSize(); // get the size of the payload
radio.read(&payload, bytes); // fetch payload from FIFO
Serial.print(F("Received "));
Serial.print(bytes); // print the size of the payload
Serial.print(F(" bytes on pipe "));
Serial.print(pipe); // print the pipe number
Serial.print(F(": "));
Serial.println(payload); // print the payload's value
}
} // role
if (Serial.available()) {
// change the role via the serial monitor
char c = toupper(Serial.read());
if (c == 'T' && !role) {
// Become the TX node
role = true;
Serial.println(F("*** CHANGING TO TRANSMIT ROLE -- PRESS 'R' TO SWITCH BACK"));
radio.stopListening();
} else if (c == 'R' && role) {
// Become the RX node
role = false;
Serial.println(F("*** CHANGING TO RECEIVE ROLE -- PRESS 'T' TO SWITCH BACK"));
radio.startListening();
}
}
} // loop
For the Raspberry pi I am using dotnet 6 with the Iot.Device.Bindings package, building on my windows machine, scp'ing the dotnet publish output to the pi and running through the dotnet cli with 'dotnet MyProgram.dll':
using System.Text;
using System.Linq.Expressions;
using System.Device.Spi;
using System.Device.Gpio;
using System.Device.Gpio.Drivers;
using Iot.Device.Nrf24l01;
byte[] address1 = new byte[] { 0xE0, 0xE0, 0xE0, 0xE0, 0xE0 };
byte[] address2 = new byte[] { 0xF0, 0xF0, 0xF0, 0xF0, 0xF0 };
int spiBusId = 1;
int spiChipSelect = 2;
int nrfCE = 5;
int nrfIRQ = 6;
byte nrfPacketSize = 4;
byte nrfChannel = 76;
OutputPower nrfPower = OutputPower.N12dBm;
DataRate nrfRate = DataRate.Rate1Mbps;
using RaspberryPi3Driver rpiDriver = new();
using GpioController gpio = new(PinNumberingScheme.Logical, rpiDriver);
SpiConnectionSettings spiSettings = new(spiBusId, spiChipSelect)
{
Mode = Nrf24l01.SpiMode,
ClockFrequency = Nrf24l01.SpiClockFrequency
};
using SpiDevice spi = SpiDevice.Create(spiSettings);
using Nrf24l01 nrf = new(spi, nrfCE, nrfIRQ, nrfPacketSize, gpioController: gpio, channel: nrfChannel, dataRate: nrfRate, outputPower: nrfPower);
// nrf.Address = address2;
nrf.Pipe0.Address = address1;
nrf.Pipe0.AutoAck = true;
nrf.DataReceived += DataReceived;
Console.WriteLine(nrf.NRFDetails());
while (true)
{
Thread.Sleep(200);
}
static void DataReceived(object sender, DataReceivedEventArgs e)
{
var data = e.Data.ToHexString();
Console.WriteLine($"Data received: {data}");
}
static class DataExtensions
{
public static string ToHexString(this byte[] data)
{
return string.Join(", ", data?.Select(x => $"{x:X}") ?? Array.Empty<string>());
}
public static string ToDecString(this byte[] data)
{
return string.Join(", ", data?.Select(x => $"{x}") ?? Array.Empty<string>());
}
public static string NRFDetails(this Nrf24l01 nrf, Expression<Func<Nrf24l01, Nrf24l01Pipe>>? pipeSel = null)
{
StringBuilder builder = new();
builder.AppendLine($"Address: {nrf.Address.ToHexString()}");
builder.AppendLine($"Channel: {nrf.Channel}");
builder.AppendLine($"PacketSize: {nrf.PacketSize}");
builder.AppendLine($"DataRate: {nrf.DataRate}");
builder.AppendLine($"OutputPower: {nrf.OutputPower}");
builder.AppendLine($"PowerMode: {nrf.PowerMode}");
builder.AppendLine($"WorkingMode: {nrf.WorkingMode}");
pipeSel ??= ((x) => x.Pipe0);
string mName = "Pipe";
if (pipeSel.Body is MemberExpression me)
mName = me.Member.Name;
Nrf24l01Pipe pipe = pipeSel.Compile().Invoke(nrf);
builder.AppendLine($"{mName}.Address: {pipe.Address.ToHexString()}");
builder.AppendLine($"{mName}.AutoAck: {pipe.AutoAck}");
builder.AppendLine($"{mName}.Enable: {pipe.Enable}");
builder.AppendLine($"{mName}.Payload: {pipe.Payload}");
return builder.ToString();
}
}
The radio seems to initialize fine, as I can write/read values from the nrf registers without issue, but I am unable to transmit or receive anything from the raspberry pi.
I have tried using the GettingStarted example with an arduino nano every and an arduino uno, using the same nrf modules+adapters and that works fine.
I've tried receiving/transmitting to/from the pi using both the nano every and the uno with no success.
Is this a power issue with the pi? Is this an issue with the dotnet iot library? I was unable to get the nrf24 library by tmrh working on my pi as I get g++ compilation errors from the install.sh provided, so I haven't been able to test a different library yet.
Has anyone run into similar issues, or has anyone made this setup work?
I have a Unity script which connects to a server using TCP. The server sends messages of arbitrary length. The length of the message is described in the first 4 bytes of the message, which I use to determine when the full message has arrived. For some reason, the message isn't being "stitched" together in the correct order. This problem, however, only occurs on Mac. The same script works perfectly fine on Windows (same version of Unity too). Any idea?
Unity version: 2018.4.19f
The connection code is as follows (I edited out things for simplicity of the question):
using System;
using System.Collections.Concurrent;
using System.Net.Sockets;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Events;
public class ServerConnection : MonoBehaviour {
// Example connection parameters
public string IP = "192.168.0.10";
public int Port = 8085;
/// The task object where TCP connection runs.
Task ServerTask { get; set; }
TcpClient Client { get; set; }
Socket Socket { get; set; }
string Message { get; set; }
/// Gets called on the push of a button
public void Connect() {
ServerTask = Task.Factory.StartNew(listenForDataAsync, TaskCreationOptions.LongRunning);
}
async private void listenForDataAsync() {
Debug.Log(string.Format("Connecting to server: IP={0}, Port={1}", IP, Port));
try {
Client = new TcpClient(IP, Port);
Debug.Log("Connection established");
byte[] message = new byte[0];
byte[] msgSize = new byte[4];
int messageSize = 0;
using (NetworkStream stream = Client.GetStream()) {
while (true) {
// Wait for 4 bytes to get message size
await stream.ReadAsync(msgSize, 0, 4);
// Convert message size byte array to an int
int totalMessageSize = BitConverter.ToInt32(msgSize, 0);
// subtract 4 from total message size (4 bytes previously read)
messageSize = totalMessageSize - 4;
// Wait for the rest of the message
message = new byte[messageSize];
int readBytes = 0;
while (messageSize != 0) {
readBytes = await stream.ReadAsync(message, readBytes, messageSize);
messageSize -= readBytes;
}
// Decode byte array to string
string response = System.Text.ASCIIEncoding.ASCII.GetString(message);
// On Mac, response has the message out of order.
// On Windows, it is always perfectly fine.
}
}
}
}
}
Edit: The server is implemented in C++ using Qt and is running on a Windows machine. The server sends responses that may contain large chunks of data. The entire message is composed of 4 bytes indicating the length of the whole message followed by the response itself. The response is a Json string. The error I'm getting is that the final string obtained from the code above is not ordered the same way as it was when it was sent. The message ends in } (closing the outer Json object). However, in the received response, after the }, there are tons of other values that were supposed to be part of an an array inside the Json object. To give you an example, take the following response sent by the server:
{data: [0, 1, 2, 3, 4, 5, 6, 7]}
The code above gets something like:
{data: [0, 1, 2]}, 3, 4, 5, 7
This only happens when the responses are starting to hit the kilobytes range (and higher), and I emphasize, the exact same code works perfectly fine on Windows.
The server sends the message in the following way:
// The response in Json
QJsonObject responseJson;
// Convert to byte array
QJsonDocument doc(responseJson);
QByteArray message = doc.toJson(QJsonDocument::JsonFormat::Compact);
// Insert 4 bytes indicating length of message
qint32 messageSize = sizeof(qint32) + message.size();
message.insert(0, (const char*)&messageSize, sizeof(qint32));
// Send message
// In here, socket is a QTcpSocket
socket->write(message);
Edit #2 Here is an image of the output. The values after the } should have been part of the big array right before Message.
any time you see Read/ReadAsync without capturing and using the result: the code is simply wrong.
Fortunately, it isn't a huge fix:
int toRead = 4, offset = 0, read;
while (toRead != 0 && (read = stream.ReadAsync(msgSize, offset, toRead)) > 0)
{
offset += read;
toRead -= read;
}
if (toRead != 0) throw new EndOfStreamException();
As a side note: BitConverter.ToInt32 is dangerous - it assumes CPU endianness, which is not fixed. It would be better to use BinaryPrimitives.ReadInt32BigEndian or BinaryPrimitives.ReadInt32LittleEndian.
This is an issue mainly due to my inexperience with winsock, sockets, UDP, and programming in general. There are a lot of questions/answers posted related to this issue, but I'm been unable to sift through them due to my poor understanding of winsock, etc., to grasp how to resolve my issue.
Problem.
I currently have a UDP connection running (continously), correctly, through a C++ application and C# script running in a Unity application. Currently, the C++ application convert position data information from an external haptic device into a simple string and sends that datagram via UDP to the unity application. The Unity script then converts the simple string back into relevant information and is used to control objects within the Unity gamespace. This works fine, but mainly because I have been able to find scripts online which only needed a little editing and adjusting to work for my specific application. What I now need to do is to send datagrams from the Unity application to the C++ application (the reverse of what I am currently doing) to provide haptic feedback to the haptic devices when the gameobject interacts with colliders in the Unity gamespace. I have the data that I want to send available when contact with the colliders is made (currently prints via Debug.Log), but I don't know how to properly use the winsock Sentto and Recvfrom functions to have the C++ application actively listening (not just sending) and the Unity application actively sending (not just listening).
I will continue to look through the questions/solutions already posted to see if I can figure it out, but in the meantime if anyone can provide some assistance I would greatly appreciate it. The relevant portions of the code that I am using are posted below:
This script is attached to my gameobject in Unity
using UnityEngine;
using System.Collections;
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Globalization;
using UnityEngine.Serialization;
public class TestUDPConnection : MonoBehaviour
{
private const int listenPort = 11000;
private UdpClient listener;
private IPEndPoint remoteIpEndPoint;
public static string currentUDPData;
private Thread listenerThread;
public bool inputSignal = false;
//bool to fix unity crash
public int fixMe = 1;
// Use this for initialization
void Start ()
{
// Creat a new thread for receiving UDP messages
listenerThread = new Thread(new ThreadStart(UDPReceive));
listenerThread.Name = "UDP Receiving Thread";
listenerThread.IsBackground = true; // run thread in background
listenerThread.Start();
}
// Thread for receiving UDP input
public void UDPReceive()
{
Debug.Log("Start receiving UDP.");
listener = new UdpClient(listenPort); // Initializes and BINDS directly (this is an overload of the UdpClient function, or to be precise the Constructor)
while (fixMe > 0) {
try {
// Opening udpclient listener on port
Debug.Log("Listening to port: " + listenPort);
// Receive bytes
remoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0); // Initializes the Endpoint with the correct Port and any IP address (UDP is connectionless the receiver doesn't need any IP address)
byte[] receiveBytes = listener.Receive(ref remoteIpEndPoint);
// Encode bytes to string (with UTF8 or ASCII Encoding)
currentUDPData = Encoding.ASCII.GetString(receiveBytes);
try {
//code is parsed here
} catch (Exception parseError) {
Debug.Log("Parsing didn't work: " + parseError.ToString());
}
} catch (Exception e) {
Debug.Log("Something went wrong: " + e.ToString());
}
}
Debug.Log("Stop receiving UDP.");
}
// Update is called once per frame
void Update ()
{
// Executed when BACKSPACE key hit
if (Input.GetKeyDown("backspace")) {
DestroyObject(gameObject);
fixMe = 0;
}
}
// Executed when object gets destroyed
void OnDisable()
{
if (listenerThread != null) {
listenerThread.Abort();
}
listener.Close();
Debug.Log("UDP receiver closed");
fixMe = 0;
}
}
This is script that I have in my C++ application (there is more code in the script that deals with the haptic device, but this portion pertains to the UDP communication):
#ifndef UNICODE
#define UNICODE
#endif
#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#include <Ws2tcpip.h>
#include <stdio.h>
#include <vector>
// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")
int iResult;
WSADATA wsaData;
SOCKET SendSocket = INVALID_SOCKET;
sockaddr_in RecvAddr;
unsigned short Port = 11000; //port communicated through
char SendBuf[1024]; //message variable
int BufLen = 1024; //max length of buffer
int main()
{
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != NO_ERROR) {
wprintf(L"WSAStartup failed with error: %d\n", iResult);
//return 1;
}
//---------------------------------------------
// Create a socket for sending data
SendSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (SendSocket == INVALID_SOCKET) {
wprintf(L"socket failed with error: %ld\n", WSAGetLastError());
WSACleanup();
//return 1;
}
//---------------------------------------------
// Set up the RecvAddr structure with the IP address of
// the receiver (in this example case "192.168.1.1")
// and the specified port number.
RecvAddr.sin_family = AF_INET;
RecvAddr.sin_port = htons(Port);
RecvAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
//InetPton(AF_INET, _T("127.0.0.1"), &RecvAddr.sin_addr.s_addr);
//---------------------------------------------
// Clean up and quit.
//wprintf(L"Exiting.\n");
//WSACleanup();
//return 0;
//the udp communication, data parsing, etc., is contained within this loop:
// main haptic simulation loop
while (simulationRunning)
{
//script here for various stuff...
//taking in position values of haptic device in different variables
//converting them to strings
//adding them to the pBuffer that is sent through the UDP pipeline via:
iResult = sendto(SendSocket,
pBuffer, nStrSize + 1, 0, (SOCKADDR *)& RecvAddr, sizeof(RecvAddr));
if (iResult == SOCKET_ERROR) {
wprintf(L"sendto failed with error: %d\n", WSAGetLastError());
closesocket(SendSocket);
WSACleanup();
//return 1;
}
}
So, basically, I'd like to implement the reverse of what is happening in each code (i.e., sending data in the C# Unity script and receiving data in the C++ application script). From what I've read, you can send/receive on the same socket so it seems that it might be simple to add a few lines to my C++ simulation loop which includes the recvfrom function, and some simple copy and paste of my c++ loop into the C# script (with few adjustments) to get the data sent as well. I'll be reading more and trying different code see about to getting it working in the meantime. Thanks ahead of time for any assistance that you can provide!
I have script in Unity that is exchanging data with another Python app. It has a while loop that listens for UDP messages as a background thread. Also the script is asking for new data every frame via the Update function.
After I receive a message, the script parses it as a string and it needs to split the string by tabs in order to retrieve all the values. Currently, the string contains eyetracker and joystick data that Unity needs as player inputs.
UDPController.cs
private void init()
{
// define address to send data to
pythonEndPoint = new IPEndPoint(IPAddress.Parse(IP), pythonPort);
unityEndPoint = new IPEndPoint (IPAddress.Parse (IP), unityPort);
pythonSock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
//define client to receive data at
client = new UdpClient(unityPort);
client.Client.ReceiveTimeout = 1;
client.Client.SendTimeout = 1;
// start background thread to receive information
receiveThread = new Thread(new ThreadStart(ReceiveData));
receiveThread.IsBackground = true;
receiveThread.Start();
}
void Update(){
if (Calibration.calibrationFinished && startRequestNewFrame) {
RequestData();
}
}
private void RequestData() {
// Sends this to the UDP server written in Python
SendString("NEWFRAME");
}
// receive thread which listens for messages from Python UDP Server
private void ReceiveData()
{
while (true)
{
try
{
if(client.Available > 0) {
double unixRecvTimeStamp = DataManager.ConvertToUnixTimestamp(DateTime.Now);
byte[] data = client.Receive(ref pythonEndPoint);
string rawtext = Encoding.UTF8.GetString(data);
string[] msgs = rawtext.Split('\t');
string msgType = msgs[0];
double pythonSentTimeStamp = double.Parse(msgs[msgs.Length-1].Split(' ')[1]);
DataManager.UdpRecvBuffer += '"' + rawtext + '"' + "\t" + pythonSentTimeStamp + "\t" + unixRecvTimeStamp + "\t" + DataManager.ConvertToUnixTimestamp(DateTime.Now) + "\n";
if (String.Equals(msgType, "FRAMEDATA"))
{
DataManager.gazeAdcsPos = new Vector2(float.Parse(msgs[1].Split(' ')[1]), float.Parse(msgs[2].Split(' ')[1]));
float GazeTimeStamp = float.Parse(msgs[3].Split(' ')[1]);
DataManager.rawJoy = new Vector2(float.Parse(msgs[4].Split(' ')[1]), 255 - float.Parse(msgs[5].Split(' ')[1]));
float joyC = float.Parse(msgs[6].Split(' ')[1]);
float ArduinoTimeStamp = float.Parse(msgs[7].Split(' ')[1]);
}
}
}
catch (Exception err)
{
print(err.ToString());
}
}
}
So according to the Unity Profiler, it seems like there is a huge amount of time spent in Behaviour Update, especially inside UDPController.Update() and GC.Collect. My initial hypothesis is that perhaps I'm creating too many strings and arrays overtime and the garbage collector kicks in quite often to remove the unused memory space.
So my question is, is my hypothesis right? If so, how I can rewrite this code to increase my performance and reduce the drop in FPS and perceived lag. If not, where is the issue at because currently the game starts to lag right about 10 minutes in.
Moreover, is there a better way or format for data transferring? It seems like I can be using objects like JSON, Google Protocol Buffer or msgpack or would that be an overkill?
I can see a lot of local variables in your while loop (along with arrays). Local variables cause Garbage collector to run. You should declare all the variables outside of the method.
Moreover, avoid string operations in while/update() as strings are immutable. Thus your code create a new copy to store the result after every concatenation. Use StringBuilder in these situations to avoid GC.
Read More
When I print out received messages on the Console the displayed messages are all messed up, each message containing 5 string sub-messages that are printed on the Console before control reverts back to the incoming message callback. I strongly assume this is because the incoming message event is raised async in Booksleeve?
I refer to the following post, How does PubSub work in BookSleeve/ Redis?, where the author, Marc Gravell, pointed to the ability to force sync reception by setting Completion Mode to "PreserveOrder". I have done that, tried before and after connecting the client. Neither seems to work.
Any ideas how I can receive messages and print them on the console in the exact order they were sent? I only have one single publisher in this case.
Thanks
Edit:
Below some code snippets to show how I send messages and the Booksleeve wrapper I quickly wrote.
Here the client (I have a similar Client2 that receives the messages and checks order, but I omitted it as it seems trivial).
class Client1
{
const string ClientId = "Client1";
private static Messaging Client { get; set; }
private static void Main(string[] args)
{
var settings = new MessagingSettings("127.0.0.1", 6379, -1, 60, 5000, 1000);
Client = new Messaging(ClientId, settings, ReceiveMessage);
Client.Connect();
Console.WriteLine("Press key to start sending messages...");
Console.ReadLine();
for (int index = 1; index <= 100; index++)
{
//I turned this off because I want to preserve
//the order even if messages are sent in rapit succession
//Thread.Sleep(5);
var msg = new MessageEnvelope("Client1", "Client2", index.ToString());
Client.SendOneWayMessage(msg);
}
Console.WriteLine("Press key to exit....");
Console.ReadLine();
Client.Disconnect();
}
private static void ReceiveMessage(MessageEnvelope msg)
{
Console.WriteLine("Message Received");
}
}
Here the relevant code snippets of the library:
public void Connect()
{
RequestForReplyMessageIds = new ConcurrentBag<string>();
Connection = new RedisConnection(Settings.HostName, Settings.Port, Settings.IoTimeOut);
Connection.Closed += OnConnectionClosed;
Connection.CompletionMode = ResultCompletionMode.PreserveOrder;
Connection.SetKeepAlive(Settings.PingAliveSeconds);
try
{
if (Connection.Open().Wait(Settings.RequestTimeOutMilliseconds))
{
//Subscribe to own ClientId Channel ID
SubscribeToChannel(ClientId);
}
else
{
throw new Exception("Could not connect Redis client to server");
}
}
catch
{
throw new Exception("Could not connect Redis Client to Server");
}
}
public void SendOneWayMessage(MessageEnvelope message)
{
SendMessage(message);
}
private void SendMessage(MessageEnvelope msg)
{
//Connection.Publish(msg.To, msg.GetByteArray());
Connection.Publish(msg.To, msg.GetByteArray()).Wait();
}
private void IncomingChannelSubscriptionMessage(string channel, byte[] body)
{
var msg = MessageEnvelope.GetMessageEnvelope(body);
//forward received message
ReceivedMessageCallback(msg);
//release requestMessage if returned msgId matches
string msgId = msg.MessageId;
if (RequestForReplyMessageIds.Contains(msgId))
{
RequestForReplyMessageIds.TryTake(out msgId);
}
}
public void SubscribeToChannel(string channelName)
{
if (!ChannelSubscriptions.Contains(channelName))
{
var subscriberChannel = Connection.GetOpenSubscriberChannel();
subscriberChannel.Subscribe(channelName, IncomingChannelSubscriptionMessage).Wait();
ChannelSubscriptions.Add(channelName);
}
}
Without seeing exactly how you are checking for this, it is hard to comment, but what I can say is that any threading oddity is going to be hard to track down and fix, and is therefore very unlikely to be addressed in BookSleeve, given that it has been succeeded. However! It will absolutely be checked in StackExchange.Redis. Here's the a rig I've put together in SE.Redis (and, embarrassingly, it did highlight a slight bug, fixed in next release, so .222 or later); output first:
Subscribing...
Sending (preserved order)...
Allowing time for delivery etc...
Checking...
Received: 500 in 2993ms
Out of order: 0
Sending (any order)...
Allowing time for delivery etc...
Checking...
Received: 500 in 341ms
Out of order: 306
(keep in mind that 500 x 5ms is 2500, so we should not be amazed by the 2993ms number, or the 341ms - this is mainly the cost of the Thread.Sleep we have added to nudge the thread-pool into overlapping them; if we remove that, both loops take 0ms, which is awesome - but we can't see the overlapping issue so convincingly)
As you can see, the first run has the correct order output; the second run has mixed order, but it ten times faster. And that is when doing trivial work; for real work it would be even more noticeable. As always, it is a trade-off.
Here's the test rig:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using StackExchange.Redis;
static class Program
{
static void Main()
{
using (var conn = ConnectionMultiplexer.Connect("localhost"))
{
var sub = conn.GetSubscriber();
var received = new List<int>();
Console.WriteLine("Subscribing...");
const int COUNT = 500;
sub.Subscribe("foo", (channel, message) =>
{
lock (received)
{
received.Add((int)message);
if (received.Count == COUNT)
Monitor.PulseAll(received); // wake the test rig
}
Thread.Sleep(5); // you kinda need to be slow, otherwise
// the pool will end up doing everything on one thread
});
SendAndCheck(conn, received, COUNT, true);
SendAndCheck(conn, received, COUNT, false);
}
Console.WriteLine("Press any key");
Console.ReadLine();
}
static void SendAndCheck(ConnectionMultiplexer conn, List<int> received, int quantity, bool preserveAsyncOrder)
{
conn.PreserveAsyncOrder = preserveAsyncOrder;
var sub = conn.GetSubscriber();
Console.WriteLine();
Console.WriteLine("Sending ({0})...", (preserveAsyncOrder ? "preserved order" : "any order"));
lock (received)
{
received.Clear();
// we'll also use received as a wait-detection mechanism; sneaky
// note: this does not do any cheating;
// it all goes to the server and back
for (int i = 0; i < quantity; i++)
{
sub.Publish("foo", i);
}
Console.WriteLine("Allowing time for delivery etc...");
var watch = Stopwatch.StartNew();
if (!Monitor.Wait(received, 10000))
{
Console.WriteLine("Timed out; expect less data");
}
watch.Stop();
Console.WriteLine("Checking...");
lock (received)
{
Console.WriteLine("Received: {0} in {1}ms", received.Count, watch.ElapsedMilliseconds);
int wrongOrder = 0;
for (int i = 0; i < Math.Min(quantity, received.Count); i++)
{
if (received[i] != i) wrongOrder++;
}
Console.WriteLine("Out of order: " + wrongOrder);
}
}
}
}