Writing custom protocol for nanomsg
http://vitiy.info/writing-custom-protocol-for-nanomsg/
nanomsg is next version of ZeroMQ lib, providing smart cross-platform sockets for implementation of distributed architectures. Here you can find basic examples of included protocols (communication patterns). Lib is simple (written in pure C) and does not have any dependencies like boost. And as this is at least 3rd iteration from same author you can expect some quality/performance here.
This is kind of solution for the hell of writing of your own serious socket server. If you already had such experience you should understand the range of problems which are not so obvious at start. But here we expect to skip all such problems and go straight to processing messages. Lib handles automatic reconnection in case of link disconnects, nonblocking receiving/sending, sockets which can handle large set of clients, etc. All this seems like perfect solution for server-side inner transport of fast distributed architectures.
But i also want to try it outside. The basic communication patterns (PAIR, BUS, REQREP, PUBSUB, PIPELINE, SURVEY) may fit large set of inner server transport schemes, but there are some minor limits in current implementation for client side application. I mean limits of protocols, not the lib itself.
Unfortunately, current version of PUBSUB protocol does filtering on subscriber side. So ‘subscribing clients’ will receive all message flow and this is unbearable for me.
BUS protocol requires full-linked scheme:
I expect BUS-like protocol to work in more sparse conditions.
As nanomsg is open-source lib under nice licence (MIT/X11 license) – first thought was to extend some of existing protocols to meet my needs.
Why new protocol?
As i wanted to try these smart sockets for external clients, to meet today’s reality i’m assuming each client has set of devices, which are connected simultaneously to some network service.
At first i aimed to create some complex routing protocol, but than came up with more simple approach: I want to create custom protocol as fusion of BUS and SUB/PUB protocols (here i refer it as SUBBUS).
Scheme:
Black lines are initial connections. Coloured lines are messages. This scheme contains 2 clients Bob and John. John has 2 devices and Bob is geek, so he has 4 devices simultaneously connected to server node. Each message from client device goes to other devices of same client. You can look at this scheme as two BUS protocols separated by subscription.
This gives ability to perform instant cloud synchronisation, simultaneous operation from multiple devices and other various fun stuff.
Possible inner structure (there can other ways):
- Each node has the list of subscriptions (socket option as list of strings) i.e. /users/john/ or /chats/chat15/.
- Subscription filtering is done on sending side (This is important if you have large number of clients – each of them don’t have to receive all messages. Not only for saving bandwidth but also for security reasons.) So client should somehow send his subscription list to server (subscription forwarding). In case of reconnect this information should be also resent again. While subscriptions were not sent client should receive nothing.
- Each message should contain routing prefix (header) i.e. /users/john/ or /chats/chat15/
- Each node should have tree of connected client subscriptions which contains pipe lists as leafs. Sending operation uses this tree to send to subscribed range of clients.
- Each message from client node should be transmitted to other nodes within same subscription (forwarding). This is done before server side processing and aimed to speed up message propagation between devices. Some optional filters can be added here.
- [Optional] SSL-like encryption for each pipe
- All this stuff should be as simple as possible
Its not too complicated to start writing your own protocol for nanomsg. The only problem is that lib is written in pure C – so you must be a bit ready for it. Go to src/protocols folder. It contains all protocols sources you can explore. Mostly they simply implement the list of given methods, which are described inside src/protocol.h:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
/* To be implemented by individual socket types. */
struct nn_sockbase_vfptr {
/* Ask socket to stop. */
void (*stop) (struct nn_sockbase *self);
/* Deallocate the socket. */
void (*destroy) (struct nn_sockbase *self);
/* Management of pipes. 'add' registers a new pipe. The pipe cannot be used
to send to or to be received from at the moment. 'rm' unregisters the
pipe. The pipe should not be used after this call as it may already be
deallocated. 'in' informs the socket that pipe is readable. 'out'
informs it that it is writable. */
int (*add) (struct nn_sockbase *self, struct nn_pipe *pipe);
void (*rm) (struct nn_sockbase *self, struct nn_pipe *pipe);
void (*in) (struct nn_sockbase *self, struct nn_pipe *pipe);
void (*out) (struct nn_sockbase *self, struct nn_pipe *pipe);
/* Return any combination of event flags defined above, thus specifying
whether the socket should be readable, writable, both or none. */
int (*events) (struct nn_sockbase *self);
/* Send a message to the socket. Returns -EAGAIN if it cannot be done at
the moment or zero in case of success. */
int (*send) (struct nn_sockbase *self, struct nn_msg *msg);
/* Receive a message from the socket. Returns -EAGAIN if it cannot be done
at the moment or zero in case of success. */
int (*recv) (struct nn_sockbase *self, struct nn_msg *msg);
/* Set a protocol specific option. */
int (*setopt) (struct nn_sockbase *self, int level, int option,
const void *optval, size_t optvallen);
/* Retrieve a protocol specific option. */
int (*getopt) (struct nn_sockbase *self, int level, int option,
void *optval, size_t *optvallen);
};
|
So you can just clone some protocol as base foundation for your own – i took bus folder and cloned it to subbus. I renamed everything inside from ‘bus’ to ‘subbus’ using find/replace. In root src folder there is bus.h file which contains only list of consts for protocol access. You also need to clone it under your new protocol name (subbus.h in my case). Next steps are to add new protocol to makefile and socket types list.
Add to makefile.am:
1
2
3
4
5
6
7
8
9
|
NANOMSG_PROTOCOLS = \
$(PROTOCOLS_BUS) \
$(PROTOCOLS_SUBBUS) \ .....
PROTOCOLS_SUBBUS = \
src/protocols/subbus/subbus.h \
src/protocols/subbus/subbus.c \
src/protocols/subbus/xsubbus.h \
src/protocols/subbus/xsubbus.c
|
Add protocol to /core/symbol.c
1
2
3
4
|
{NN_BUS, "NN_BUS", NN_NS_PROTOCOL,
NN_TYPE_NONE, NN_UNIT_NONE},
{NN_SUBBUS, "NN_SUBBUS", NN_NS_PROTOCOL,
NN_TYPE_NONE, NN_UNIT_NONE},
|
Add protocol’s socket types into supported list inside /core/global.c (don’t forget includes):
1
2
3
4
5
6
7
|
/* Plug in individual socktypes. */
...
nn_global_add_socktype (nn_bus_socktype);
nn_global_add_socktype (nn_xbus_socktype);
nn_global_add_socktype (nn_subbus_socktype);
nn_global_add_socktype (nn_xsubbus_socktype);
|
After that i grabbed one of examples for bus protocol from here and changed socket creation part:
1
2
3
4
5
6
7
8
9
10
11
12
|
#include "../nanomsg/src/subbus.h"
int node (const int argc, const char **argv)
{
int sock = nn_socket (AF_SP, NN_SUBBUS);
if (sock < 0)
{
printf ("nn_socket failed with error code %d\n", nn_errno ());
if (errno == EINVAL) printf("%s\n", "Unknown protocol");
}
...
|
After that sample should compile and work. If you failed to add your protocol copy to socket types list you will get Unknown protocol error.
Here is complete Dockerfile i use to build&run simple test. It gets latest nanomsg from github, modifies sources to include new protocol, copies protocol source from host, builds the lib and protocol test.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
|
# THIS DOCKERFILE COMPILES Custom Nanomsg protocol + sample under Ubuntu
FROM ubuntu
MAINTAINER Victor Laskin "victor.laskin@gmail.com"
# Install compilation tools
RUN apt-get update && apt-get install -y \
automake \
build-essential \
wget \
p7zip-full \
bash \
curl \
git \
sed \
libtool
# Get latest Nanomsg build from github
RUN mkdir /nanomsg && cd nanomsg
WORKDIR /nanomsg
RUN git clone https://github.com/nanomsg/nanomsg.git && ls
# Modify nanomsg files to register new protocol
RUN cd nanomsg && sed -i '/include "..\/bus.h"/a #include "..\/subbus.h"' src/core/symbol.c && \
sed -i '/"NN_BUS", NN_NS_PROTOCOL,/a NN_TYPE_NONE, NN_UNIT_NONE}, \n\
{NN_SUBBUS, "NN_SUBBUS", NN_NS_PROTOCOL,' src/core/symbol.c && \
cat src/core/symbol.c && \
sed -i '/#include "..\/protocols\/bus\/xbus.h"/a #include "..\/protocols\/subbus\/subbus.h" \n\#include "..\/protocols\/subbus\/xsubbus.h"' src/core/global.c && \
sed -i '/nn_global_add_socktype (nn_xbus_socktype);/a nn_global_add_socktype (nn_subbus_socktype); \n\
nn_global_add_socktype (nn_xsubbus_socktype);' src/core/global.c && \
cat src/core/global.c | grep nn_global_add_socktype
# Modify Makefile.am
RUN cd nanomsg && sed -i '/xbus.c/a \\n\
PROTOCOLS_SUBBUS = \\\n\
src/protocols/subbus/subbus.h \\\n\
src/protocols/subbus/subbus.c \\\n\
src/protocols/subbus/xsubbus.h \\\n\
src/protocols/subbus/xsubbus.c \n\
\\
' Makefile.am && \
sed -i '/$(PROTOCOLS_BUS)/a $(PROTOCOLS_SUBBUS) \\\
' Makefile.am && cat Makefile.am
# This is temporal fix - DISABLE STATS
RUN sed -i '/nn_global_submit_statistics ();/i if (0)' nanomsg/src/core/global.c
# Get custom protocol source (copy from host)
RUN mkdir nanomsg/src/protocols/subbus
COPY subbus.h /nanomsg/nanomsg/src/
COPY subbus/*.c /nanomsg/nanomsg/src/protocols/subbus/
COPY subbus/*.h /nanomsg/nanomsg/src/protocols/subbus/
# Build nanomsg lib
RUN cd nanomsg && ./autogen.sh && ./configure && make && ls .libs
# Get and build custom protocol test
RUN mkdir test
COPY testsubbus.c /nanomsg/test/
COPY test.sh /nanomsg/test/
RUN cd test && ls && gcc -pthread testsubbus.c ../nanomsg/.libs/libnanomsg.a -o testbus -lanl && ls
# Set port and entry point
EXPOSE 1234 1235 1236 1237 1238 1239 1240
ENTRYPOINT cd /nanomsg/test/ && ./test.sh
|
Note: the lib is still beta (0.5-beta, released on November 14th, 2014) so you could expect something yet not polished there. Inside script you could find the line which disables statistics as it has some blocking bug at the moment but i expect it to be fixed very soon as the fix was pulled already.
Docker is optional way to build this, of course, and you can modify this Dockerfile to simple client script. Don’t forget to change the name of your protocol.
Modifications i made
I will not paste here the cuts of source code as it will make the post too messy. This is plain old C so even simple things tend to be a bit longer there. So i will note some main steps of my implementation. Keep in mind that thats only my approach and everything can be done another way.
I modified nn_xsubbus_setopt to set subscriptions (i use linked list to store the list of local subscriptions).
I have two trees to speed up all process of communication routing. First tree contains descriptions of client subscriptions by pipe id (nn_pipe*). Also it contains the flag if this node’s subscriptions were sent to this pipe for first time. To make this tree more balanced i use some hash of pointer to pipe as binary tree key.
This tree is used in nn_xsubbus_add / nn_xsubbus_rm / nn_xsubbus_out functions to synchronise subscription lists. nn_xsubbus_add is called when new pipe is connected and there we add new leaf into the tree. nn_xsubbus_out tells that pipe is writable so we can send our list of subscriptions to other side (if we have not already done it). nn_xsubbus_rm – pipe was removed.
Second tree is used for main sending operation and gives the list of pipes by subscription string key. As starting point i took triple tree where each node contains actual list of connected pipes. nn_xsubbus_send method splits header from each message and sends it to corresponding tree part.
When new message arrives inside nn_xsubbus_recv there is check of header, and if it starts from special mark of the list of subscriptions – we add this list into the second tree. If message is ‘normal’ there is sending to other pipes of same subscription (message forwarding as BUS protocol wants).
Note, that trees should work as persistent trees in multithread environment. I prefer some non locking structures here. Current implementation does not clean up chains of disconnected leafs (just removes the pipes) to achieve this simple way. Some tree rebalancing algorithm would be nice to add in future.
As test i slightly modified bus test sample to set subscription from argv[2] as socket option and prepend message by current subscription.
1
2
3
4
5
6
7
|
./testbus node0 / tcp://127.0.0.1:1234 & node0=$!
./testbus node1 /USER/JOHN/ tcp://127.0.0.1:1235 tcp://127.0.0.1:1234 & node1=$!
./testbus node2 /USER/BOB/ tcp://127.0.0.1:1236 tcp://127.0.0.1:1234 & node2=$!
./testbus node3 /USER/JOHN/ tcp://127.0.0.1:1237 tcp://127.0.0.1:1234 & node3=$!
./testbus node4 /USER/BOB/ tcp://127.0.0.1:1238 tcp://127.0.0.1:1234 & node4=$!
./testbus node5 /USER/BOB/ tcp://127.0.0.1:1239 tcp://127.0.0.1:1234 & node5=$!
./testbus node6 /USER/BOB/ tcp://127.0.0.1:1240 tcp://127.0.0.1:1234 & node6=$!
|
Here is the part of test output (for Bob):
1
2
3
4
5
6
7
8
9
10
11
12
|
node5: RECEIVED '/USER/BOB/=node2 18' 20 FROM BUS
node4: RECEIVED '/USER/BOB/=node2 18' 20 FROM BUS
node6: RECEIVED '/USER/BOB/=node2 18' 20 FROM BUS
node2: RECEIVED '/USER/BOB/=node5 18' 20 FROM BUS
node5: RECEIVED '/USER/BOB/=node6 18' 20 FROM BUS
node4: RECEIVED '/USER/BOB/=node5 18' 20 FROM BUS
node6: RECEIVED '/USER/BOB/=node5 18' 20 FROM BUS
node2: RECEIVED '/USER/BOB/=node6 18' 20 FROM BUS
node5: RECEIVED '/USER/BOB/=node4 18' 20 FROM BUS
node2: RECEIVED '/USER/BOB/=node4 18' 20 FROM BUS
node4: RECEIVED '/USER/BOB/=node6 18' 20 FROM BUS
node6: RECEIVED '/USER/BOB/=node4 18' 20 FROM BUS
|
As you can see there is bus between node2, node4, node5, node6.
I will post the sources here after i perform some tests with large set of clients, some stress tests and so on.
Writing custom protocol for nanomsg的更多相关文章
- [翻译]Writing Custom Wizards 编写自定义的向导
Writing Custom Wizards 编写自定义的向导 You can extend FastReport's functionality with the help of custom ...
- [翻译]Writing Custom DB Engines 编写定制的DB引擎
Writing Custom DB Engines 编写定制的DB引擎 FastReport can build reports not only with data sourced from ...
- [翻译]Writing Custom Common Controls 编写自定义控件
摘要:介绍如何编写自定义的控件,用在报表的窗体上(如Edit,Button等) Writing Custom Common Controls 编写自定义控件 FastReport contains ...
- [翻译]Writing Custom Report Components 编写自定义报表组件
摘要:简单介绍了如何编写一个FastReport的组件,并且注册到FastReport中使用. Writing Custom Report Components 编写自定义报表组件 FastRep ...
- Apache HttpComponents Custom protocol interceptors通过拦截器自定义压缩
/* * ==================================================================== * Licensed to the Apache S ...
- [转]Writing Custom Middleware in ASP.NET Core 1.0
本文转自:https://www.exceptionnotfound.net/writing-custom-middleware-in-asp-net-core-1-0/ One of the new ...
- Writing Custom Providers
转自:https://www.terraform.io/docs/extend/writing-custom-providers.html 很详细,做为一个记录 In Terraform, a Pro ...
- [译]Writing Custom Middleware in ASP.NET Core 1.0
原文: https://www.exceptionnotfound.net/writing-custom-middleware-in-asp-net-core-1-0/ Middleware是ASP. ...
- Writing and playing with custom Terraform Providers
转自:https://petersouter.xyz/writing-and-playing-with-custom-terraform-providers/ I’ve been digging de ...
随机推荐
- parcel 中小型项目打包工具
"0配置"打包器(bundler)Parcel Parcel官网(有中文文档) webpack 要有大量的配置,这样带来的成本就是复杂性--与此相对的,Parcel 带来了简洁性. ...
- history.go(-1)和History.back()的区别
简单的说就是:go(-1): 返回上一页,原页面表单中的内容会丢失:back(-1): 返回上一页,原页表表单中的内容会保留,一般还是back(-1)用的多
- Redis的快照持久化-RDB与AOF
Redis持久化功能 Redis为了内部数据的安全考虑,会把本身的数据以文件形式保存到硬盘中一份,在服务器重启之后会自动把硬盘的数据恢复到内存(redis)的里边. 数据保存到硬盘的过程就称为“持久化 ...
- 【独家】硅谷创业公司在中国常跌的五个坑|禾赛科技CEO李一帆柏林亚太周主题演讲
[独家]硅谷创业公司在中国常跌的五个坑|禾赛科技CEO李一帆柏林亚太周主题演讲 李一帆 Xtecher特稿作者 关注 Xtecher推荐 演讲者:李一帆 翻译:晓娜 网址:www.xt ...
- KVM之CPU虚拟化
1.1 为什么要虚拟化CPU 虚拟化技术是指在x86的系统中,一个或以上的客操作系统(Guest Operating System,简称:Guest OS)在一个主操作系统(Host Operatin ...
- Docker搭建带有访问认证的私有仓库
2017年06月10日 17:30:26 阅读数:2581 以下步骤均为本人亲自踩坑,历经数次失败,最终搭建成功 一.环境信息: 操作系统:CentOS 7 Docker版本:1.12.5 (更高版本 ...
- Tomcat 9.0 安装配置
本文转自:http://blog.sina.com.cn/s/blog_15126e2170102w5o8.html 一.JDK的安装与配置 1.从官网下载jdk,注意是jdk不是jre.最好从官网下 ...
- grid - 网格项目层级
网格项目可以具有层级和堆栈,必要时可能通过z-index属性来指定. 1.在这个例子中,item1和item2的开始行都是1,item1列的开始是1,item2列的开始是2,并且它们都跨越两列.两个网 ...
- GIT里的一些名词
origin:他是一个特定远程仓库的别名,他不是一个仓库的属性. head:通常情况下可以将它与当前的分支等同.
- 微信小程序官方DEMO解读
我们在开始微信小程序开发的时候,对JS,HTML等前端知识一无所知,完完全全就是门外汉在尝试一个新的方向. 在下载好开发工具,微信就已经提供了一个DEMO例子: 从程序开发的角度来看这个陌生的目录结构 ...