Pass autobahn with pub/sub
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
#include "App.h"
|
#include "App.h"
|
||||||
|
|
||||||
|
struct us_listen_socket_t *listen_socket;
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
/* ws->getUserData returns one of these */
|
/* ws->getUserData returns one of these */
|
||||||
struct PerSocketData {
|
struct PerSocketData {
|
||||||
@@ -10,7 +12,7 @@ int main() {
|
|||||||
uWS::App().ws<PerSocketData>("/*", {
|
uWS::App().ws<PerSocketData>("/*", {
|
||||||
/* Settings */
|
/* Settings */
|
||||||
.compression = uWS::SHARED_COMPRESSOR,
|
.compression = uWS::SHARED_COMPRESSOR,
|
||||||
.maxPayloadLength = 16 * 1024,
|
.maxPayloadLength = 16 * 1024 * 1024,
|
||||||
.idleTimeout = 10,
|
.idleTimeout = 10,
|
||||||
/* Handlers */
|
/* Handlers */
|
||||||
.open = [](auto *ws, auto *req) {
|
.open = [](auto *ws, auto *req) {
|
||||||
@@ -18,8 +20,15 @@ int main() {
|
|||||||
ws->subscribe("broadcast");
|
ws->subscribe("broadcast");
|
||||||
},
|
},
|
||||||
.message = [](auto *ws, std::string_view message, uWS::OpCode opCode) {
|
.message = [](auto *ws, std::string_view message, uWS::OpCode opCode) {
|
||||||
|
/* Exit gracefully if we get a closedown message (ASAN debug) */
|
||||||
|
if (message == "closedown") {
|
||||||
|
/* Bye bye */
|
||||||
|
us_listen_socket_close(0, listen_socket);
|
||||||
|
ws->close();
|
||||||
|
}
|
||||||
|
|
||||||
/* Simply broadcast every single message we get */
|
/* Simply broadcast every single message we get */
|
||||||
ws->publish("broadcast", message/*, opCode*/);
|
ws->publish("broadcast", message, opCode);
|
||||||
},
|
},
|
||||||
.drain = [](auto *ws) {
|
.drain = [](auto *ws) {
|
||||||
/* Check getBufferedAmount here */
|
/* Check getBufferedAmount here */
|
||||||
@@ -34,6 +43,7 @@ int main() {
|
|||||||
/* We automatically unsubscribe from any topic here */
|
/* We automatically unsubscribe from any topic here */
|
||||||
}
|
}
|
||||||
}).listen(9001, [](auto *token) {
|
}).listen(9001, [](auto *token) {
|
||||||
|
listen_socket = token;
|
||||||
if (token) {
|
if (token) {
|
||||||
std::cout << "Listening on port " << 9001 << std::endl;
|
std::cout << "Listening on port " << 9001 << std::endl;
|
||||||
}
|
}
|
||||||
|
|||||||
+47
-15
@@ -58,7 +58,7 @@ struct Topic {
|
|||||||
Topic *terminatingWildcardChild = nullptr;
|
Topic *terminatingWildcardChild = nullptr;
|
||||||
|
|
||||||
/* What we published */
|
/* What we published */
|
||||||
std::map<int, std::string> messages;
|
std::map<unsigned int, std::string> messages;
|
||||||
|
|
||||||
std::set<Subscriber *> subs;
|
std::set<Subscriber *> subs;
|
||||||
};
|
};
|
||||||
@@ -70,7 +70,7 @@ private:
|
|||||||
Topic *root = new Topic;
|
Topic *root = new Topic;
|
||||||
|
|
||||||
/* Global messageId for deduplication of overlapping topics and ordering between topics */
|
/* Global messageId for deduplication of overlapping topics and ordering between topics */
|
||||||
int messageId = 0;
|
unsigned int messageId = 0;
|
||||||
|
|
||||||
/* The triggered topics */
|
/* The triggered topics */
|
||||||
Topic *triggeredTopics[64];
|
Topic *triggeredTopics[64];
|
||||||
@@ -89,7 +89,29 @@ private:
|
|||||||
parent->wildcardChild = nullptr;
|
parent->wildcardChild = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* Erase us from our parents set (wildcards also live here) */
|
||||||
parent->children.erase(std::string_view(topic->name, topic->length));
|
parent->children.erase(std::string_view(topic->name, topic->length));
|
||||||
|
|
||||||
|
/* If this node is triggered, make sure to remove it from the triggered list */
|
||||||
|
if (topic->triggered) {
|
||||||
|
Topic *tmp[64];
|
||||||
|
int length = 0;
|
||||||
|
for (int i = 0; i < numTriggeredTopics; i++) {
|
||||||
|
if (triggeredTopics[i] != topic) {
|
||||||
|
tmp[length++] = triggeredTopics[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
triggeredTopics[i] = tmp[i];
|
||||||
|
}
|
||||||
|
numTriggeredTopics = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free various memory for the node */
|
||||||
|
delete [] topic->name;
|
||||||
|
delete topic;
|
||||||
|
|
||||||
if (parent != root) {
|
if (parent != root) {
|
||||||
trimTree(parent);
|
trimTree(parent);
|
||||||
}
|
}
|
||||||
@@ -155,6 +177,10 @@ public:
|
|||||||
this->cb = cb;
|
this->cb = cb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
~TopicTree() {
|
||||||
|
delete root;
|
||||||
|
}
|
||||||
|
|
||||||
void subscribe(std::string_view topic, Subscriber *subscriber) {
|
void subscribe(std::string_view topic, Subscriber *subscriber) {
|
||||||
/* Start iterating from the root */
|
/* Start iterating from the root */
|
||||||
Topic *iterator = root;
|
Topic *iterator = root;
|
||||||
@@ -222,10 +248,12 @@ public:
|
|||||||
topic->subs.erase(subscriber);
|
topic->subs.erase(subscriber);
|
||||||
trimTree(topic);
|
trimTree(topic);
|
||||||
}
|
}
|
||||||
|
subscriber->subscriptions.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Drain the tree by emitting what to send with every Subscriber */
|
/* Drain the tree by emitting what to send with every Subscriber */
|
||||||
|
/* Better name would be commit() and making it public so that one can commit and shutdown, etc */
|
||||||
void drain() {
|
void drain() {
|
||||||
|
|
||||||
/* Do nothing if nothing to send */
|
/* Do nothing if nothing to send */
|
||||||
@@ -233,18 +261,16 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fast path for one topic (can also be used with heuristics) */
|
/* bug fix: update min, as the one tracked via subscribe gets invalid as you unsubscribe */
|
||||||
if (numTriggeredTopics == -555555) {
|
min = (Subscriber *)UINTPTR_MAX;
|
||||||
/* Disabled */
|
for (int i = 0; i < numTriggeredTopics; i++) {
|
||||||
/*std::string res;
|
if ((triggeredTopics[i]->subs.size()) && (min > *triggeredTopics[i]->subs.begin())) {
|
||||||
for (auto &p : triggeredTopics[0]->messages) {
|
min = *triggeredTopics[i]->subs.begin();
|
||||||
res.append(p.second);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (Subscriber *s : triggeredTopics[0]->subs) {
|
/* Check if we really have any sockets still */
|
||||||
cb(s, res);
|
if (min != (Subscriber *)UINTPTR_MAX) {
|
||||||
}*/
|
|
||||||
} else {
|
|
||||||
|
|
||||||
/* Up to 64 triggered Topics per batch */
|
/* Up to 64 triggered Topics per batch */
|
||||||
std::map<uint64_t, std::string> intersectionCache;
|
std::map<uint64_t, std::string> intersectionCache;
|
||||||
@@ -263,7 +289,7 @@ public:
|
|||||||
Subscriber *nextMin = (Subscriber *)UINTPTR_MAX;
|
Subscriber *nextMin = (Subscriber *)UINTPTR_MAX;
|
||||||
|
|
||||||
/* The message sets relevant for this intersection */
|
/* The message sets relevant for this intersection */
|
||||||
std::map<int, std::string> *perSubscriberIntersectingTopicMessages[64];
|
std::map<unsigned int, std::string> *perSubscriberIntersectingTopicMessages[64];
|
||||||
int numPerSubscriberIntersectingTopicMessages = 0;
|
int numPerSubscriberIntersectingTopicMessages = 0;
|
||||||
|
|
||||||
uint64_t intersection = 0;
|
uint64_t intersection = 0;
|
||||||
@@ -297,7 +323,7 @@ public:
|
|||||||
if (intersectionCache[intersection].length() == 0) {
|
if (intersectionCache[intersection].length() == 0) {
|
||||||
|
|
||||||
/* Build the union in order without duplicates */
|
/* Build the union in order without duplicates */
|
||||||
std::map<int, std::string> complete;
|
std::map<unsigned int, std::string> complete;
|
||||||
for (int i = 0; i < numPerSubscriberIntersectingTopicMessages; i++) {
|
for (int i = 0; i < numPerSubscriberIntersectingTopicMessages; i++) {
|
||||||
complete.insert(perSubscriberIntersectingTopicMessages[i]->begin(), perSubscriberIntersectingTopicMessages[i]->end());
|
complete.insert(perSubscriberIntersectingTopicMessages[i]->begin(), perSubscriberIntersectingTopicMessages[i]->end());
|
||||||
}
|
}
|
||||||
@@ -337,7 +363,13 @@ public:
|
|||||||
for (int i = 0; i < indentation; i++) {
|
for (int i = 0; i < indentation; i++) {
|
||||||
std::cout << " ";
|
std::cout << " ";
|
||||||
}
|
}
|
||||||
std::cout << std::string_view(p.second->name, p.second->length) << " = " << p.second->messages.size() << " publishes, " << p.second->subs.size() << " subscribers" << std::endl;
|
std::cout << std::string_view(p.second->name, p.second->length) << " = " << p.second->messages.size() << " publishes, " << p.second->subs.size() << " subscribers {";
|
||||||
|
|
||||||
|
for (auto &p : p.second->subs) {
|
||||||
|
std::cout << p << " referring to socket: " << p->user << ", ";
|
||||||
|
}
|
||||||
|
std::cout << "}" << std::endl;
|
||||||
|
|
||||||
print(p.second, indentation + 1);
|
print(p.second, indentation + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-3
@@ -129,6 +129,8 @@ public:
|
|||||||
|
|
||||||
/* Make sure to unsubscribe from any pub/sub node at exit */
|
/* Make sure to unsubscribe from any pub/sub node at exit */
|
||||||
webSocketContextData->topicTree.unsubscribeAll(webSocketData->subscriber);
|
webSocketContextData->topicTree.unsubscribeAll(webSocketData->subscriber);
|
||||||
|
delete webSocketData->subscriber;
|
||||||
|
webSocketData->subscriber = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Subscribe to a topic according to MQTT rules and syntax */
|
/* Subscribe to a topic according to MQTT rules and syntax */
|
||||||
@@ -147,16 +149,17 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Publish a message to a topic according to MQTT rules and syntax */
|
/* Publish a message to a topic according to MQTT rules and syntax */
|
||||||
void publish(std::string_view topic, std::string_view message) {
|
void publish(std::string_view topic, std::string_view message, OpCode opCode = OpCode::TEXT, bool compress = false) {
|
||||||
WebSocketContextData<SSL> *webSocketContextData = (WebSocketContextData<SSL> *) us_socket_context_ext(SSL,
|
WebSocketContextData<SSL> *webSocketContextData = (WebSocketContextData<SSL> *) us_socket_context_ext(SSL,
|
||||||
(us_socket_context_t *) us_socket_context(SSL, (us_socket_t *) this)
|
(us_socket_context_t *) us_socket_context(SSL, (us_socket_t *) this)
|
||||||
);
|
);
|
||||||
|
|
||||||
/* We frame the message right here and only pass raw bytes to the pub/subber */
|
/* We frame the message right here and only pass raw bytes to the pub/subber */
|
||||||
char dst[1024];
|
char *dst = (char *) malloc(protocol::messageFrameSize(message.size()));
|
||||||
size_t dst_length = protocol::formatMessage<true>(dst, message.data(), message.length(), OpCode::TEXT, message.length(), false);
|
size_t dst_length = protocol::formatMessage<true>(dst, message.data(), message.length(), opCode, message.length(), false);
|
||||||
|
|
||||||
webSocketContextData->topicTree.publish(topic, std::string_view(dst, dst_length));
|
webSocketContextData->topicTree.publish(topic, std::string_view(dst, dst_length));
|
||||||
|
free(dst);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -225,7 +225,6 @@ private:
|
|||||||
|
|
||||||
/* Handle socket disconnections */
|
/* Handle socket disconnections */
|
||||||
us_socket_context_on_close(SSL, getSocketContext(), [](auto *s) {
|
us_socket_context_on_close(SSL, getSocketContext(), [](auto *s) {
|
||||||
|
|
||||||
/* For whatever reason, if we already have emitted close event, do not emit it again */
|
/* For whatever reason, if we already have emitted close event, do not emit it again */
|
||||||
WebSocketData *webSocketData = (WebSocketData *) (us_socket_ext(SSL, s));
|
WebSocketData *webSocketData = (WebSocketData *) (us_socket_ext(SSL, s));
|
||||||
if (!webSocketData->isShuttingDown) {
|
if (!webSocketData->isShuttingDown) {
|
||||||
@@ -238,6 +237,8 @@ private:
|
|||||||
|
|
||||||
/* Make sure to unsubscribe from any pub/sub node at exit */
|
/* Make sure to unsubscribe from any pub/sub node at exit */
|
||||||
webSocketContextData->topicTree.unsubscribeAll(webSocketData->subscriber);
|
webSocketContextData->topicTree.unsubscribeAll(webSocketData->subscriber);
|
||||||
|
delete webSocketData->subscriber;
|
||||||
|
webSocketData->subscriber = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Destruct in-placed data struct */
|
/* Destruct in-placed data struct */
|
||||||
|
|||||||
@@ -41,22 +41,24 @@ struct WebSocketContextData {
|
|||||||
size_t maxPayloadLength = 0;
|
size_t maxPayloadLength = 0;
|
||||||
int idleTimeout = 0;
|
int idleTimeout = 0;
|
||||||
|
|
||||||
|
/* There needs to be a maxBackpressure which will force close everything over that limit */
|
||||||
|
size_t maxBackpressure = 16 * 1024;
|
||||||
|
|
||||||
/* Each websocket context has a topic tree for pub/sub */
|
/* Each websocket context has a topic tree for pub/sub */
|
||||||
TopicTree topicTree;
|
TopicTree topicTree;
|
||||||
|
|
||||||
WebSocketContextData() : topicTree([](Subscriber *s, std::string_view data) -> int {
|
WebSocketContextData() : topicTree([](Subscriber *s, std::string_view data) -> int {
|
||||||
//std::cout << "Skickar data: " << data << " på sub: " << s << std::endl;
|
/* We rely on writing to regular asyncSockets */
|
||||||
|
|
||||||
|
|
||||||
auto *asyncSocket = (AsyncSocket<SSL> *) s->user;
|
auto *asyncSocket = (AsyncSocket<SSL> *) s->user;
|
||||||
|
|
||||||
asyncSocket->write(data.data(), data.length());
|
asyncSocket->write(data.data(), data.length());
|
||||||
|
|
||||||
|
/* Reserved, unused */
|
||||||
return 0;
|
return 0;
|
||||||
}) {
|
}) {
|
||||||
|
/* bug: This should probably happen in both post and pre, esp for libuv */
|
||||||
Loop::get()->addPostHandler([this](Loop *loop) {
|
Loop::get()->addPostHandler([this](Loop *loop) {
|
||||||
|
/* Commit pub/sub batches every loop iteration */
|
||||||
topicTree.drain();
|
topicTree.drain();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user