I am using HiRedis with a c/c++ program and have written some tests to verify that subscriptions work (I based my solution on this comment).
However, currently I can only publish by manually typing something like publish foo "abcd" into the redis-cli terminal. This works as per the linked comment, but I'd like to publish from my c++ program. How can I do this?
I have tried this command:
redisAsyncCommand(c, SubCallback, (char*)"command", "publish foo \"abcd\"");
But that results in this runtime error:
Error: ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context
How can I publish data from within HiRedis?
Once a connection context has subscribed, it can't be used to PUBLISH. You must create a new connection.
from https://github.com/redis/hiredis in hiredis/examples/example-libevent.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <hiredis.h>
#include <async.h>
#include <adapters/libevent.h>
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
redisReply *reply = r;
if (reply == NULL) return;
printf("argv[%s]: %s\n", (char*)privdata, reply->str);
/* Disconnect after receiving the reply to GET */
redisAsyncDisconnect(c);
}
void connectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
return;
}
printf("Connected...\n");
}
void disconnectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
return;
}
printf("Disconnected...\n");
}
int main (int argc, char **argv) {
signal(SIGPIPE, SIG_IGN);
struct event_base *base = event_base_new();
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->err) {
/* Let *c leak for now... */
printf("Error: %s\n", c->errstr);
return 1;
}
redisLibeventAttach(c,base);
redisAsyncSetConnectCallback(c,connectCallback);
redisAsyncSetDisconnectCallback(c,disconnectCallback);
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
event_base_dispatch(base);
return 0;
}
Related
I'm struggling using the mosquitto lib on my RaspberryPI 4 in an non-blocking way.
This is may main method:
#include <stdio.h>
#include <mosquitto.h>
#include "mqtt.h"
#include <string>
int main(int argc, char **argv)
{
printf("Start\n");
MqttConnector * mqtt = new MqttConnector("piClient", "send", "rc", 1883, "localhost", 60);
mqtt->startClient();
printf("MQTT is started\n");
while(1)
{
}
return 0;
}
Important parts of mqtt.cpp
#include "mqtt.h"
#include <stdio.h>
#include <string>
#include <string.h>
#include <mosquitto.h>
MqttConnector::MqttConnector(std::string id, std::string sendTopic, std::string receiveTopic, int port, std::string host, int keepalive)
{
mosquitto_lib_init();
mosqClient = mosquitto_new(id.c_str(), true, this);
if(!mosqClient){
fprintf(stderr, "Error: Out of memory.\n");
}
this->keepalive = keepalive;
this->id = id;
this->host = host;
this->port = port;
this->sendTopic = sendTopic;
this->receiveTopic = receiveTopic;
}
MqttConnector::~MqttConnector()
{
mosquitto_destroy(mosqClient);
mosquitto_lib_cleanup();
}
void MqttConnector::messageCallback(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *message)
{
//MqttConnector * mqttInstance = (MqttConnector *) userdata;
if(message->payloadlen){
std::string payloadString = reinterpret_cast<char*>(message->payload);
printf("Message arriving in %s : %s\n", message->topic, payloadString.c_str());
std::string rp = "rp";
if(payloadString == rp)
{
mosquitto_publish(mosq, NULL, "send", strlen("test"), "test", 2, false);
}
}else{
printf("Empty message arriving in %s\n", message->topic);
}
}
void MqttConnector::connectCallback(struct mosquitto *mosq, void *userdata, int result)
{
if(!result){
printf("Connection established\n");
}else{
fprintf(stderr, "Connect failed\n");
}
}
void MqttConnector::logCallback(struct mosquitto *mosq, void *userdata, int level, const char *str)
{
/* Pring all log messages regardless of level. */
printf("%s\n", str);
}
void MqttConnector::publishCallback(struct mosquitto *mosq, void *userdata, int usernumber)
{
printf("Published a message\n");
}
void MqttConnector::startClient()
{
mosquitto_message_callback_set(mosqClient, messageCallback);
mosquitto_log_callback_set(mosqClient, logCallback);
mosquitto_connect_callback_set(mosqClient, connectCallback);
mosquitto_publish_callback_set(mosqClient, publishCallback);
/*
//Connecting without async works! Publish and receiving messages work!
// loop starts by calling loop_forever
if(mosquitto_connect(mosqClient, host.c_str(), port, keepalive)){
fprintf(stderr, "Unable to connect.\n");
}
mosquitto_subscribe(mosqClient, NULL, receiveTopic.c_str(), 2);
mosquitto_loop_forever(mosqClient, 10, 1);
*/
// According to docu we need to call loop_start instead of forever
// not able to publish/receive message with this!
if(mosquitto_connect_async(mosqClient, host.c_str(), port, keepalive)){
fprintf(stderr, "Unable to connect.\n");
}
mosquitto_subscribe(mosqClient, NULL, receiveTopic.c_str(), 2);
mosquitto_loop_start(mosqClient);
}
Connecting to the server with mosquitto_connect and starting the loop with mosquitto_loop_forever is the blocking way. Therefore I only see the first print statement in the main.cpp ('Start'). But with this way, I'm able to publish and receive message with that client.
As soon as I connect with mosquitto_connect_async, I need to start the loop with mosquitto_loop_start as it is mentioned in the documentation. The client connects to the server without an error. And I also see the second print statement from the main.cpp now. But I'm not able to publish oder receive any messages with the mqtt client.
My first guess was that because of the threading, the messages are not printed on the console, but even other clients startet with mosquitto_sub don't receive a message from that c++ client.
Do I need to install something for threading support on the RPI4?
I don't understand why it is not working, because the RPI4 supports multithreading.
Thanks for helping
I'm trying to use mqtt on my raspberrpi4.
I installed mqtt and the c++ headers.
I start my broker with :
mosquitto -d
Then for a validation I start a client via the command line:
mosquitto_sub -d -t "test"
Then I also start the c++ client (following code):
#include <string.h>
#include <stdio.h>
#include <mosquitto.h>
char mqMsg[30];
void my_message_callback(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *message)
{
if(message->payloadlen){
printf("BANANA %s %s\n", message->topic, message->payload);
}else{
printf("BONONO %s (null)\n", message->topic);
}
fflush(stdout);
}
void my_connect_callback(struct mosquitto *mosq, void *userdata, int result)
{
int i;
if(!result){
mosquitto_publish(mosq, NULL, "hello/world", strlen(mqMsg), mqMsg, 2, 0);
}else{
fprintf(stderr, "Connect failed\n");
}
}
void my_log_callback(struct mosquitto *mosq, void *userdata, int level, const char *str)
{
/* Pring all log messages regardless of level. */
printf("%s\n", str);
}
void my_publish_callback(struct mosquitto *mosq, void *userdata, int usernumber)
{
/* We've published so lets exit nicely */
mosquitto_disconnect(mosq);
}
int main(int argc, char *argv[])
{
int i;
char *host = "localhost";
int port = 1883;
int keepalive = 60;
bool clean_session = true;
struct mosquitto *mosq = NULL;
sprintf(mqMsg,"%s",argv[1]);
printf("And the word is >> %s <<\n", mqMsg);
mosquitto_lib_init();
mosq = mosquitto_new(NULL, clean_session, NULL);
if(!mosq){
fprintf(stderr, "Error: Out of memory.\n");
return 1;
}
mosquitto_log_callback_set(mosq, my_log_callback);
mosquitto_connect_callback_set(mosq, my_connect_callback);
mosquitto_message_callback_set(mosq, my_message_callback);
mosquitto_publish_callback_set(mosq, my_publish_callback);
if(mosquitto_connect(mosq, host, port, keepalive)){
fprintf(stderr, "Unable to connect.\n");
return 1;
}
mosquitto_loop_forever(mosq, -1, 1);
mosquitto_subscribe(mosq, NULL, "test", 2);
while(1){
}
mosquitto_destroy(mosq);
mosquitto_lib_cleanup();
return 0;
}
After that I publish something on the topic
mosquitto_pub -d -t "test" -m "test message"
I can see the message on the command line client but not on the c++ client?
Any idea why?
B.t.w. publishing via the c++ client works.
Thanks!
You call mosquitto_subscribe(mosq, NULL, "test", 2); after mosquitto_loop_forever(), therefore when your client actually runs, it's not subscribed to your topic. This also means that you don't need the while(1) in your code. See https://mosquitto.org/api/files/mosquitto-h.html#mosquitto_loop_forever for the description of the `mosquitto_loop_forever()' function.
I am trying to create a microshell. It reads commands in, parses this and splits this, then executes. To parse, first I separate by the delimiter || to get up to two commands if there is a pipe. The split each command into an array of strings.
I thought this is how execlp works, but it only runs the command even though the C string "cmd1" does contain the arguments. Can someone please help me understand how I am passing the parameters wrong to the execlp function?
shell.h
/****************************************************************
PROGRAM: MicroShell(assignment 4)
FILE: shell.h
AUTHOR: Nick Schuck
FUNCTION: This contains the header for the shell class
****************************************************************/
#ifndef _shell_h
#define _shell_h
#include <sys/types.h>
#include <unistd.h>
#include <cstdio>
#include <pwd.h>
#include <cstring>
#include <sys/wait.h>
#include <cstdlib>
#include <vector>
class Shell
{
private:
char buffer[1024];
const char *cmd1[10];
const char *cmd2[10];
public:
Shell(); //default constructor
void askForCommand();
void readCommandLine();
void parseBuffer();
void invokeCommand();
void executeOneCommand();
void executeTwoCommands();
};
#endif
shell.cc
/***************************************************************
PROGRAM: MicroShell(assignment 4)
FILE: shell.c
AUTHOR: Nick Schuck
FUNCTION: This file contains the implementation of
class shell from file "shell.h"
****************************************************************/
#include "shell.h"
#include <iostream>
Shell::Shell()
{
/**Get current user*/
struct passwd *p = getpwuid(getuid());
if (!p) //Error handling
puts("Welcome to Nick Schuck's MicroShell, Anonymous");
/**Welcome message for my shell*/
printf("\n\nWelcome to Nick Schuck's Microshell, user %s!\n\n", p->pw_name);
}
void Shell::askForCommand()
{
/**Command Prompt*/
printf("myshell>");
}
void Shell::readCommandLine()
{
/**Read stdin into buffer array IF no*/
/**errors occur */
if (fgets(this->buffer, 1024, stdin) != NULL)
{
this->buffer[strlen(this->buffer) - 1] = 0;
}
}
void Shell::parseBuffer()
{
/**Variables*/
int i = 0, u = 0,
t = 0;
char *ptr;
char parsingBuffer[2][512];
/**Parse buffer for multiple commands*/
strcpy(parsingBuffer[0], strtok(this->buffer, "||"));
while ((ptr = strtok(NULL, "||")) != NULL)
{
i++;
strcpy(parsingBuffer[i], ptr);
}
//**Get first command*/
this->cmd1[0] = strtok(parsingBuffer[0], " ");
while ((ptr = strtok(NULL, " ")) != NULL)
{
u++;
this->cmd1[u] = ptr;
this->cmd1[u+1] = '\0';
}
//!!!TESTING TO SEE COMMAND ARE IN CMD1
int b = 0;
while(cmd1[b] != '\0')
{
std::cout << cmd1[b] << "\n";
b++;
}
/**Get second command*/
this->cmd2[0] = strtok(parsingBuffer[1], " ");
while ((ptr = strtok(NULL, " ")) != NULL)
{
t++;
this->cmd2[t] = ptr;
}
}
void Shell::invokeCommand()
{
if (this->cmd1[0] == NULL)
{
//do nothing
}
else if(this->cmd1[0] != NULL && this->cmd2[0] == NULL)
{
executeOneCommand();
}
else if(this->cmd1[0] != NULL && cmd2[0] !=NULL)
{
executeTwoCommands();
}
}
void Shell::executeOneCommand()
{
pid_t pid; //pid for fork
int status;
char args[512];
if ((pid = fork()) < 0)
{
printf("fork error\n");
exit(-1);
}
else if(pid == 0) //Child Process
{
execlp(cmd1[0], *cmd1);
}
else //Parent Process
{
if ((pid = waitpid(pid, &status, 0)) < 0)
{
printf("waitpid error in main\n");
exit(-1);
}
}
}
main.cc
#include "shell.h"
#include <iostream>
#include <vector>
int main()
{
const int BUFFER_SIZE = 1024;
const int MAX_COMMANDS_IN_BUFFER = 2;
/**Initialize a new shell object*/
Shell shell;
/**Print command prompt to screen*/
shell.askForCommand();
/**Read users command*/
shell.readCommandLine();
/**parse buffer to find individual*/
/**commands */
shell.parseBuffer();
/**Invoke command*/
shell.invokeCommand();
}
You can't use execlp() — you must use execvp().
execvp(cmd1[0], cmd1);
To use execlp(), you must know at compile time the fixed list of arguments for the command — you must be able to write:
execlp(cmd_name, arg0, arg1, …, argN, (char *)0);
Your call to execlp() is also faulty because you don't provide the (char *)0 argument to indicate the end of the argument list.
Your code also needs to handle exec*() returning, which means the command failed. Usually that means it should print an error message (that the command was not found, or permission denied, or whatever), and then exit with an appropriate non-zero error status.
I'd like to store my serialized data on redis and publish to the channel that i've defined. But it seems that there is a problem on SETting the key value at redis. What do i miss for the solution ?
Thanks in advance.
#include <stdio.h>
#include <assert.h>
#include <signal.h>
#include <stdlib.h>
#include "hiredis.h"
#include "async.h"
#include "macosx.h"
#define PACKETSIZE sizeof(cloudRANMessage)
#define COMMANDSIZE 256
typedef struct cloudRANMessage
{
unsigned int station_id;
unsigned int location_area;
char command[COMMANDSIZE];
}cloudRANMessage;
void printMyMessage(cloudRANMessage *message)
{
printf("%d\n", message->location_area);
printf("%d\n", message->station_id);
printf("%s\n", message->command);
}
void serialize(cloudRANMessage *message, char *data)
{
assert(data != NULL);
memcpy(data, message, sizeof *message);
}
void deserialize(char *data)
{
cloudRANMessage *tempMessage = malloc(sizeof(cloudRANMessage)); // To store deserialized message.
memset(tempMessage, 0, sizeof(cloudRANMessage));
memcpy(tempMessage, data, sizeof(cloudRANMessage));
printMyMessage(tempMessage);
}
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
redisReply *reply = r;
if (reply == NULL) return;
printf("%s\n", reply->str); // Call deserializaton function for the data retrieval.;
/* Disconnect after receiving the reply to GET */
redisAsyncDisconnect(c);
}
void callbackDeserialize(redisAsyncContext *c, void *r, void *privdata) {
redisReply *reply = r;
if (reply == NULL) return;
printf("%s\n", reply->str); // Call deserializaton function for the data retrieval.
char *stringReply = reply->element[0]->str;
deserialize(stringReply);
/* Disconnect after receiving the reply to GET */
redisAsyncDisconnect(c);
}
void connectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
return;
}
printf("Connected...\n");
}
void disconnectCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
printf("Error: %s\n", c->errstr);
return;
}
CFRunLoopStop(CFRunLoopGetCurrent());
printf("Disconnected...\n");
}
int main (int argc, char **argv) {
cloudRANMessage *newcloudRANMessage = malloc(sizeof(cloudRANMessage));
newcloudRANMessage->location_area = 7214;
newcloudRANMessage->station_id = 45632;
strcpy(newcloudRANMessage->command, "HANDOVER\0");
char data[PACKETSIZE];
serialize(newcloudRANMessage, data);
signal(SIGPIPE, SIG_IGN);
CFRunLoopRef loop = CFRunLoopGetCurrent();
if( !loop ) {
printf("Error: Cannot get current run loop\n");
return 1;
}
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->err) {
/* Let *c leak for now... */
printf("Error: %s\n", c->errstr);
return 1;
}
redisMacOSAttach(c, loop);
redisAsyncSetConnectCallback(c,connectCallback);
redisAsyncSetDisconnectCallback(c,disconnectCallback);
redisAsyncCommand(c,getCallback,NULL,"SUBSCRIBE cloudRAN");
// Serialize Data then send to Redis
//redisAsyncCommand(c, getCallback, (char*) "SET", "SET LTEdata %s", data, strlen(data)); // key for our data in this case is LTEdata
redisAsyncCommand(c,NULL, NULL, "SET LTEdata %s", data);
//redisAsyncCommand(c, getCallback,(char*) "GET", "GET LTEdata");
redisAsyncCommand(c, callbackDeserialize,NULL, "GET LTEdata");
// Publish the information to the all subscribers.
redisAsyncCommand(c,NULL, NULL, "PUB cloudRAN %b",data,strlen(data));
CFRunLoopRun();
return 0;
}
In this call to redisAsyncCommand:
redisAsyncCommand(c,NULL, NULL, "SET LTEdata %s", data);
any null bytes appearing in data will terminate the hiredis string interpolation. Since you are binary encoding, this is likely to be truncating your string. Try specifying the length to make it binary-safe:
redisAsyncCommand(c,NULL, NULL, "SET LTEdata %b", data, sizeof(data));
I'm trying to improve my knowledge of OOP and decided to create a simple class to simplify sockets programming.
This is a learning experiment so I do not want to use boost, or other libraries.
I want to implement an event-driven recv(). Meaning, everytime there is new data coming in, it should call my function.
I think I need to create a thread to run a recv() loop and then call my function everytime there is new data. Is there other way around using threads? I want my code to be portable.
Here is my simple Class and example code:
class.h:
#ifndef _SOCKETSCLASS_H
#define _SOCKETSCLASS_H
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__)
#define W32
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
#else
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#define SOCKET int
#endif
#include <string>
#include<ctime>
#include <stdio.h>
#include <stdarg.h>
#include <varargs.h>
#include <tchar.h>
using namespace std;
#ifdef _DEBUG
#define DEBUG(msg) XTrace(msg)
#else
#define DEBUG(msg, params)
#endif
struct TCP_Client_opts
{
BOOL UseSCprotocol;
BOOL UseEncryption;
BOOL UseCompression;
int CompressionLevel;
void *Callback;
BOOL async;
};
struct TCP_Stats
{
unsigned long int upload; //bytes
unsigned long int download;//bytes
time_t uptime; //seconds
};
class TCP_Client
{
public:
TCP_Client();
TCP_Client(TCP_Client_opts opts_set);
~TCP_Client();
SOCKET GetSocket();
void SetOptions(TCP_Client_opts opts_set);
TCP_Client_opts GetOptions();
BOOL Connect(string server, int port);
int Send(string data);
int Recv(string *data);
BOOL IsConnected();
int Disconnect();
TCP_Stats GetStats();
private:
SOCKET s = SOCKET_ERROR;
TCP_Client_opts opts;
TCP_Stats stats;
BOOL connected = FALSE;
time_t starttime;
};
#endif
class.cpp:
#include "SocketsClass.h"
void XTrace(LPCTSTR lpszFormat, ...)
{
va_list args;
va_start(args, lpszFormat);
int nBuf;
TCHAR szBuffer[512]; // get rid of this hard-coded buffer
nBuf = _vsnwprintf_s(szBuffer, 511, lpszFormat, args);
::OutputDebugString(szBuffer);
va_end(args);
}
TCP_Client::TCP_Client(TCP_Client_opts opts_set)
{
SetOptions(opts_set);
}
TCP_Client::~TCP_Client()
{
Disconnect();
}
TCP_Client::TCP_Client()
{
}
void TCP_Client::SetOptions(TCP_Client_opts opts_set)
{
opts = opts_set;
}
TCP_Client_opts TCP_Client::GetOptions()
{
return opts;
}
SOCKET TCP_Client::GetSocket()
{
return s;
}
BOOL TCP_Client::IsConnected()
{
return connected;
}
int TCP_Client::Disconnect()
{
connected = FALSE;
stats.uptime = time(0) - starttime;
return shutdown(s, 2);
}
BOOL TCP_Client::Connect(string server, int port)
{
struct sockaddr_in RemoteHost;
#ifdef W32
WSADATA wsd;
if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
{
DEBUG(L"Failed to load Winsock!\n");
return FALSE;
}
#endif
//create socket if it is not already created
if (s == SOCKET_ERROR)
{
//Create socket
s = socket(AF_INET, SOCK_STREAM, 0);
if (s == SOCKET_ERROR)
{
DEBUG(L"Could not create socket");
return FALSE;
}
}
//setup address structure
if (inet_addr(server.c_str()) == INADDR_NONE)
{
struct hostent *he;
//resolve the hostname, its not an ip address
if ((he = gethostbyname(server.c_str())) == NULL)
{
//gethostbyname failed
DEBUG(L"gethostbyname() - Failed to resolve hostname\n");
return FALSE;
}
}
else//plain ip address
{
RemoteHost.sin_addr.s_addr = inet_addr(server.c_str());
}
RemoteHost.sin_family = AF_INET;
RemoteHost.sin_port = htons(port);
//Connect to remote server
if (connect(s, (struct sockaddr *)&RemoteHost, sizeof(RemoteHost)) < 0)
{
DEBUG(L"connect() failed");
return FALSE;
}
connected = TRUE;
starttime = time(0);
stats.download = 0;
stats.upload = 0;
return TRUE;
}
TCP_Stats TCP_Client::GetStats()
{
if (connected==TRUE)
stats.uptime = time(0)-starttime;
return stats;
}
int TCP_Client::Send(string data)
{
stats.upload += data.length();
return send(s, data.c_str(), data.length(), 0);
}
int TCP_Client::Recv(string *data)
{
int ret = 0;
char buffer[512];
ret = recv(s, buffer, sizeof(buffer), 0);
data->assign(buffer);
data->resize(ret);
stats.download += data->length();
return ret;
}
main.cpp:
#include <stdio.h>
#include <string.h>
#include "SocketsClass.h"
using namespace std;
int main(int argc, char *argv)
{
TCP_Client tc;
tc.Connect("127.0.0.1", 9999);
tc.Send("HEllo");
string data;
tc.Recv(&data);
puts(data.c_str());
tc.Disconnect();
printf("\n\nDL: %i\nUP: %i\nUptime: %u\n", tc.GetStats().download, tc.GetStats().upload, tc.GetStats().uptime);
return 0;
}
Some extra questions:
Imagine I'm sending a file. How would my function know that the current data is related to the previous message?
How is my class design and implementation? SHould I change anything?
Thank you
If by "portable" you mean runs on other platforms besides Windows then a recv() loop in a worker thread is your only portable option. On Windows specifically, you have some additional choices:
Allocate a hidden window and then use WSAAsyncSelect() to receive FD_READ notifications. This requires a message loop, which you can put in a worker thread.
Use WSAEventSelect() to register a waitable event for FD_READ notifications and then wait for those events via WSAWaitForMultipleEvents() in a thread.
use WSARecv() with an I/O Completion Port. Poll the IOCP via GetQueuedCompletionResult() in a thread.
As for your question regarding messaging, TCP is a byte stream, it has no concept of messages. You have to frame your messages yourself. You can either:
give each message a fixed header that contains the message length. Read the header first, then read however many bytes it says, then read the next header, and so on.
separate each message with a unique delimiter that does not appear in the message data. Read until you encounter that delimiter, then read until the next delimiter, and so on.
Have your event loop call either poll or select to determine if there is data that can be read on the socket(s). Then read it, and call the appropriate callback function.