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的更多相关文章

  1. [翻译]Writing Custom Wizards 编写自定义的向导

    Writing Custom Wizards  编写自定义的向导   You can extend FastReport's functionality with the help of custom ...

  2. [翻译]Writing Custom DB Engines 编写定制的DB引擎

    Writing Custom DB Engines  编写定制的DB引擎   FastReport can build reports not only with data sourced from ...

  3. [翻译]Writing Custom Common Controls 编写自定义控件

    摘要:介绍如何编写自定义的控件,用在报表的窗体上(如Edit,Button等)   Writing Custom Common Controls 编写自定义控件 FastReport contains ...

  4. [翻译]Writing Custom Report Components 编写自定义报表组件

    摘要:简单介绍了如何编写一个FastReport的组件,并且注册到FastReport中使用.   Writing Custom Report Components 编写自定义报表组件 FastRep ...

  5. Apache HttpComponents Custom protocol interceptors通过拦截器自定义压缩

    /* * ==================================================================== * Licensed to the Apache S ...

  6. [转]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 ...

  7. Writing Custom Providers

    转自:https://www.terraform.io/docs/extend/writing-custom-providers.html 很详细,做为一个记录 In Terraform, a Pro ...

  8. [译]Writing Custom Middleware in ASP.NET Core 1.0

    原文: https://www.exceptionnotfound.net/writing-custom-middleware-in-asp-net-core-1-0/ Middleware是ASP. ...

  9. Writing and playing with custom Terraform Providers

    转自:https://petersouter.xyz/writing-and-playing-with-custom-terraform-providers/ I’ve been digging de ...

随机推荐

  1. 实验楼-Git实战教程

    实验1-git介绍 1.版本控制系统: 1)集中式版本控制系统:版本库是集中存放在中央服务器的,工作时需要先从中央服务器取得最新的版本,然后工作完成后把自己的修订推送给中央服务器.这类系统都有一个单一 ...

  2. 前端工程化系列[04]-Grunt构建工具的使用进阶

    在前端工程化系列[02]-Grunt构建工具的基本使用和前端工程化系列[03]-Grunt构建工具的运转机制这两篇文章中,我们对Grunt以及Grunt插件的使用已经有了初步的认识,并探讨了Grunt ...

  3. 07、RDD持久化

    为了避免多次计算同一个RDD(如上面的同一result RDD就调用了两次Action操作),可以让Spark对数据进行持久化.当我们让Spark持久化存储一个RDD时,计算出RDD的节点会分别保存它 ...

  4. C# System.Threading.Timer

    提供以指定的时间间隔对线程池线程执行方法的机制 using System; using System.Threading; class TimerExample { static void Main( ...

  5. Exception thrown on Scheduler.Worker thread. Add `onError` handling

    <html> <head></head> <body> java.lang.IllegalStateException: Exception throw ...

  6. Spring Cloud Netflix Zuul 重试会自动跳过经常超时的服务实例的简单说明和分析

    在使用E版本的Spring Cloud Netflix Zuul内置的Ribbon重试功能时,发现Ribbon有一个非常有用的特性: 如果某个服务的某个实例经常需要重试,Ribbon则会在自己维护的一 ...

  7. BAT 删除隐藏文件

    删除文件 del命令参数说明/F   强制删除文件./S      从所有子目录删除指定文件./Q      安静模式.删除全局通配符时,不要求确认./A      根据属性选择要删除的文件. 删除指 ...

  8. 图 总结 AI

    这个哥们 总结的太好了, 我忍不住就“偷”过来了 人工智能是一门极富挑战性的科学,从事这项工作的人必须懂得计算机知识,心理学和哲学. 人工智能是包括十分广泛的科学,它由不同的领域组成,如机器学习,计算 ...

  9. PHP-问题处理验证码无法显示出来

    1.问题 今天重新安装了ubuntu,PHP,MySQL,Apache,到测试CMS项目时发生一个错误: 验证码无法显示出来. 2.解决: 2.1 使用 phpinfo检查: phpinfo(); 在 ...

  10. 你可能不知道的IDEA高级调试技巧

    一.条件断点 循环中经常用到这个技巧,比如:遍历1个大List的过程中,想让断点停在某个特定值. 参考上图,在断点的位置,右击断点旁边的小红点,会出来一个界面,在Condition这里填入断点条件即可 ...