实例 : 基于Netty+JavaCV+ConcurrentHashMap处理分多次发送的图片(处理像素数组)

实例 : 基于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);
    }

}

参考资料

  1. https://github.com/bytedeco/javacv#readme
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值