缘起netcore框架下实现基于zmq的应用。

在.net framework时代,我们进行zmq开发由很多的选择,比较常用的有clrzmq4和NetMQ。 其中clrzmq是基于libzmq的Interop包装,

NetMQ是100%C#的zmq实现(基于AsyncIO组件)。以上两种组件我都有过应用,孰优孰劣各有千秋,本文就不详谈了。

回归正题,netcore下使用zmq首先也是想到引用上述组件,实践后发现clrzmq暂时没有基于netstandard或者netcore的支持,而NetMQ做的比较好,已经基于

netstandard1.3进行了支持。

一番折腾,搭程序,配环境。。。而后 dotnet run ,在windows下正常呈现了zmq的各项功能。

于是继续dotnet publlish,通过Wnscp拷贝到centos7下执行。立即报错,

挺意外的,本以为NetMQ已经基于netstandard进行了支持,也应该对跨平台进行支持。 可事实是AsyncIO的IOControl方法not supported on linux platform/

无奈,网上搜了会,也没找到任何关于netcore在linux下进行zmq的相关内容, 事实上也没有看到AsyncIO或者NetMQ有关于跨平台支持的说明。

既然现有方式行不通那就只好自己动手了,自己操刀通过Interop包装libzmq来实现跨平台的zmq应用吧!

首先看下libzmq的组件目录: 按x86和x64平台分为i386文件夹和amd64文件夹,且都包含windos下的dll组件和linux下的so组件

这挺难办了,要想做好还得考虑平台类型 和 操作系统类型,  还要想想 netcore里有没有相关API提供。

先是网上搜了圈,也极少有关于netcore进行平台判断相关内容。根据以往在framework下的经验,直接到https://apisof.net搜索相关关键字

:OSPlatform

非常不错,netcore已经提供了,同理搜索了 OSArchitecture 、DllImport 等都发现netcore1.1版本已经实现了相关api (说白了是netstandard已经实现了相关API)

准备就绪,直接开干,首先是平台相关信息判断,并加载目标组件 (具体方式请参照如下代码)

   class ZmqNative
{
private const string LibraryName = "libzmq"; const int RTLD_NOW = ; // for dlopen's flags
const int RTLD_GLOBAL = ;
[DllImport(@"libdl")]
static extern IntPtr dlopen(string filename, int flags);
[DllImport("libdl")]
protected static extern IntPtr dlsym(IntPtr handle, string symbol); [DllImport("kernel32.dll")]
static extern IntPtr LoadLibrary(string filename); private static IntPtr LibPtr = IntPtr.Zero;
static ZmqNative()
{ Console.WriteLine("OSArchitecture:{0}",RuntimeInformation.OSArchitecture);
try {
var libPath = @"i386";
if (RuntimeInformation.OSArchitecture == Architecture.X86)
{
libPath = @"i386";
}
else if (RuntimeInformation.OSArchitecture == Architecture.X64)
{
libPath = @"amd64";
}
else
{
Console.WriteLine("OSArchitecture not suported!");
} if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var libName = $"{AppContext.BaseDirectory}\\{libPath}\\{LibraryName}.dll";
Console.WriteLine("windows:{0}", libName);
LibPtr = LoadLibrary(libName); }
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
var libName = $"{AppContext.BaseDirectory}/{libPath}/{LibraryName}.so";
Console.WriteLine("linux:{0}", libName);
LibPtr = dlopen(libName, RTLD_NOW|RTLD_GLOBAL); if(LibPtr!=IntPtr.Zero)
{
var ptr1 = dlsym(LibPtr, "zmq_ctx_new");
context = Marshal.GetDelegateForFunctionPointer<ZmqContext>(ptr1) ; var ptr2 = dlsym(LibPtr, "zmq_socket");
socket = Marshal.GetDelegateForFunctionPointer<ZmqSocket>(ptr2); var ptr3 = dlsym(LibPtr, "zmq_connect");
connect = Marshal.GetDelegateForFunctionPointer<ZmqConnect>(ptr3); }
}
else
{
Console.WriteLine("OSPlatform not suported!");
}
if (LibPtr != IntPtr.Zero)
Console.WriteLine("load zmqlib success!");
}
catch(Exception ex)
{
Console.WriteLine("load zmqlib error:\r\n{0}",ex);
}
} public delegate IntPtr ZmqContext(); [DllImport(LibraryName, EntryPoint = "zmq_ctx_new", CallingConvention=CallingConvention.Cdecl)]
public static extern IntPtr zmq_ctx_new();
public static ZmqContext context = null; public delegate IntPtr ZmqSocket(IntPtr context, Int32 type);
[DllImport(LibraryName, EntryPoint = "zmq_socket", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr zmq_socket(IntPtr context, Int32 type);
public static ZmqSocket socket = null; public delegate Int32 ZmqConnect(IntPtr socket, IntPtr endpoint);
[DllImport(LibraryName, EntryPoint = "zmq_connect", CallingConvention = CallingConvention.Cdecl)]
public static extern Int32 zmq_connect(IntPtr socket, IntPtr endpoint);
public static ZmqConnect connect = null; [DllImport(LibraryName, EntryPoint = "zmq_errno", CallingConvention = CallingConvention.Cdecl)]
public static extern Int32 zmq_errno(); [DllImport(LibraryName, EntryPoint = "zmq_strerror", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr zmq_strerror(int errnum);
}

以上为测试代码,请自动忽略代码质量!

简单解释下,如上代码通过平台判断,动态加载组件,采用LoadLibaray的方式。 有心的同学可能会发现几个delegate并且在Linux部分内通过dlsym获取了函数指针,具体原因下面会讲。

以上测试代码,在windows平台下同样正常无误, 而在linux下还是遇到几个小坑~~容我慢慢道来:

1、通过DllImport进行Interop的时候,组件路径必须是确定的,这就引起了如何动态加载不同目录下组件的问题;

好在windows平台下通过LoadLibaray加载dll到进程空间后,DllImport标记的函数就从进程空间查找,不会重复import组件了。

而同样的原理在linux下用dlopen却不能实现,还是会提示找不到组件

2、初次部署centos7上时,报找不到libdl.so组件问题,主要原因是系统下没有glibc的原因,该问题可以通过yum安装glibc的方式解决;

//先查找系统内是否存在组件
$ sudo find / -name libdl* //如不存在则安装glibc
# yum install glibc #安装完毕后进行链接
$ sudo ln -s /usr/lib64/libdl.so. /usr/lib64/libdl

3、解决了libdl组件问题后,继续运行还是会发现报找不到libzmq组件的问题,实际就产生了问题1中所描述的,在linux系统下dlopen后,Interop过的函数并不会从进程空间查找。

为了解决上面遇到的问题,我们还有一条办法,就是创建 delegate , 并且通过LoadLibaray组件后通过GetProcAddress方式获取函数指针了。  具体的解决方案在上述测试代码已经体现了,这里就不过多解释了。

以上,就全部解决了在 netcore框架基础上进行跨平台native组件应用的问题。 真实测试结果如图:

请主动忽略初zmq应用外的其他信息, 本次测试一同测试了通过App入口启动webapi +  websockets + zmq ,api创建为aspnetcore在Web.dll内,websockets在Lib.dll内,zmq在App.dll内。

补充下完整测试代码:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices; namespace App
{ class ZmqNative
{
private const string LibraryName = "libzmq"; const int RTLD_NOW = ; // for dlopen's flags
const int RTLD_GLOBAL = ;
[DllImport(@"libdl")]
static extern IntPtr dlopen(string filename, int flags);
[DllImport("libdl")]
protected static extern IntPtr dlsym(IntPtr handle, string symbol); [DllImport("kernel32.dll")]
static extern IntPtr LoadLibrary(string filename); private static IntPtr LibPtr = IntPtr.Zero;
static ZmqNative()
{ Console.WriteLine("OSArchitecture:{0}",RuntimeInformation.OSArchitecture);
try {
var libPath = @"i386";
if (RuntimeInformation.OSArchitecture == Architecture.X86)
{
libPath = @"i386";
}
else if (RuntimeInformation.OSArchitecture == Architecture.X64)
{
libPath = @"amd64";
}
else
{
Console.WriteLine("OSArchitecture not suported!");
} if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var libName = $"{AppContext.BaseDirectory}\\{libPath}\\{LibraryName}.dll";
Console.WriteLine("windows:{0}", libName);
LibPtr = LoadLibrary(libName); }
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
var libName = $"{AppContext.BaseDirectory}/{libPath}/{LibraryName}.so";
Console.WriteLine("linux:{0}", libName);
LibPtr = dlopen(libName, RTLD_NOW|RTLD_GLOBAL); if(LibPtr!=IntPtr.Zero)
{
var ptr1 = dlsym(LibPtr, "zmq_ctx_new");
context = Marshal.GetDelegateForFunctionPointer<ZmqContext>(ptr1) ; var ptr2 = dlsym(LibPtr, "zmq_socket");
socket = Marshal.GetDelegateForFunctionPointer<ZmqSocket>(ptr2); var ptr3 = dlsym(LibPtr, "zmq_connect");
connect = Marshal.GetDelegateForFunctionPointer<ZmqConnect>(ptr3); }
}
else
{
Console.WriteLine("OSPlatform not suported!");
}
if (LibPtr != IntPtr.Zero)
Console.WriteLine("load zmqlib success!");
}
catch(Exception ex)
{
Console.WriteLine("load zmqlib error:\r\n{0}",ex);
}
} public delegate IntPtr ZmqContext(); [DllImport(LibraryName, EntryPoint = "zmq_ctx_new", CallingConvention=CallingConvention.Cdecl)]
public static extern IntPtr zmq_ctx_new();
public static ZmqContext context = null; public delegate IntPtr ZmqSocket(IntPtr context, Int32 type);
[DllImport(LibraryName, EntryPoint = "zmq_socket", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr zmq_socket(IntPtr context, Int32 type);
public static ZmqSocket socket = null; public delegate Int32 ZmqConnect(IntPtr socket, IntPtr endpoint);
[DllImport(LibraryName, EntryPoint = "zmq_connect", CallingConvention = CallingConvention.Cdecl)]
public static extern Int32 zmq_connect(IntPtr socket, IntPtr endpoint);
public static ZmqConnect connect = null; [DllImport(LibraryName, EntryPoint = "zmq_errno", CallingConvention = CallingConvention.Cdecl)]
public static extern Int32 zmq_errno(); [DllImport(LibraryName, EntryPoint = "zmq_strerror", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr zmq_strerror(int errnum);
} class ZContext
{
private static IntPtr _context = IntPtr.Zero;
static ZContext()
{
if (ZmqNative.context != null)
{
_context = ZmqNative.context();
}
else
{
_context = ZmqNative.zmq_ctx_new();
} if (_context == IntPtr.Zero)
{
Console.WriteLine("zerror:{0}", ZError.GetLastError());
}
} public static IntPtr Current
{
get
{
return _context;
}
}
} class ZError
{
public static string GetLastError()
{
var error = string.Empty;
var no = ZmqNative.zmq_errno();
if (no != )
{
var ptr = ZmqNative.zmq_strerror(no);
error = Marshal.PtrToStringUTF8(ptr);
}
return error;
}
} enum ZmqSocketType :int {
SUB =
} class ClrZmq
{
private IntPtr zmq = IntPtr.Zero;
public ClrZmq(string url) {
Url = url;
} public string Url { get; private set; } private bool _IsStarted= false;
public bool IsStarted
{
get
{
return _IsStarted;
}
} public void Start() {
if (IsStarted) return;
try
{
if (ZmqNative.socket != null)
zmq = ZmqNative.socket(ZContext.Current, (int)ZmqSocketType.SUB);
else
zmq = ZmqNative.zmq_socket(ZContext.Current, (int)ZmqSocketType.SUB);
if (zmq == IntPtr.Zero)
{
//error
Console.WriteLine("zerror:{0}",ZError.GetLastError());
return;
}
Console.WriteLine("create mq success!"); var str = Marshal.StringToHGlobalAnsi(Url); var ret = ;
if (ZmqNative.connect != null)
ret = ZmqNative.connect(zmq, str);
else
ret = ZmqNative.zmq_connect(zmq, str);
if ( ret!= )
{
//error
Console.WriteLine("zerror:{0}",ZError.GetLastError());
return;
}
Marshal.FreeHGlobal(str); Console.WriteLine("connect zmq success!"); }
catch (Exception ex) {
Console.WriteLine(ex);
}
}
}
}

netcore实践:跨平台动态加载native组件的更多相关文章

  1. vue实践---vue动态加载组件

    开发中遇到要加载10个或者更多的,类型相同的组件时,如果用普通的 import 引入组件,components注册组件,代码显得太啰嗦了,这时候就需要用到 require.context 动态加载这些 ...

  2. Vue动态加载异步组件

    背景: 目前我们项目都是按组件划分的,然后各个组件之间封装成产品.目前都是采用iframe直接嵌套页面.项目中我们还是会碰到一些通用的组件跟业务之间有通信,这种情况下iframe并不是最好的选择,if ...

  3. Vue(二十八)el-cascader 动态加载 - 省市区组件

    1.后台接口为点击加载下一级 ,传省市区id <template> <el-cascader v-model="selectedOptions" placehol ...

  4. 实现react路由动态加载的组件

    import React, { Component } from 'react'; import Loading from '../../base/nc_Loading'; /* * date: 20 ...

  5. Vue 动态加载组件

    为什么要动态加载呢?而不是一次性加载呢? 一次性?你能保证你拿的内容不多,那从性能方面说还是OK的.否则,就该什么时候用,就什么时候取. 得出这想法,源于前几天上班赶产品的故事: A组件是父亲,B组件 ...

  6. React 性能优化之组件动态加载(react-loadable)

    React 项目打包时,如果不进行异步组件的处理,那么所有页面所需要的 js 都在同一文件中(bundle.js),整个js文件很大,从而导致首屏加载时间过长. 所有,可以对组件进行异步加载处理,通常 ...

  7. 记一次奇怪IE动态加载js的乱码

    1. 问题背景 某个老产品需要支持IE8,前端部分组件采用scrat开发体系进行开发的,当页面中内嵌的iframe的页面再加载组件js的时候,某些情况下会出现组件的js乱码,导致组件的js不能运行.而 ...

  8. ExtJS4.x动态加载js文件

    动态加载js文件是ext4.x的一个新特性,可以有效的减少浏览器的压力,提高渲染速度.如动态加载自定义组件 1.在js/extjs/ux目录下,建立自定义组件的js文件. 2.编写MyWindow.j ...

  9. QML之使用Loader加载QML组件

    呵呵,今晚是边看<裸婚时代>边敲代码,那电影看得...!钱真他妈不是个东西. 盼望Meego火起来. QML的Loader元素经常备用来动态加载QML组件.可以使用source属性或者so ...

随机推荐

  1. 《类型编程晋级——shapeless类库使用指南》前言及第一章翻译

    从年初开始进行此项工作,我和合作伙伴包亮付出了大量而艰辛的劳动,现基本翻译完毕,有出版意向,如果有意向欢迎联系,不甚感激!也欢迎各位博友对此翻译提出意见建议以及指导如何出版,在此谢过! 前言 时间回到 ...

  2. Bean复制

    有的时候我们需要将一个bean的属性赋值到另一个bean的属性中,比如用户提交过来的表单bean需要将该bean中的属性值赋值到对应的数据库bean,我们通常会以这样的方式做: User user = ...

  3. 《学习记录》ng2-bootstrap中的component使用教程

    前序: 现在angular2已经除了集成的angular-cli,建议大家可以基于这个来快速开发ng2的项目,不用自己再搭建环境: 相关内容请前往:https://angular.cn/docs/ts ...

  4. (Mac OS平台)升级.NetCore1.0正式版小记

    昨天终于发布了.NetCore1.0正式版.昨晚回去就顺手把手里的一个.NetCore项目升级了一下.还是遇到了一些问题,这里记录下吧. 1.Restore问题 这个问题一直都有,一直放那没去解决.主 ...

  5. 2017-03-10 T-sql 语句 高级查询

    T-SQL语句: 创建数据库: 1,点击新建查询,在弹出的页面上进行代码编写.点击可用数据库,编写前确定当前操作的页面是自己想要进行操作的界面. 2,数据库创建语句 Create datebase   ...

  6. KoaHub平台基于Node.js开发的Koa的调试实用程序

    debug small debugging utility debug tiny node.js debugging utility modelled after node core's debugg ...

  7. OpenStack/devstack with Neutron on Ubuntu 14 (2)

    在前面的文章中,已经完成了devstack的安装.下面,我会介绍如何使用neutron 首先创建两个neutron net, vmnet1 和vmnet2 stack@ubuntu:~/devstac ...

  8. ubuntu 笔记

    Ubuntu学习之路还很长,做个笔记也不亏 terminal tab 补全忽略大小写: 在 /ect/inputrc文件中添加或修改 'set completion-ignore-case on' U ...

  9. burpsuite+sqlmap跨登录验证SQL注入

    (我操作的系统是kali linux) 1.利用burpsuite代理设置拦截浏览器请求(具体操作步骤可参考:http://www.cnblogs.com/hito/p/4495432.html) 2 ...

  10. 使用JSON.parse(),JSON.stringify()实现对对象的深拷贝

    根据不包含引用对象的普通数组深拷贝得到启发,不拷贝引用对象,拷贝一个字符串会新辟一个新的存储地址,这样就切断了引用对象的指针联系. 测试例子: var test={ a:"ss", ...