实例 : 基于Netty+JavaCV+ConcurrentHashMap处理分多次发送的图片(图片由像素点组成)
一、场景
有一个Java编写的服务器 , 一个C++编写的客户端 , 其中客户端的物理机是NanoPC T4的板子。现要基于此配置按字节流的方式通过Socket实现对图片的传输。同时在数据包中还有5个数据字段。
二、技术选型
先简述一下客户端程序的实现 : 客户端通过安装的前后两个摄像头轮流抓拍, 将获取到的图片解析成一个OpenCV的Mat对象 , Mat是基于像素值(0~255)构建的。然后在程序中封装上5个业务字段 , 将这个数据包分若干次发送给服务器 , 发送次数不固定。数据包的总长度为480X640X3+400
基于客户端的实现 , 在服务器端我们选用Netty搭建网络通信模块。 而在图像解析方面 , OpenCV官方提供的Java语言库在实际使用体验上较差 ,与C++版的库在使用体验上也是相差甚远 , 因此在大量调试后决定使用了JavaCV库(第三方)。
由于客户端是分多次发送一个数据包的 , 并且客户端数量较多 , 有并发的需求 , 因此选用JUC的ConcurrentHashMap为核心自定义一个管理设备的类。在ConcurrentHashMap中 , 设定以Netty的channel通道的id为键 , 已读取的byte数组为值。
三、具体实现与代码
图像还原
// byte[] dat是图像的像素数据
public static void trans2Pic(byte[] dat,String pathInServer){
opencv_core.Mat src2 = new opencv_core.Mat(dat);
opencv_core.Mat reshape = src2.reshape(3, 480);
opencv_imgcodecs.imwrite(pathInServer,reshape); //写入磁盘
}
NettyServer
// bossGroup仅处理连接请求
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1); // 2个线程处理连接请求
// workerGroup和客户端做业务处理
NioEventLoopGroup workerGroup = new NioEventLoopGroup(2);
try{
// 创建服务器端启动的对象 , 配置启动参数
ServerBootstrap bootstrap = new ServerBootstrap();
// 链式编程设置参数
bootstrap.group(bossGroup,workerGroup) // 设置两个线程组
.channel(NioServerSocketChannel.class) // 使用NioSctpServerChannel作为服务器的通道实现
.option(ChannelOption.SO_BACKLOG,1028) // 设置线程队列得到连接个数
.childOption(ChannelOption.SO_KEEPALIVE,true) // 设置保持活动连接状态
.childHandler(new ChannelInitializer<SocketChannel>() {
// 向pipeline 设置处理器
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyPicServerHandler());
}
});
log.info("服务器就绪");
//绑定一个端口并设置为同步
// 启动服务器
ChannelFuture cf = bootstrap.bind(8889).sync();
//给cf注册监听器,并注册事件
cf.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if (cf.isSuccess()){
System.out.println("监听端口成功 8889");
}else{
System.out.println("监听端口失败 8889");
}
}
});
// 监听关闭通道事件
cf.channel().closeFuture().sync();
} catch (Exception e){
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
NettyServerHandler (给两个关键函数)
// 将设备加入设备管理类中
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
String id = ctx.channel().id().asShortText();
byte[] data = {};
DeviceService.addDevice(id,data);
log.info("一个新客户端已连接");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String serverThreadName = Thread.currentThread().getName();
ByteBuf buf = (ByteBuf) msg;
int length =480*640*3+400; //480*640*3图片长度 , 400业务字段长度
byte[] bytes = new PackageUtils().getBytes(buf);
pictureAnalysis = new KDPictureAnalysisImpl();
// 已读取的数组
byte[] data = DeviceService.getDevice(ctx.channel().id().asShortText());
byte[] newBytes = ArrayUtils.addAll(data, bytes);
// readCount 已经成功读取的字节的个数
Integer readCount = data.length+bytes.length;
if (readCount == 0){ // 若已读取的字节数为0说明是新连接上来的客户端 , 此时需要更新其中的字节数组
DeviceService.addDevice(ctx.channel().id().asShortText(), newBytes);
}
if (readCount < length){ // byte数组未读满
DeviceService.addDevice(ctx.channel().id().asShortText(),newBytes);
} else {
DeviceService.removeDevice(ctx.channel().id().asShortText()); // 将该设备从ConcurrentHashMap移除,防止OOM
/**
* 图像还原
* 数据库操作
* */
PICTRANS
DBOPT
log.info("图片解析完成 "+analysisResult);
ctx.flush();
}
}
DeviceService (设备管理类)
public class DeviceService {
private static Map<String , byte[]> map = new ConcurrentHashMap<>();
public static void addDevice(String id, byte[] data){
map.put(id, data);
}
public static Map<String , byte[]> getDevices(){
return map;
}
public static byte[] getDevice(String id){
return map.get(id);
}
public static void removeDevice(String id){
map.remove(id);
}
}
参考资料
- https://github.com/bytedeco/javacv#readme