avframe转byte数组_多路串口转TCP连接,进行数据双向透传

最近在写一个关于物联网的小工具,用linux工控小主机做一个串口服务器,将串口数据与指定的tcp服务器做数据双向透传,使用spring-integration和jssc的方案实现,把主要过程记录下来,以备查询

整个工程是基于jssc和spring-integration-ip在Spring boot上开发,便于后期集成管理界面,总体思路是用jssc接收发和转发串口数据,再用spirng integration将串口数据转发到tcp服务端,大体架构如下图所示:

3168d0437db86f374f5ff66d51d1075d.png

图1 串口转TCP服务逻辑架构图

工程中需要用的几个关键依赖包如下:

org.springframework.integration     spring-integration-ip     5.3.1.RELEASEcommons-codec     commons-codec     1.14org.scream3r     jssc     2.8.0

对于spring integration的配置还是比较容易的,这里有几个概念对于第一次接触spring integration的来说可有点晕,主要涉及到消息的入站、出站,管道,我个人理解就是入站从外部输入数据,出站是从本地输出数据,管道就是数据的通道,对于出站和入站也分出站管道和入站管道,具体配置如下:

package org.noka.serialservice.config;import org.apache.commons.codec.binary.Hex;import org.noka.serialservice.Serializer.NByteArrayCrLfSerializer;import org.noka.serialservice.service.SerialService;import org.noka.serialservice.service.TcpGateway;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.ApplicationEvent;import org.springframework.context.ApplicationListener;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.integration.annotation.ServiceActivator;import org.springframework.integration.config.EnableIntegration;import org.springframework.integration.ip.tcp.TcpReceivingChannelAdapter;import org.springframework.integration.ip.tcp.TcpSendingMessageHandler;import org.springframework.integration.ip.tcp.connection.AbstractClientConnectionFactory;import org.springframework.integration.ip.tcp.connection.TcpConnectionOpenEvent;import org.springframework.integration.ip.tcp.connection.TcpNetClientConnectionFactory;import org.springframework.messaging.Message;import org.springframework.messaging.MessageHandler;import org.springframework.messaging.support.MessageBuilder;/** ---------------------------------------------------------------------------------- * TCP数据转发服务配置 * @author  xiefangjian@163.com * @version 1.0.0 *----------------------------------------------------------------------------------*/@EnableIntegration@Configurationpublic class TcpConfig implements ApplicationListener {    private static Logger logger = LoggerFactory.getLogger(TcpConfig.class);    //TCP服务器地址,可以用域名或IP    @Value("${socket.client.ip}")    private String host;    //TCP服务器端口    @Value("${socket.client.port}")    private int port;    //串口数据转发服务对象    private final SerialService serialService;    //TCP端数据转发网关    private final TcpGateway tcpGateway;    /**     * 配置构造方法     * @param serialService 串口数据转发服务对象     * @param tcpGateway TCP端数据转发网关     */    public TcpConfig(SerialService serialService, TcpGateway tcpGateway) {        this.serialService = serialService;        this.tcpGateway = tcpGateway;    }    /**     * 创建TCP连接     * @return tcp clinet连接工厂对象 AbstractClientConnectionFactory     */    @Bean    public AbstractClientConnectionFactory clientCF() {        TcpNetClientConnectionFactory tc = new TcpNetClientConnectionFactory(this.host, this.port);//创建连接        tc.setDeserializer(new NByteArrayCrLfSerializer());//设置自定义反序列化对象,对数据转发做分析处理        tc.setSerializer(new NByteArrayCrLfSerializer());//设置自定义反序列化对象,对数据转发做分析处理        return tc;    }    /**     * TCP服务下发数据接收管道配置,spring integration称之为入站管道配置     * @param    connectionFactory  连接工厂     * @return   TcpReceivingChannelAdapter 入站管道对象     */    @Bean    public TcpReceivingChannelAdapter tcpInAdapter(AbstractClientConnectionFactory connectionFactory) {        TcpReceivingChannelAdapter inGate = new TcpReceivingChannelAdapter();//新建一个TCP入站管道        inGate.setConnectionFactory(connectionFactory);//绑定到当前的连接工厂上        inGate.setClientMode(true);//设置连接为客户端模式        inGate.setOutputChannelName("clientIn");//入站管道名称,后面数据接收的地方需要该名称进行匹配        return inGate;    }    /**     * 服务器有数据下发     * @param in 服务器有数据下发时,序列化后的对象,这里使用byte数组     */    @ServiceActivator(inputChannel = "clientIn")    public void upCase(Message in) {        logger.info("[net service data]========================================");        logger.info("[net dow data]"+new String(in.getPayload()));//字符串方式打印服务器下发的数据        logger.info("[net dow hex]"+Hex.encodeHexString(in.getPayload(),false));//16进制方式打印服务器下发的数据        serialService.send(in.getPayload());//将服务器下发的数据转发给串口    }    /**     * 向服务器发送数据管道绑定     * @param connectionFactory tcp连接工厂类     * @return 消息管道对象     */    @Bean    @ServiceActivator(inputChannel = "clientOut")    public MessageHandler tcpOutAdapter(AbstractClientConnectionFactory connectionFactory) {        TcpSendingMessageHandler outGate = new TcpSendingMessageHandler();//创建一个新的出站管道        outGate.setConnectionFactory(connectionFactory);//绑定到连接工厂        outGate.setClientMode(true);//设置为客户端连接模式        return outGate;    }    /**     * 连接成功时调用的方法     * @param event 响应事件     */    @Override    public void onApplicationEvent(ApplicationEvent event) {//监听连接打开事件        if (event instanceof TcpConnectionOpenEvent) {            /**---------连接时如果需要发送认证类消息时可以写在这里--------------------**/            byte[] snc="OK".getBytes();//这里在连接时,简单的向服务器发送一个OK字符串            tcpGateway.send(MessageBuilder.withPayload(snc).build());//发送消息        }    }}

TcpGateway只是一个接口,用于在其它地方调用该接口向TCP服务器发送消息,具体实现如下:

package org.noka.serialservice.service;import org.springframework.integration.annotation.MessagingGateway;import org.springframework.messaging.Message;import org.springframework.stereotype.Component;/**---------------------------------------------------------------- * TCP发送消息网关,其它需要发向TCP服务器发送消息时,调用该接口 **--------------------------------------------------------------**/@MessagingGateway(defaultRequestChannel = "clientOut")@Componentpublic interface TcpGateway {    void send(Message out);//发送消息方法}

自定义序列列和反序列化对象,主要是处理byte类型的数据,在测试的时候开始使用默认的序列化对象,TCP服务端每次发送数据都需要在结束时加入"",否则收不到数据,在使用byte类型传输时,显示很不友好,而且这样对TCP服务端造成了特殊要求,不能通用,改造后的序列化对象如下:

package org.noka.serialservice.Serializer;import org.springframework.integration.ip.tcp.serializer.AbstractPooledBufferByteArraySerializer;import org.springframework.integration.ip.tcp.serializer.SoftEndOfStreamException;import java.io.*;/**---------------------------------------------------------------------------------- * 自定义序列化工具类 * @author xiefangjian@163.com * @version  1.0.0 **--------------------------------------------------------------------------------**/public class NByteArrayCrLfSerializer extends AbstractPooledBufferByteArraySerializer{    /**     * 单例模式     */    public static final NByteArrayCrLfSerializer INSTANCE = new NByteArrayCrLfSerializer();        /**     * 数据转换输出,当前服务器有数据下发时,读取成byte数组后调用输出方法,传输给出站管道     * @param inputStream 输入流     * @param buffer 缓存对象     * @return 读取之后的bytes     * @throws IOException     */    @Override    public byte[] doDeserialize(InputStream inputStream, byte[] buffer) throws IOException {        int n = this.fillToCrLf(inputStream, buffer);        return this.copyToSizedArray(buffer, n);    }    /**     * 数据校验及数据处理,原对象是逐个byte读取的,然后校验是否为结束符     * 这里做了修改,直接读取到指定长度的bytes中,然后返回,     * @param inputStream     * @param buffer     * @return     * @throws IOException     */    public int fillToCrLf(InputStream inputStream, byte[] buffer) throws IOException {        int n=0; //读到到的数据长度,这里指byte数组长度        if (logger.isDebugEnabled()) {            logger.debug("Available to read: " + inputStream.available());        }        try {            return inputStream.read(buffer);//读取数据,buffer默认为2048个byte        } catch (SoftEndOfStreamException e) {            throw e;        } catch (IOException e) {            publishEvent(e, buffer, n);            throw e;        } catch (RuntimeException e) {            publishEvent(e, buffer, n);            throw e;        }    }    /**     * 写入数据,去掉了原对象的结束符自动补齐,不需要特定结束符     * @param bytes     * @param outputStream     * @throws IOException     */    @Override    public void serialize(byte[] bytes, OutputStream outputStream) throws IOException {        outputStream.write(bytes);//直接输出    }}

串口部分采用一个服务类加一个工具类完成,一个串口一个监听,有数据就进行TCP转发,同样TCP有数据下发就向串口转发,服务类实现如下:

package org.noka.serialservice.service;import jssc.SerialPort;import jssc.SerialPortList;import org.apache.commons.codec.binary.Hex;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import java.util.ArrayList;import java.util.List;/**--------------------------------------------------- * 串口服务类,主要对串口进行操作 * @author  xiefangjian@163.com * @version 1.0.0 **-------------------------------------------------**/@Componentpublic class SerialService{    private static Logger logger = LoggerFactory.getLogger(SerialService.class);        private  final  TcpGateway tcpGateway;//TCP数据发送网关,自动注入,调用该接口发送数据到TCP服务端    private List COM_LIST = new ArrayList<>();//缓存串口列表    /**     * 构造方法,注入TCP网关对象     * @param tcpGateway TCP网关对象     */    public  SerialService(TcpGateway tcpGateway){        this.tcpGateway = tcpGateway;        initComs();//初始化串口    }    /**     * 打开所有串口     */    public  void initComs(){        String[] com_lists= SerialPortList.getPortNames();//获取串口列表        for(String com:com_lists){            logger.info("init com:"+com);            SerialPort serialPort = new SerialPort(com);//设置串口            COM_LIST.add(new SerialUtils(serialPort,tcpGateway));//缓存串口列表        }    }    /**     * 发送数据到所有打开的串口     * @param str 需要发送的数据     */    public  void send(byte[] str){        for(SerialUtils s:COM_LIST){            s.sendData(str);//发送数据到串口            logger.info("[send "+s.getName()+" data]"+new String(str));//字符串方式日志打印            logger.info("[send "+s.getName()+" hex]"+Hex.encodeHexString(str,false));//16进制方式打印日志        }    }}

串口工具类如下:

package org.noka.serialservice.service;import jssc.SerialPort;import jssc.SerialPortEvent;import jssc.SerialPortEventListener;import jssc.SerialPortException;import org.apache.commons.codec.binary.Hex;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.integration.support.MessageBuilder;import java.io.ByteArrayOutputStream;import java.io.IOException;/**-------------------------------------------------------------------- * 串口工具类 * @author  xiefangjian@163.com * @version  1.0.0 **------------------------------------------------------------------**/public class SerialUtils implements SerialPortEventListener {    private static Logger logger = LoggerFactory.getLogger(SerialUtils.class);    private SerialPort serialPort=null;//串口对象    private TcpGateway tcpGateway; //网关对象    /**     * 构造方法,初始化串口对象和网关对象     * @param serialPort 串口对象     * @param tcpGateway 网关对象     */    public SerialUtils(SerialPort serialPort, TcpGateway tcpGateway) {        if(null!=serialPort){            this.serialPort=serialPort; //串口对象            openCom();//初始化串口        }        this.tcpGateway = tcpGateway;//网关对象    }    /**     * 获取本串口名称     * @return 串口名称     */    public String getName(){        if(null!=serialPort) {            return serialPort.getPortName();        }        return null;    }    /**     * 打开串口     */    public void openCom(){        try {            serialPort.openPort();//打开串口            serialPort.setParams(                    SerialPort.BAUDRATE_115200,//串口波特率                    SerialPort.DATABITS_8,//8位数据位                    SerialPort.STOPBITS_1,//1位停止位                    SerialPort.PARITY_NONE//无奇偶校验            );            serialPort.addEventListener(this);//开启数据接收监听        }catch (SerialPortException ux) {            logger.error(ux.getMessage());        }    }    /**     * 发送数据     * @param str 需要写入的数据     */    public void sendData(byte[] str){        if(null!=serialPort && serialPort.isOpened()){//如果串口已经打开            try {                serialPort.writeBytes(str);//向串口写入数据            }catch (SerialPortException ex){               logger.error(ex.getMessage());            }        }    }    /**     * 发送数据     * @param str 需要写入的数据     */    public void sendData(String str){        if(null!=serialPort && serialPort.isOpened()){//如果串口已经打开            try {                serialPort.writeString(str);//向串口写入数据            }catch (SerialPortException ex){                logger.error(ex.getMessage());            }        }    }    /**     * 串口有数据上来,转发数据到服务器上     * @param serialPortEvent 事件类型     */    @Override    public void serialEvent(SerialPortEvent serialPortEvent) {        if (serialPortEvent.isRXCHAR()) {//有数据到达事件发生            ByteArrayOutputStream xs = new ByteArrayOutputStream();//数据缓存对象            if(serialPortEvent.getEventValue() > 0){//数据池有数据                try{ Thread.sleep(100); }catch (Exception ex){}//等待100毫秒,以便更多的数据进入数据池,以确保数据传输完成                while(serialPortEvent.getEventValue() > 0) {//循环读取数据                    try{ Thread.sleep(10); }catch (Exception ex){}//等待10毫秒                    try {                        byte[] sx = serialPort.readBytes();//读取数据                        if (null != sx) {                            xs.write(sx);//放入数据缓存池                        } else {                            break;//为空时,说明读取完成,需要跳出循环                        }                    } catch (SerialPortException | IOException ec) {                        logger.error(ec.getMessage());                        break;                    }                }            }            byte[] xbs = xs.toByteArray();//获取所有缓存数据            tcpGateway.send(MessageBuilder.withPayload(xbs).build());//发送串口数据到服务器            logger.info("[com up data]"+new String(xbs));//字符串方式打印日志            logger.info("[com up hex]"+ Hex.encodeHexString(xbs,false));//16进制方式打印日志            try{xs.close();}catch (Exception es){}//关闭数据缓存池        }    }}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值