缘由
有时候研发或者测试过程中需要把各个终端的数据整合到统一的图表绘制,这就需要一个可视化的现场调试工具根据你的调试需要进行即时的数据展示和修改,当然可以有很多种选择,包括qt绘图,python,甚至r语言(后边我大概会写一个r语言版本的)。不过我比较喜欢用Matlab,因为他的运算库很丰富,而且绘图刷新效率也非常高,所以在Matlab开一个socket监听端口让其他设备连接上来是一个很自然的想法,然而代码写了几行就开始觉得恶心了:
- 如果只有一个客户端那就非常简单,accept到了就开始不断recv,收到断开消息就重新accept。但是多客户端的socket队列写起来挺恶心的,matlab不太适合干这种脏活;
- 字节流接收处理有些慢;
- 同样在传输数据量较大的时候,accept会耽误recv造成延迟,因为accept需要同步等待;换句话说就是Matlab没有多线程;
解决方法也很自然,就是让matlab调用java。为什么不调用c/++库是因为matlab和c之间还得封装接口,而matlab和java可以浑然一体。
预先工作
确认Matlab的java版本
K>>version -java
ans =
Java 1.7.0_75-b13 with Oracle Corporation Java HotSpot™ 64-Bit Server VM mixed mode
接下来就要用相同版本的jdk进行java代码编译和打包
java端
java有一个非常优雅的Nio库,基于epoll或完成端口,足以满足大部分的网络服务器需求,而且写起来也简单很多。
代码
- NioServer.java
package niosrv4matlab;
import java.net.SocketAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.io.ByteArrayOutputStream;
public class NioServer {
private int _port;
private ServerSocketChannel _ssc;
private Selector _selector;
private char _cliName;
public NioServer(int port){
_port = port;
_cliName = '1';
}
public int listen() throws Exception {
// create channel, async
_ssc = ServerSocketChannel.open();
_ssc.configureBlocking(false);
// create selector
_selector = Selector.open();
_ssc.register(_selector, SelectionKey.OP_ACCEPT).attach("accept_channel");
// bind listen port
InetSocketAddress sa = new InetSocketAddress(_port);
_ssc.socket().bind(sa);
System.out.println("listening ...");
return 0;
}
public byte[] readMessage() throws Exception {
String msgflag = "nioserver4matlab";
ByteArrayOutputStream bo = new ByteArrayOutputStream();
int readyChannels = _selector.selectNow();
if (readyChannels > 0) {
Set<SelectionKey> keys = _selector.selectedKeys();
Iterator it = keys.iterator();
while (it.hasNext()) {
// System.out.println("ready");
SelectionKey key = (SelectionKey)it.next();
it.remove();
if (key.isAcceptable()) {
ServerSocketChannel acceptCh = (ServerSocketChannel)key.channel();
SocketChannel scCli = (SocketChannel)acceptCh.accept().configureBlocking(false);
scCli.register(_selector, (SelectionKey.OP_READ)).attach(_cliName);
_cliName++;
}
if (key.isReadable()) {
SocketChannel scCli = (SocketChannel) key.channel();
ByteBuffer buf = ByteBuffer.allocate(512);
int ret = scCli.read(buf);
if(ret>0){
InetSocketAddress remoteAddr = (InetSocketAddress)scCli.getRemoteAddress();
String head = String.format("%s,%s,%d,", msgflag, remoteAddr.getAddress().getHostAddress(), ret);
bo.write(head.getBytes());
bo.write(buf.array(), 0, ret);
//System.out.println(remoteAddr.getAddress().getHostAddress() + ": msg");
}
if(ret == -1){
scCli.close();
key.cancel();
System.out.println("client closed.");
continue;
}
} //readable
} //it next
} //ch>0
return bo.toByteArray();
}
protected void finalize() throws Exception{
if(_ssc.isOpen()){
_ssc.close();
}
if(_selector.isOpen()){
_selector.close();
}
}
}
接口说明
- 构造函数指定端口进行初始化
- listen()开始监听消息
- 调用readMessage()读取网络消息,没有消息返回空串。
消息头部是一个简单的flag字符串作为头部标记,需要的话你也可以改为json格式。
返回值用byte[]是因为Matlab调用的时候可以直接拿byte[]=过来。
更高级的改进
可以看到这段代码并没有开线程和维护数据队列,我把这部分工作放到了Matlab端。实际上更高效的做法是java端单独开线程不断的接受和维护数据队列,调用者(Matlab端)根据需要定时peek消息就可以了,但是这样做的话要注意matlab程序中断退出的时候要给个消息过来清理线程和数据。
编译和打包
javac niosrv4matlab/NioServer.java
jar -cvf niosrv4matlab.jar niosrv4matlab/NioServer.class
然后就会得到一个niosrv4matlab.jar,把他放到一个地方比如c:\matlabjava\niosrv4matlab.jar。
Matlab端
Matlab直接支持原生态java调用,所以处理起来非常简单。
导入jar包
首先要添加这个jar,在Matlab的命令行
K>>edit classpath.txt
编辑器会打开一个classpath.txt,在最末尾添加一行比如
c:\matlabjava\niosrv4matlab.jar
然后保存,重启Matlab。重启之后试一下
K>>import niosrv4matlab.NioServer
如果没有报错就是成功了。如果提示找不到这个类,检查一下打包路径是否正确,以及java版本是否匹配,等等。
示例代码
- niosrv.m
function niosrv(port)
if nargin<1
port = 4369;
end
import niosrv4matlab.NioServer
ns = NioServer(port);
ns.listen();
while true
msg = ns.readMessage();
if ~isempty(msg)
fprintf(char(msg));
else
pause(0.05);
end
end
end
%%
测试
运行上边的脚本,然后找台机器telnet或者nc发消息过来,成功的话可以在Matlab端打印出消息来源ip,消息长度,消息内容。
结语
这段代码来源于一个数据调试的需求,而且情况相对特殊,在局域网环境多个终端最高要传输几百KB/s的数据进行统一处理和图表绘制。我觉得效果挺好的,java只做网络消息逻辑,matlab只做数据处理和绘图,所以就放了上来。希望能够帮助到也遇到需要用Matlab做网络服务器这种奇葩需求的朋友。
当然实际产品的计算网关是用c++,也没有用epoll,就是简单的select模式,也没那么大的数据量。
转载请注明出处