nanomsg
Nanomsg是一个Socket的通讯库,使用C语言编写实现的,这样就可以适用于多种操作系统,而且几乎不需要什么依赖,可扩展并且能易于使用。Nanomsg提供了几种常见的通信模式 ( 也称为“可扩展性协议” ) 是构建分布式系统的基本框架。 通过组合它们,可以创建广 泛的分布式应用程序。
目录结构
src
├── aio 异步IO,包含实现异步操作所需的上下文管理、有限状态机、事件轮询器、定时器、统一socket抽象、以及工作线程等组件
├── core 核心功能,包含端点管理、全局状态、管道管理、套接字实现等
├── devices 设备功能,用于桥接不同的通信模式,实现消息的中继和路由
├── protocols 六种通信协议
│ ├── bus 总线模式
│ ├── pair 配对模式
│ ├── pipeline 管道模式
│ ├── pubsub 发布订阅模式
│ ├── reqrep 请求响应模式
│ ├── survey 调查模式
│ └── utils 实现协议层共用功能
├── transports 四种传输层协议
│ ├── inproc 进程内通信,基于内存共享实现
│ ├── ipc 进程间通信,基于域套接字(Linux)或命名管道(Windows)
│ ├── tcp TCP协议
│ ├── ws Websocket协议
│ └── utils 实现传输层共用功能
├── utils 系统调用相关封装
├── nn.h 公共API定义
└── protocol.h 协议接口定义
通信协议
Nanomsg提供了六种可扩展协议,每种模式都有特定的消息路由策略,所有协议都是可插拔的。
-
PAIR (配对模式):
最简单的一对一通信模式。每个端点只能与一个对等端点建立连接,适用于简单的点对点通信场景。

-
BUS (总线模式):
多对多的网状通信模式。每个节点可以向总线上的所有其他节点广播消息,消息会被传递给除发送者外的所有连接节点。适用于去中心化的网络拓扑,如分布式系统中的服务发现。

-
REQREP (请求-响应模式):
实现同步请求-响应模式。请求者发送请求后必须等待响应,响应者收到请求后必须回复响应。支持自动重试和负载均衡,适用于RPC、分布式计算等需要双向通信的场景。

-
PIPELINE (管道模式):
实现单向数据流。由PUSH(推送)和PULL(拉取)两种节点组成,数据只能从PUSH流向PULL。支持负载均衡,适用于数据流处理、任务分发等场景。

-
PUBSUB (发布-订阅模式):
实现一对多的消息分发。发布者(PUB)发送的消息会被传递给所有订阅者(SUB)。支持基于主题的过滤,订阅者可以选择只接收感兴趣的主题。适用于数据广播、事件通知等场景。

-
SURVEY (调查模式):
实现一对多的请求-响应模式。调查者(SURVEYOR)发送调查请求,响应者(RESPONDENT)在超时时间内可以回复。适用于服务发现、状态收集等需要汇总多个节点响应的场景。

| 协议 | 通信方向 | 连接模式 | 消息模式 | 典型应用场景 |
|---|---|---|---|---|
| PAIR | 双向 | 一对一 | 点对点 | 专用连接、设备控制 |
| BUS | 双向 | 多对多 | 广播 | 服务发现、状态同步 |
| REQREP | 双向 | 多对多 | 调查 | RPC、查询服务 |
| PIPELINE | 单向 | 多对多 | 负载均衡 | 任务分发、数据流处理 |
| PUBSUB | 单向 | 一对多 | 广播 | 数据分发、事件通知 |
| SURVEY | 双向 | 一对多 | 聚合 | 系统监控、配置收集 |
传输方式
Nanomsg 提供了四种传输方式,处理底层的网络通信细节,可以独立扩展新的传输方式:
- INPROC (进程内通信):
INPROC 是用于同一进程内的线程之间通信的传输机制。它通过共享内存实现,速度最快,因为不需要进行上下文切换或网络操作。适用于需要在同一应用程序的不同组件之间快速传递消息的场景。 - IPC (进程间通信):
IPC 使用域套接字(在 Linux 上)或命名管道(在 Windows 上)来实现不同进程之间的通信。这种传输机制适用于需要在同一台机器上的不同进程之间传递消息的场景。 - TCP (TCP网络传输):
TCP 是一种可靠的、基于连接的协议,适用于需要在不同机器之间进行可靠数据传输的场景。Nanomsg 使用 TCP 来实现跨网络的进程间通信。 - WS (WebSocket传输):
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,通常用于浏览器与服务器之间的通信。Nanomsg 的 WebSocket 传输机制适用于需要与 Web 应用程序进行通信的场景。
| 传输方式 | 描述 | 使用场景 |
|---|---|---|
| inproc | 进程内通信 | 同一进程中的线程间通信,零拷贝优化 |
| ipc | 进程间通信 | 同一主机上的不同进程间通信 |
| tcp | TCP网络传输 | 网络中的跨主机通信,广泛支持 |
| ws | WebSocket传输 | 浏览器与服务器通信,跨网络边界 |
结构解析
在 Nanomsg 的目录结构中,每个子目录都负责特定的功能模块:
-
core:包含 Nanomsg 的核心功能,如端点管理、全局状态管理、管道管理等。这是 Nanomsg 的基础模块,负责管理通信的基本元素。
-
aio:提供异步 I/O 操作的支持,包括事件轮询器、定时器、套接字管理等。异步 I/O 是实现高性能网络通信的关键。
-
protocols:实现了多种消息传递协议,如发布/订阅、请求/响应等,提供了不同的通信模式以满足应用需求。
-
transports:提供了不同的传输机制,如TCP、IPC和WebSocket等,支持在不同网络环境中进行高效的数据传输。
-
devices:实现设备功能,用于在不同的通信模式之间进行桥接,支持消息的中继和路由。
-
utils:提供通用的实用工具函数和类,供其他模块使用。
核心组件类图

utils
utils
├── fd.h 跨平台定义fd
├── win.h 导入win平台头文件
├── attr.h 定义GNU编译属性
├── cont.h 宏定义从成员推断结构体地址
├── fast.h GNU分支优化
|
├── alloc 封装内存分配,提供宏NN_ALLOC_MONITOR用于监视内存分配情况
├── atomic 封装原子变量uint32_t
├── chunk 缓冲区
├── chunkref 缓冲区引用
├── clock 提供毫秒时间戳
├── closefd 关闭文件描述符
├── condvar 封装条件变量
├── efd eventfd的接口,支持下面四种实现
├── efd_eventfd
├── efd_pipe
├── efd_socketpair
├── efd_win win下使用socket+tcp实现
├── err 错误处理,提供堆栈信息打印以及平台间适配
├── hash 哈希表实现
├── list 链表实现
├── queue 队列实现
├── msg 消息封装
├── mutex 封装mutex
├── once 封装once
├── random 随机数
├── sem 信号量
├── sleep 休眠
├── stopwatch 时间测量,微秒级别
├── strcasecmp 字符串比较
├── strcasestr
├── strncasecmp
├── thread 线程
├── thread_posix
├── thread_win
└── wire 字节序转换
-
alloc:
alloc 模块封装了内存分配的功能,并提供了宏 NN_ALLOC_MONITOR 用于监视内存分配情况。在内存分配的时候会打印内存分配的模块以及大小,能够帮助开发者追踪内存泄漏和不当使用。pfd = nn_alloc (sizeof (struct pollfd) * nfds * 2, "pollset"); printf ("Allocating %s (%zu bytes)\n", name/* pollset */, size/* sizeof (struct pollfd) * nfds * 2 */); printf ("Current memory usage: %zu bytes in %zu blocks\n", nn_alloc_bytes, nn_alloc_blocks); -
cont:
-
从成员地址反推结构体指针地址
#define nn_cont(ptr, type, member) \ (ptr ? ((type*) (((char*) ptr) - offsetof(type, member))) : NULL) timer = nn_cont (self, struct nn_timer, fsm); // self是nn_timer的成员fsm,返回timer的地址 -
chunk 和 chunkref:
chunk 模块提供了缓冲区的管理,而 chunkref 则允许对缓冲区的引用管理。这种设计使得在处理消息时能够高效地管理内存,避免不必要的复制。
devices
示例代码
test/device.c
nanomsg的设备支持以下几种模式:
-
双向设备 (Bidirectional Device):
-
通过
NN_CHECK_ALLOW_BIDIRECTIONAL检查,设备可以在两个套接字之间双向传递消息。 -
代码片段:
if (device->required_checks & NN_CHECK_ALLOW_BIDIRECTIONAL) { if (s1rcv != NN_BAD_FD && s1snd != NN_BAD_FD && s2rcv != NN_BAD_FD && s2snd != NN_BAD_FD) return nn_device_twoway (device, s1, s2); }
-
-
单向设备 (Unidirectional Device):
-
通过
NN_CHECK_ALLOW_UNIDIRECTIONAL检查,设备可以在一个方向上传递消息。 -
代码片段:
if (device->required_checks & NN_CHECK_ALLOW_UNIDIRECTIONAL) { /* Single-directional device passing messages from s1 to s2. */ if (s1rcv != NN_BAD_FD && s1snd == NN_BAD_FD && s2rcv == NN_BAD_FD && s2snd != NN_BAD_FD) return nn_device_oneway (device, s1, s2); /* Single-directional device passing messages from s2 to s1. */ if (s1rcv == NN_BAD_FD && s1snd != NN_BAD_FD && s2rcv != NN_BAD_FD && s2snd == NN_BAD_FD) return nn_device_oneway (device, s2, s1); }
-
-
环回设备 (Loopback Device):
-
通过
NN_CHECK_ALLOW_LOOPBACK检查,设备可以处理单个套接字的环回情况。 -
代码片段:
if (device->required_checks & NN_CHECK_ALLOW_LOOPBACK) { if (s2 < 0) return nn_device_loopback (device, s1); if (s1 < 0) return nn_device_loopback (device, s2); }
-
使用nanomsg的设备(device)时,有几个重要的注意事项:
-
套接字类型:
-
确保使用的套接字类型符合设备的要求。例如,如果设备要求原始套接字(raw sockets),则必须使用
AF_SP_RAW类型的套接字。 -
代码片段:
if (device->required_checks & NN_CHECK_REQUIRE_RAW_SOCKETS) { // 检查套接字是否为原始套接字 }
-
-
协议一致性:
-
确保两个套接字属于同一协议族。不同协议的套接字不能一起使用。
-
代码片段:
if (device->required_checks & NN_CHECK_SAME_PROTOCOL_FAMILY) { // 检查套接字是否属于同一协议族 }
-
-
方向性检查:
-
在使用设备时,确保套接字的方向性符合要求。例如,某些设备可能要求一个套接字用于接收,另一个用于发送。
-
代码片段:
if (device->required_checks & NN_CHECK_SOCKET_DIRECTIONALITY) { // 检查套接字的方向性 }
-
aio
aio
├── ctx 上下文
├── fsm 状态机
├── poller IO多路分离器,支持下面三种实现
├── poller_epoll
├── poller_kqueue
├── poller_poll
├── pool 线程池
├── timer
├── timerset 定时器集,管理各种事务
├── usock
├── usock_posix
├── usock_win
├── worker 工作线程
├── worker_posix
└── worker_win
状态机和异步处理框架
nanomsg中几乎所有重要组件都是基于状态机实现的
主要包括这几个核心组件:
- nn_fsm: 状态机基类,定义了状态转换和事件处理机制
- nn_fsm_event: 状态机事件,用于组件间通信
- nn_ctx: 上下文,管理事件队列和工作线程
- nn_pool: 线程池,包含工作线程
- nn_worker: 工作线程,负责事件处理
- nn_ep: 端点,负责管理连接生命周期
核心组件类图

异步处理流程图

创建端点并连接示例

关键协作机制
-
事件传递流程:
- 组件通过nn_fsm_raise触发事件
- 事件被添加到目标组件上下文的事件队列
- 工作线程从队列获取事件并处理
-
上下文与状态机的绑定:
- 每个状态机都绑定到一个上下文
- 子状态机继承父状态机的上下文
- 上下文提供了事件队列和线程安全机制
-
多线程协作:
- 状态机不关心实际执行的线程
- 上下文确保同一时间只有一个线程访问状态机
- 工作线程负责实际的事件处理
protocols
protocols
├── bus
│ ├── bus
│ └── xbus
├── pair
│ ├── pair
│ └── xpair
├── pipeline
│ ├── pull
│ ├── push
│ ├── xpull
│ └── xpush
├── pubsub
│ ├── pub
│ ├── sub
│ ├── trie
│ ├── xpub
│ └── xsub
├── reqrep
│ ├── rep
│ ├── task
│ ├── xrep
│ └── xreq
├── survey
│ ├── respondent
│ ├── surveyor
│ ├── xrespondent
│ └── xsurveyor
└── utils 实现协议层共用功能:优先级列表、分发器、互斥管理、公平队列、负载均衡
├── dist
├── excl
├── fq
├── lb
└── priolist
protocol架构设计
nanomsg的协议实现遵循以下架构设计:
-
nn_socktype:描述协议的类型和操作 (创建nn_sockbase)
struct nn_socktype { int domain; // 协议域(通常为AF_SP) int protocol; // 协议ID int flags; // 标志(如NORECV, NOSEND) int (*create)(void *hint, struct nn_sockbase **sockbase); // 创建函数 int (*ispeer)(int socktype); // 检查对等性 }; -
nn_sockbase:实现具体协议功能(比如req/xreq)
struct nn_sockbase { const struct nn_sockbase_vfptr *vfptr; struct nn_sock *sock; }; -
nn_sockbase_vfptr:定义协议操作的虚函数表(req/xreq的发送函数)
struct nn_sockbase_vfptr { void (*stop) (struct nn_sockbase *self); void (*destroy) (struct nn_sockbase *self); // 管道管理 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); // 状态和消息操作 int (*events) (struct nn_sockbase *self); int (*send) (struct nn_sockbase *self, struct nn_msg *msg); int (*recv) (struct nn_sockbase *self, struct nn_msg *msg); // 选项管理 int (*setopt) (struct nn_sockbase *self, int level, int option, ...); int (*getopt) (struct nn_sockbase *self, int level, int option, ...); };
示例代码
async_demo.c
transports
transports
├── inproc
│ ├── binproc
│ ├── cinproc
│ ├── inproc
│ ├── ins 进程内名称服务实现
│ ├── msgqueue 消息队列实现
│ └── sinproc
├── ipc
│ ├── aipc
│ ├── bipc
│ ├── cipc
│ ├── ipc
│ └── sipc
├── tcp
│ ├── atcp
│ ├── btcp
│ ├── ctcp
│ ├── stcp
│ └── tcp
├── utils 实现传输层共用功能
│ ├── backoff 退避算法
│ ├── base64 base64编码
│ ├── dns dns解析
│ ├── dns_getaddrinfo_a
│ ├── dns_getaddrinfo
│ ├── iface 网络接口
│ ├── literal url处理
│ ├── port 端口管理
│ └── streamhdr 流协议头部
└── ws
├── aws
├── bws
├── cws
├── sha1 SHA1哈希实现(用于WebSocket握手)
├── sws
├── ws
└── ws_handshake WebSocket握手实现
nn_transport接口设计
struct nn_transport {
// 传输方式名称
const char *name;
// 传输ID
int id;
// 全局初始化与终止函数(可选)
void (*init) (void);
void (*term) (void);
// 创建绑定与连接端点的函数
int (*bind) (struct nn_ep *ep);
int (*connect) (struct nn_ep *ep);
// 创建传输专用选项集(可选)
struct nn_optset *(*optset) (void);
};
端点使用传输层的功能:
- Socket创建Endpoint时会传入具体的Transport实例
- Endpoint保存这个Transport引用
- Endpoint调用Transport的bind或connect方法创建具体的传输实例
- Transport负责具体的底层通信细节,并通过nn_ep_tran_setup设置回调(主要是跟绑定端点和连接端点有关)
传输方式实现结构
每种传输方式都遵循相似的结构:绑定端点(b)、连接端点©、接受端点(a)、会话(s)和主接口
TCP传输实现示例
以TCP传输为例,TCP传输的实现分为几个关键组件:
tcp.c定义了nn_transport接口的实现,包括绑定、连接函数和选项集。btcp(Bound TCP)处理监听端点,负责创建监听套接字,接受新连接,并为每个新连接创建atcp对象。ctcp(Connected TCP)处理连接端点,负责解析地址,创建套接字,连接到远程地址,并处理连接失败的重试逻辑。atcp(Accepted TCP)处理服务器接受的连接,创建和管理数据流。stcp(Stream TCP)处理活动连接的数据传输,实现了实际的发送和接收功能
1万+

被折叠的 条评论
为什么被折叠?



