故事背景:
我们在项目中有一些采集数据的需求,在实现的时候我们实现了一些驱动来进行不同通信协议的适配,包括modbus,TCP等通信协议。在串口modbus(serialModbus)采集过程中,我们发现重新连接一直失败。通过我的研究发现,在jssc中,串口通信并不需要手动进行重连,因此删除了重连代码解决了问题。为了证明串口通信无需重连并且查找重连失败的原因,做了以下记录。
jar包使用:
jamod-2.0.0-SNAPSHOT.jar:这边我们对网络上的jar包进行了挑选,选择了可以使用的,加入我们的maven私服,版本号定位2.0.0.
jssc:jssc-2.6.2.jar:这个版本目前maven服务器中好像也没有,不过没有进行尝试,最新版可能也可以使用。
jssc中连接失败的原因:
关闭端口实现原理
以下为jssc中关闭端口的代码:
public boolean closePort() throws SerialPortException {
this.checkPortOpened("closePort()");
if (this.eventListenerAdded) {
this.removeEventListener();
}
boolean returnValue = this.serialInterface.closePort(this.portHandle);
if (returnValue) {
this.maskAssigned = false;
this.portOpened = false;
}
return returnValue;
}
可以看出关闭接口一共做了两件事:
- 移出listener
- 调用this.serialInterface的关闭功能。
打开端口实现原理
public boolean openPort() throws SerialPortException {
if (this.portOpened) {
throw new SerialPortException(this.portName, "openPort()", "Port already opened");
} else if (this.portName == null) {
throw new SerialPortException(this.portName, "openPort()", "Null not permitted");
} else {
boolean useTIOCEXCL = System.getProperty("JSSC_NO_TIOCEXCL") == null && System.getProperty("JSSC_NO_TIOCEXCL".toLowerCase()) == null;
this.portHandle = this.serialInterface.openPort(this.portName, useTIOCEXCL);
if (this.portHandle == -1L) {
throw new SerialPortException(this.portName, "openPort()", "Port busy");
} else if (this.portHandle == -2L) {
throw new SerialPortException(this.portName, "openPort()", "Port not found");
} else if (this.portHandle == -3L) {
throw new SerialPortException(this.portName, "openPort()", "Permission denied");
} else if (this.portHandle == -4L) {
throw new SerialPortException(this.portName, "openPort()", "Incorrect serial port");
} else {
this.portOpened = true;
return true;
}
}
}
重新连接发生了什么?
当程序执行重连流程时,this.portHandle返回结果为2104,此时连接并没有打开,但是因为
portHandle返回值不在错误代码中,所以串口依然可以连接成功。
下图为首次连接成功的内存模型:
下图为连接成功的内存模型
可以看出重新连接之后,serialPort对象发生了改变:
而此时,ModbusSerialTransaction中的serialPort还是重新连接之前的serialPort,并没有进行改变。
那么导致此现象的问题是什么呢?
查看ModbusSerialTransaction的创建逻辑
下图为程序中创建驱动时创建transaction
protected ModbusTransaction createModbusTransaction(TransportConfig config) {
ModbusSerialTransaction transaction = new ModbusSerialTransaction(serialConnection);
transaction.setTransDelayMS(5);
return transaction;
}
这里引用到中执行以下功能:
public void setSerialConnection(SerialConnection con) {
this.m_SerialCon = con;
this.m_IO = con.getModbusTransport();
}
通过查看ModbusSerialTransaction的代码(这里未列出),发现ModbusSerialTransaction并没有重连功能,全部参数都会在初始化的时候由传入的serialConnection。因此如果重新创建了连接,必须要重新创建ModbusSerialTransaction才可以使用新的连接,而目前重连的操作只是重新建立了新的SerialPort,而当前ModbusSerialTransaction仍在使用的7428的SerialPort已经被关闭了,因此导致发送数据失败。
而初始化加载的ModbusSerialTransaction又会与驱动一一对应,因此只有在每次新增加驱动的时候,才会创建新的ModbusSerialTransaction。
作为验证,我们在不删除连接的情况下重新创建驱动,可以看到可以正常采集。
为什么串口modbus通信无需重新连接?
虽然上面发现了问题所在,但是还是不能解决问题。为了解决问题,我们分析一下串口modbus工作原理。
串口modbus工作原理
首先我们看一下serialModbusTransport的实现:
public class SerialModubsTransport extends ModbusTransport {
private static final Logger log = LoggerFactory.getLogger(SerialModubsTransport.class);
private SerialConnection serialConnection;
public SerialModubsTransport(SerialModbusConfig config) {
super(config);
SerialParameters serialParameters = new SerialParameters();
serialParameters.setBaudRate( config.getBaudRate());
serialParameters.setDatabits(config.getDataBits());
serialParameters.setPortName( config.getPortName());
serialParameters.setStopbits( config.getStopBit());
serialParameters.setParity(config.getParity());
serialParameters.setReceiveTimeout(getTimeOutConfig(config));
serialParameters.setEncoding(config.getEncoding());
this.serialConnection = new SerialConnection(serialParameters);
}
@Override
public void reconnect() {
log.info("reconnect!");
setAlive(true);
}
@Override
public void connect() {
try {
serialConnection.open();
setAlive(true);
log.info("successfully to connect {}", getProtocol());
} catch (Exception e) {
setAlive(false);
log.error("Failed to connect:{}", getProtocol());
}
}
@Override
public void close() throws IOException {
setAlive(false);
if(serialConnection !=null){
serialConnection.close();
log.info("close port {}", getProtocol());
}
}
@Override
protected ModbusTransaction createModbusTransaction(TransportConfig config) {
ModbusSerialTransaction transaction = new ModbusSerialTransaction(serialConnection);
transaction.setTransDelayMS(5);
return transaction;
}
}
采集数据的时候,程序调用 modbusTransaction.execute() 进行数据采集,modbusTransaction中通过调用jamodTransaction.execute() 执行。
执行代码:
通过调试我们发现,当连接断开时,m_OutputStream可以正常写入数据,但是接收响应会抛出异常。
发送数据功能正常:
数据发送成功,接下来接收响应,读取串口返回值,readResponse中会调用m_InputStream的readResponse方法:
如果连接存在,则uid返回数值,如果连接丢失,则会抛出异常:
在ModbusSerialTransaction中可以捕获异常,retry一定的次数:
最后如果连续失败,则抛出ModbusIOException被程序捕获。
由此我们猜想,串口通信并不需要建立连接,传递数据使用输入输出流,只要串口硬件存在且可以被程序发现,则发送数据都可以成功。当串口连接有设备并且能够响应请求时,则接收到响应数据,至此通信成功。
同样我们使用jssc工具实现了DLT645/2007电表采集协议。