Java结合Web页面使用多线程实现全双工串口通信

在几天在接手的项目中要求实现串口全双工通信,由于自己Java也是刚刚开始学习有些概念还不是理解的很透彻,所以这个工程还是花费了点时间,遇到了些许问题都会在这篇文章中有所体现,还有源码凹,希望可以帮助到你们。

要是对串口通信的基本概念没有理解的话(原理还不是很清楚的),还是软件层没有串口的设备都可以看这篇文章(里面包含了串口模拟助手的安装和调试,还有一个串口的全双工的实验,但是为什么我还要写这个文章呢?那肯定是那个方法实现不了这个项目的需求):Java程序与串口通信的实现及通信源码(全网最详细,一步一步教会你)

            话不多说开始分享本次在做项目时遇到的问题和解决方法,最终实现我们需要的目的。

                                      

目录

项目的需求

项目需求分析

项目经历的过程

一  瞒天过海

二  脚踏实地 

项目中遇到的问题及其解决方法

 总结

项目的代码

Read线程类:

Write线程类:

主线程

后言


项目的需求

本项目主要想实现的功能就是网页触发一个事件(比如点击一个按钮)可以传输到底层一个数据,然后也可以通过底层传输一个数据给网页(实时显示:jQuery实现AJAX定时刷新局部和全部页面实例(附jQuery1.2到最新3.3.1所有版本下载)不会局部刷新的可以看看这篇文章) 

项目需求分析

需求看去挺简单的,不就是Web端和后台串数据嘛!这个确实是这样的,页面之间传输数据也确实很简单,写点request和response就好了。

但我们的重点是串口全双工数据传输,上面那个链接中的源码也可以实现串口之间的发送和接收数据,但是他实现不了外部触发一个事件产生一个效果,对于底层来说那个方法只能接收一次并且那个数据是固定的。我们要实现在不同时间可以发送不同数据,所以就要对那个源码进行一定的修改。其实在全双工之前我还做了单双工的两个读写方法。刚开始想偷个懒什么的结果导师说不行,看了学习的道路上还是没有什么捷径可言,就需要老老实实的做项目。

在单双工的实验过程中我们是不是可以稍加改变就可以实现全双工的效果了呢?      当然可以

                                                     

           思路有了就剩实践了 ,那就动手开始淦

项目经历的过程

一  瞒天过海

开始的时候想通过串口的快速关闭再开启实现“假”全双工,前提就是读取的时候永远在读取,当你收到一个指令的时候就把读的串口停掉,在再写的程序中开起来,写完再关掉。这是我最开始想的瞒天过海,并且也去实践了,但是会发现不可行,因为你调取不到前面那个串口的信息(或许有大佬可以指点指点,哈哈哈)两个程序,实现不了同一变量的修改。网上也搜了很多解决的方法,怎么通过一个串口名关闭串口,但是很无奈,没有这种方法,所以最后还是放弃了这个瞒天过海的大招!!!!

二  脚踏实地 

看来还是要脚踏实地的操作,歪门邪道都是没啥用的,话不多说就开始实践了,网上看了一下实现全双工通信的都是Java多线程但我那时并不知道什么是多线程,还是很小白的,直接就去学习什么叫多线程及其原理。了解了个大概加上有大佬相助写出了差不多的程序(Read和Write分别使用两个线程实现,在一个线程类中实现这两个线程的运行,有一部分的代码是公共的我就把它写在了Read的线程中,在运行Read线程的时候就判断串口是否连接成功,最终实现了Read和Write两个线程互不影响,独立运行。最好在两个线程中实现一下线程的睡眠),但是没那么简单就会成功的,还是出现了很多的问题,会在下面进行提现。

项目中遇到的问题及其解决方法

上面说的两个程序中的数据共享问题 

  在两个main函数的的两个程序中,你新建立一个线程然后在其他的main函数中想去改变线程的的一个变量实现不同的操作,这样是不可以的,要在同一个main函数中使用

比如在实验中遇到的实际例子:

                   

左图中我新建类一个线程,这个线程中会有两个子线程来实现Read和Write,两个线程分别都在run()方法中写入了死循环实现一直等待数据的到来。

右图想实现的是改变Write线程中的一个值,让他运行起来,实现写的操作。

但是我通过代码的调试以后发现了,在Write中的flag变量并不会随右图程序的运行而改变,说明了右图中的修改并不是修改了Write线程中的flag值。

要把这个修改值的代码放入左图中的main函数。这样就可以实现发送了。

 总结

不可以把两个main函数的数据共享,我说的是这个项目中,当然在其他的还是可以通过Static变量实现数据的共享,我想可能是这个是线程的原因吧,他线程已经在运行了,修改的时候不可以在另外的main函数中修改,尽管使用了static变量。

                                                      

项目的代码

Read线程类:

package Chuankou;

import java.io.*;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import gnu.io.*;

public class ContinueckRead extends Thread implements SerialPortEventListener { // SerialPortEventListener
    // 监听器,我的理解是独立开辟一个线程监听串口数据
    static CommPortIdentifier portId; 		// 串口通信管理类
    static Enumeration<?> portList; 		// 有效连接上的端口的枚举
    InputStream inputStream; 				// 从串口来的输入流
    static OutputStream outputStream;		// 向串口输出的流
    static SerialPort serialPort; 			// 串口的引用
    // 堵塞队列用来存放读到的数据
    private BlockingQueue<String> msgQueue = new LinkedBlockingQueue<String>();
    static int i=0;                         //判断是否打开串口
    @Override
    /**
     * SerialPort EventListene 的方法,持续监听端口上是否有数据流
     */
    public void serialEvent(SerialPortEvent event) {//

        switch (event.getEventType()) {
        case SerialPortEvent.BI:
        case SerialPortEvent.OE:
        case SerialPortEvent.FE:
        case SerialPortEvent.PE:
        case SerialPortEvent.CD:
        case SerialPortEvent.CTS:
        case SerialPortEvent.DSR:
        case SerialPortEvent.RI:
        case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
            break;
        case SerialPortEvent.DATA_AVAILABLE:// 当有可用数据时读取数据
            byte[] readBuffer = new byte[14];
            String a;                       //传输过来的协议内容
            try {
                int numBytes = -1;
                while (inputStream.available() > 0) {
                    numBytes = inputStream.read(readBuffer);
                    //字节转十六进制
                    a=Tool.bytesToHexString(readBuffer);
                    if (numBytes > 0) {
                        msgQueue.add(new Date() + "真实收到的数据为:-----"+a);
                        IntoDb.IntoDatebase(a);
                        readBuffer = new byte[14];							// 重新构造缓冲对象,否则有可能会影响接下来接收的数据
                    } else {
                        msgQueue.add("额------没有读到数据");
                    }
                }
            } catch (IOException e) {
            }
            break;
        }
    }

    /**
     * 
     * 通过程序打开COM4串口,设置监听器以及相关的参数
     * 
     * @return 返回1 表示端口打开成功,返回 0表示端口打开失败
     */
    public int startComPort() {
        // 通过串口通信管理类获得当前连接上的串口列表
        portList = CommPortIdentifier.getPortIdentifiers();

        while (portList.hasMoreElements()) {

            // 获取相应串口对象
            portId = (CommPortIdentifier) portList.nextElement();

            System.out.println("设备类型:---->" + portId.getPortType());
            System.out.println("设备名称:---->" + portId.getName());
            // 判断端口类型是否为串口
            if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) {
                // 判断如果COM4串口存在,就打开该串口
                if (portId.getName().equals("COM4")) {
                    try {
                        // 打开串口名字为COM_4(名字任意),延迟为2毫秒
                        serialPort = (SerialPort) portId.open("COM_4", 2000);

                    } catch (PortInUseException e) {
                        e.printStackTrace();
                        return 0;
                    }
                    // 设置当前串口的输入输出流
                    try {
                        inputStream = serialPort.getInputStream();
                        outputStream = serialPort.getOutputStream();
                    } catch (IOException e) {
                        e.printStackTrace();
                        return 0;
                    }
                    // 给当前串口添加一个监听器
                    try {
                        serialPort.addEventListener(this);
                    } catch (TooManyListenersException e) {
                        e.printStackTrace();
                        return 0;
                    }
                    // 设置监听器生效,即:当有数据时通知
                    serialPort.notifyOnDataAvailable(true);

                    // 设置串口的一些读写参数
                    try {
                        // 比特率、数据位、停止位、奇偶校验位
                        serialPort.setSerialPortParams(9600,
                                SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
                                SerialPort.PARITY_NONE);
                    } catch (UnsupportedCommOperationException e) {
                        e.printStackTrace();
                        return 0;
                    }

                    return 1;
                }
            }
        }
        return 0;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
    	
        try {
            System.out.println("--------------任务读处理线程运行了--------------");
            
            while (true) {
                // 如果堵塞队列中存在数据就将其输出
                if (msgQueue.size() > 0) {
                    System.out.println(msgQueue.take());
                    sleep(3000);
                    //ContinueckWrite.setvalues("send");
                    //System.out.println(DataStorage.flag);
                }
            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }     
}

Write线程类:

package Chuankou;

import java.io.*;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import gnu.io.*;

public class ContinueckWrite extends Thread {  
	
    
    public boolean isFlag() {
		return DataStorage.flag;
	}

	/**

     * 16进制表示的字符串转换为字节数组

     *

     * @param hexString 16进制表示的字符串

     * @return byte[] 字节数组

     */

    public static byte[] hexStringToByteArray(String hexString) {
    	
    		hexString = hexString.replaceAll(" ", "");

            int len = hexString.length();

            byte[] bytes = new byte[len / 2];

            for (int i = 0; i < len; i += 2) {

                // 两位一组,表示一个字节,把这样表示的16进制字符串,还原成一个字节

                bytes[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character

                        .digit(hexString.charAt(i+1), 16));

            }

            return bytes;
    	}


    public static void setvalues(String x) {
    	DataStorage.str=x;
    	DataStorage.flag = true;
    	
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        try {
        	System.out.println("--------------任务写处理线程运行了--------------");
            while (true) {
                // 如果堵塞队列中存在数据就将其输出
            	
            	try {
					sleep(3000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
                if (DataStorage.flag){
                	System.out.println("--------------数据发送成功!--------------");
                	System.out.print("发出的数据为:");
                	System.out.println(DataStorage.str);
                	byte[] st =ContinueckWrite.hexStringToByteArray(DataStorage.str);
                    System.out.println(st);
                    System.out.println("发出字节数:" + st.length);
                	ContinueckRead.outputStream.write(st);
                	ContinueckRead.outputStream.flush();
                	
                	DataStorage.flag =false;
                	
                }
                
            }
        } catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		
    }
    }    
    
}

主线程

package Chuankou;

public class RWTest extends Thread {
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		   int i=0;
		   ContinueckRead cRead = new ContinueckRead();
	   	   ContinueckWrite write=new ContinueckWrite();
	       i = cRead.startComPort();
	       if (i == 1) {
	       	System.out.println("启动成功!!!"+i);
	           // 启动线程来处理收到的数据
	           cRead.start();
	           System.out.println("准备开启写线程");
	           write.start();
	          
	       } else {
	           return;
	       }
	}

//	public static void main(String[] args) {
//		// TODO Auto-generated method stub
//	    RWTest rw=new RWTest();
//	    rw.start();
//	}

}

在Web页面运行的时候就自行启动主线程中注释的部分,这样就实现了在同一个main函数中启动Read和Write线程(可以实现随时写入和输出)。

后言

文章纯属个人总结如有不对,请各位大佬们提出来!!!有喜欢学习Java的小伙伴可以关注我一下,本人也是学习Java路上的小白,但是会自己会经常总结出一些基础并且很实用的知识点拿出来分享给大家,希望在学习的道路上可以对你们有所帮助,我们可以一起学习一起进步。还有也欢迎大佬们和我讨论关于Java的一些知识,欢迎大家进我主页看看学习。相互学习!!!互相进步!!! 

                                                         

No pains No results 

猜你喜欢

转载自blog.csdn.net/weixin_45629315/article/details/106249685
今日推荐