-1

I want to send random trigger signals (A and B) from Matlab to a C++ Code. The point where I stuck now is, that whenever I am not sending this trigger signal/message, the C++ Code keeps waiting for it and doesn't continue its process. How can I make the C++ Code keep running (to collect data) without waiting for the next trigger message. Because now only once it receives the message (UDP transfers trigger signal) it gives me the specific outcome.

----------- BEGIN MATLAB CODE ---------------------

send_trigger_signal = instrfind('Type', 'udp', 'LocalHost', '127.0.0.1','RemoteHost', '192.168.0.100', 'RemotePort', 8888, 'LocalPort', 8844, 'Tag', '');

% Create the udp object if it does not exist otherwise use the object that was found.
 if isempty(send_trigger_signal)
     send_trigger_signal = udp('127.0.0.1', 'RemotePort', 8888, 'LocalPort', 8844);
 else
     fclose(send_trigger_signal);
     send_trigger_signal = send_trigger_signal(1);
 end

 send_trigger_signal.DatagramTerminateMode='off';
 send_trigger_signal.Timeout=0.0001;
 send_trigger_signal.Timerperiod=0.01;
 
 %send_trigger_signal.
% Connect to instrument object, send_trigger_signal.
 fopen(send_trigger_signal);

% Communicating with instrument object, send_trigger_signal.
 on_trigger_command=typecast(swapbytes(uint16([1 1 0 0])),'uint8'); %trigger on
 off_trigger_command=typecast(swapbytes(uint16([0 0 0 0])),'uint8'); %trigger off

while(true) 
 for i=1:1
     fprintf(send_trigger_signal, 'A');
     WaitSecs(5);
end
end
fclose(send_trigger_signal);    

send_trigger_signal=instrfindall;
delete(send_trigger_signal);
instrfindall;
----------- END MATLAB CODE ---------------------


This is the C++ code which should receive the random trigger signals from Matlab (A and B), while collecting gyro data between those signals. To test it here the message is send every 5sec. The problem is that I cannot collect the gyro data in within those 5sec. The UDP communication is interrupting the data collection - because it is waiting those 5sec.

----------- START C++ CODE ---------------------

#include <iostream>
#include <winsock2.h>
using namespace std;

#pragma comment(lib,"ws2_32.lib") // Winsock Library
#pragma warning(disable:4996) 

#define BUFLEN 512
#define PORT 8888

int receiver(void)
{
    int value = 5;
    system("title UDP Server");

    sockaddr_in server, client;

    // initialise winsock
    WSADATA wsa;
    printf("Initialising Winsock...");
    if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
    {
        printf("Failed. Error Code: %d", WSAGetLastError());
        exit(0);
    }
    printf("Initialised.\n");

    // create a socket
    SOCKET server_socket;
    if ((server_socket = socket(AF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET)
    {
        printf("Could not create socket: %d", WSAGetLastError());
    }
    printf("Socket created.\n");

    // prepare the sockaddr_in structure
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_port = htons(PORT);

    // bind
    if (bind(server_socket, (sockaddr*)&server, sizeof(server)) == SOCKET_ERROR)
    {
        printf("Bind failed with error code: %d", WSAGetLastError());
        exit(EXIT_FAILURE);
    }
    puts("Bind done.");

    while (true)
    {
        printf("Waiting for data...");
        fflush(stdout);
        char message[BUFLEN] = {};

        // try to receive some data, this is a blocking call
        int message_len;
        int slen = sizeof(sockaddr_in);
        if (message_len = recvfrom(server_socket, message, BUFLEN, 0, (sockaddr*)&client, &slen) == SOCKET_ERROR)
        {
            printf(message);
            printf("recvfrom() failed with error code: %d", WSAGetLastError());
            exit(0);
        }
        if (message[0] == 'A')
        {
            value = 6;
            break;
        }
        if (message[0] == 'B')
        {
            value = 7;
            break;
        }
        // print details of the client/peer and the data received
        printf("Received packet from %s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
        printf("Data: %s\n", message);
        return 0;
    }

    closesocket(server_socket);
    WSACleanup();

    return value;
}

int main()
{
    while (true)
    {

        // Reading some gyro data here

        // Listening UDP
        receiver();

    }
    return 0;
}

----------- END C++ CODE ---------------------
hardillb
  • 54,545
  • 11
  • 67
  • 105
mcs
  • 1
  • 3
  • The usual solution to having a program do multiple things at once is threads. Sometimes you can use groovy stuff like `select` or Overlapped IO, but I'm not seeing an easy way to take advantage of either in this case other than via poling. – user4581301 Sep 12 '22 at 20:29
  • 1
    Rethinking this. Polling probably is the right way to go. Use dumb-old non-blocking sockets. Read the gyro, read the socket, if the socket returns WOULDBLOCK, loop back to reading the gyro, otherwise read the packet. – user4581301 Sep 12 '22 at 20:45
  • Thanks! You mean I should create two sockets? I have no experience with that and I don’t understand well- could you be more specific? Instead of UDP- shall I setup a MQTT communication? – mcs Sep 13 '22 at 17:36
  • Still one receiver socket, but set the socket to non blocking. I've never done this on Windows with a UDP socket, but it should be almost exactly the same as [this example here for a TCP socket](https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-ioctlsocket) – user4581301 Sep 13 '22 at 19:04

1 Answers1

0

With a few structural tweaks:

  1. Using non-blocking socket.
  2. You don't want to restart winsock and rebind the socket every time you read from it, so that's spun off to different functions (an RAII wrapper class in the case of winsock).
  3. C-style IO replaced with C++ IO.
  4. exit(0) means the program succeeded, but was used in many cases where failure occurred. Consistently using exit(EXIT_FAILURE);. Might be worth throwing an exception, but it's annoying to get the error code into the exception text.
  5. Removed some of the output because it would be spammed out now that the receive function can immediately return .

Your program could look something like this:

#include <iostream>
#include <winsock2.h>
using namespace std;

#pragma comment(lib,"ws2_32.lib") // Winsock Library
#pragma warning(disable:4996)

// using modern C++ constants
constexpr int BUFLEN = 512;
constexpr int PORT = 8888;

//RAII wrapper to make sure winsock is created and disposed of responsibly
struct winsock_RAII
{
    winsock_RAII()
    {
        WSADATA wsa;
        if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
        {
            std::cerr << "Failed to initialize winsock. Error Code: " << WSAGetLastError() << '\n';
            exit(EXIT_FAILURE);
        }
    }
    ~winsock_RAII()
    {
        WSACleanup(); // what are we gonna do if it fails? Not much we can do.
    }
};
//socket initialization
SOCKET init_sock()
{
    SOCKET server_socket;
    if ((server_socket = socket(AF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET)
    {
        std::cerr << "Failed to get socket. Error Code: " << WSAGetLastError() << '\n';
        exit(EXIT_FAILURE);
    }
    u_long iMode = 1;
    //setr socket non-blocking
    if (ioctlsocket(server_socket, FIONBIO, &iMode) != NO_ERROR)
    {
        std::cerr << "Failed to get socket. Error Code: " << WSAGetLastError() << '\n';
        exit(EXIT_FAILURE);
    }
    // prepare the sockaddr_in structure
    sockaddr_in server;
    
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_port = htons(PORT);
    
    // bind
    if (bind(server_socket, (sockaddr*) &server, sizeof(server)) == SOCKET_ERROR)
    {
        std::cerr << "Bind failed. Error Code: " << WSAGetLastError() << '\n';
        exit(EXIT_FAILURE);
    }
    return server_socket;
}
// read from socket
int receiver(SOCKET server_socket)
{
    // try to receive some data, this is a non-blocking call
    int slen = sizeof(sockaddr_in);
    sockaddr_in client;
    char message[BUFLEN + 1]; // no need to clear the whole buffer. We'll know 
                              // exactly where to put the null thanks to message_len
                              // +1 makes sure we have room for terminator

    int message_len = recvfrom(server_socket, message,
                               BUFLEN,
                               0, 
                               (sockaddr*) &client, 
                               &slen);
    int value = 5;
    if (message_len != SOCKET_ERROR)
    {
        message[message_len] = '\0'; // place terrminator
        if (message[0] == 'A')
        {
            value = 6;
        }
        if (message[0] == 'B')
        {
            value = 7;
        }
        // print details of the client/peer and the data received
        std::cout << "Received packet from " << inet_ntoa(client.sin_addr) << ':' << ntohs(client.sin_port) << '\n' 
                  << "Data: " << message << '\n';
    }
    else if (WSAGetLastError() != WSAEWOULDBLOCK)
    {
        // printf(message); no point to printing message. There isn't one
        std::cerr << "recvfrom() failed . Error Code: " << WSAGetLastError() << '\n';
        exit(EXIT_FAILURE);
    }
    return value;
}

int main()
{
    winsock_RAII winsock; // scoped winsock initializer
    SOCKET server_socket = init_sock();
    while (true)
    {
        // Reading some gyro data here
        receiver(server_socket);
    }
    closesocket(server_socket);
    
    return 0;
}

You might want to use select with a short timeout to throttle the loop because it can be a serious and unnecessary CPU-eater if the gyro reading code is also quick.

user4581301
  • 33,082
  • 7
  • 33
  • 54
  • Thank you very much! This was really helping me - it is exactly how I wanted to solve my problem! What you also sent as an option was really interesting! I was trying to use the 'select' socket, but I failed. How can I use this solution in this code? – mcs Sep 15 '22 at 19:56
  • I'm going to [let Beej explain `select`](https://beej.us/guide/bgnet/html/#select) – user4581301 Sep 15 '22 at 20:04
  • The set up for and call to `select` goes in there the call to `recvfrom` currently sits. If `select` returns 0, nothing happened. If it returns negative, there was an error, log it and exit. If it returns a positive number, data is available, so in this branch you call `recvfrom` and handle errors or parse the data exactly as you are now. – user4581301 Sep 15 '22 at 20:09