Java 1.6打印机驱动中设备文件带超时读写后通道损坏的问题求助
老哥,我之前在做Linux下Java设备文件操作的时候,正好踩过和你一模一样的坑——用RandomAccessFile包装/dev/usb/lp0,通过FileChannel做带超时的读写,结果超时一次后通道直接废掉,下次操作就抛ClosedChannelException。给你几个亲测有效的解决方案,你可以试试:
1. 超时后直接重建资源(最直接的临时方案)
Java NIO的FileChannel一旦因为超时、中断等异常进入错误状态,基本上就没法复用了,这是NIO通道的特性——一旦关闭或者故障,就彻底凉了。所以最简单的办法就是:捕获到超时或通道异常时,立刻关闭当前的RandomAccessFile和Channel,然后重新打开新的实例。
给你个代码片段参考:
public class LpDeviceHandler { private RandomAccessFile raf; private FileChannel channel; // 打开设备文件 public void openDevice() throws IOException { this.raf = new RandomAccessFile("/dev/usb/lp0", "rw"); this.channel = raf.getChannel(); } // 带超时的写操作(这里假设你是用线程+Future实现的超时控制) public void writeWithTimeout(byte[] data, long timeoutMs) throws IOException { ByteBuffer buffer = ByteBuffer.wrap(data); try { // 这里替换成你自己的超时写逻辑,比如用FutureTask包装写操作 channel.write(buffer); } catch (WriteTimeoutException | ClosedChannelException e) { // 出问题了,先把旧资源关干净 closeResources(); // 重新打开设备 openDevice(); // 要么重试一次,要么把异常抛给上层处理 throw new IOException("设备写超时,已自动重建连接,请重试", e); } } // 关闭资源的工具方法 private void closeResources() { if (channel != null) { try { channel.close(); } catch (IOException ignored) { // 关不上也无所谓,反正要重建 } } if (raf != null) { try { raf.close(); } catch (IOException ignored) {} } } }
2. 放弃FileChannel,直接用RandomAccessFile+线程超时控制
Java 1.6的FileChannel其实是不支持非阻塞I/O的,也没有内置的超时参数,你之前说的“通道允许定义超时”,估计是用了线程包装的方式。那不如直接跳过FileChannel,用RandomAccessFile的原生读写,配合FutureTask做超时控制,这样超时后中断线程,再重建RAF即可,避免通道损坏的问题。
代码示例:
import java.util.concurrent.*; public class LpDeviceHandler { private RandomAccessFile raf; public void openDevice() throws IOException { this.raf = new RandomAccessFile("/dev/usb/lp0", "rw"); } public boolean writeWithTimeout(byte[] data, long timeoutMs) throws Exception { ExecutorService singleExecutor = Executors.newSingleThreadExecutor(); Future<Boolean> writeFuture = singleExecutor.submit(new Callable<Boolean>() { @Override public Boolean call() throws IOException { raf.write(data); raf.getFD().sync(); // 强制刷到设备,避免缓存 return true; } }); try { // 等待指定时间获取结果 return writeFuture.get(timeoutMs, TimeUnit.MILLISECONDS); } catch (TimeoutException e) { // 超时了,取消任务并关闭旧资源 writeFuture.cancel(true); closeResources(); openDevice(); throw new TimeoutException("写入打印机超时,已重建连接"); } finally { singleExecutor.shutdown(); } } private void closeResources() { if (raf != null) { try { raf.close(); } catch (IOException ignored) {} } } }
3. 直接给Linux设备设置超时(从根源解决)
上面的都是Java层的临时 workaround,其实你可以直接给/dev/usb/lp0这个设备本身设置超时,让操作系统来处理超时逻辑,这样Java层就不用自己搞超时了,自然也不会因为中断导致通道损坏。
这个需要用JNI调用Linux的ioctl命令来设置设备超时,Java 1.6里可以这么做:
首先在Java类里声明native方法:
public class LpDeviceHandler { private RandomAccessFile raf; // 声明native方法,用来设置设备超时 private native void setLpTimeout(int fd, int timeoutSeconds); // 加载本地库 static { System.loadLibrary("lpdeviceutils"); } public void openDevice() throws IOException { raf = new RandomAccessFile("/dev/usb/lp0", "rw"); // 给设备设置5秒超时 setLpTimeout(raf.getFD().intValue(), 5); } // 直接写,超时由设备本身处理,返回错误而不是损坏通道 public void writeData(byte[] data) throws IOException { raf.write(data); raf.getFD().sync(); } }
然后写对应的C代码(编译成动态链接库liblpdeviceutils.so):
#include <jni.h> #include <linux/lp.h> #include <sys/ioctl.h> #include <fcntl.h> JNIEXPORT void JNICALL Java_com_yourpackage_LpDeviceHandler_setLpTimeout (JNIEnv *env, jobject obj, jint fd, jint timeout) { // 调用Linux的LPSETTIMEOUT命令设置超时 ioctl(fd, LPSETTIMEOUT, timeout); }
编译这个C代码的时候,要链接对应的头文件,比如:gcc -shared -fPIC -o liblpdeviceutils.so LpDeviceUtils.c -I$JAVA_HOME/include -I$JAVA_HOME/include/linux
这样设置后,当设备操作超时的时候,Linux内核会直接返回I/O错误,Java层捕获IOException即可,不会损坏通道,也不用重建资源,这是最优雅的方案。
最后给你几个小提醒:
- 操作
/dev/usb/lp0的时候,一定要确保程序有足够的权限,要么用sudo运行,要么给设备文件加权限(比如sudo chmod 666 /dev/usb/lp0)。 - 重建资源的时候,加个重试次数限制,别无限重试搞成死循环。
- Java 1.6确实比较老了,如果以后有机会升级版本,NIO2的FileChannel有更完善的异常处理机制,但现阶段只能用上面的办法。
内容来源于stack exchange

