使用RandomAccessFile在两个java进程之间传递数据
大部分情况下,我们面对在两个java进程只见传递数据的问题时,第一个想到的就是开server,然后通过socket收发消息。这方面有大量的框架可用,就不细说了。
但如果两个进程是在一台机器上,那么还可以使用另一种方式来传递数据,那就是使用RandomAccessFile的文件映射模式。
RandomAccessFile的map方法把文件映射到内存中进行快速读写。因此可以通过把消息包写入到文件中,然后另一个进程读取出来的方式来完成数据传递。
在这个过程中,重点是要考虑什么时候写入完成,只有写入完成后,读取进程才可以去读数据,否则就是错误的数据。具体方案如下:
保留文件的首个byte作为标志位,1表示A进程写入完毕,0表示B进程读取完毕。
步骤1:A进程开始写入时,先跳过首字节,然后将数据长度,数据内容写入到文件。写入完毕后,将文件的首字节置为1
步骤2:B进程定时刷新文件到内存中,读取首字节,如果是0则跳出,等待下次刷新。如果是1表示有新数据,则加载。加载完毕后设置首字节为0.表示已经读取完毕。
步骤3:A进程定时刷新文件到内存,读取首字节,如果是0,表示可以写入,继续步骤1的流程。如果是1,则表示数据还没被读取,不可写入。等待下次刷新。
经过以上3个步骤,完成数据的进程间传递。此模式也可以用于将数据传递到非java进程。
代码如下:
public class TestRandomAccessFile { private static String FileName="aa.a"; private static volatile Linux1 lx1; private static volatile Linux2 lx2; private static long from; private static void close(Closeable c){ try{ c.close(); }catch(IOException ie){ ie.printStackTrace(); } } public static class Linux1{ MappedByteBuffer buf; RandomAccessFile raf; public Linux1(){ try{ raf = new RandomAccessFile(FileName, "rw"); buf = raf.getChannel().map(MapMode.READ_WRITE, 0, 1024*1024); }catch(IOException ie){ ie.printStackTrace(); } new Thread(()->{ try{ while(true){ readAndWrite(); } }catch(IOException e){ e.printStackTrace(); } }).start(); } private void readAndWrite() throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); String s = reader.readLine(); if(s != null){ if("exit".equalsIgnoreCase(s)){ exitAll(); }else{ write(s); } } } private void write(String s){ if(s.length() == 0)return; buf.load(); buf.position(0); byte mark = buf.get(); while(mark != 0){ buf.load(); mark = buf.get(); } if(s != null && s.length() > 0){ buf.putShort((short)s.length()); for (int i = 0; i< s.length(); i++){ buf.putChar(s.charAt(i)); } buf.position(0); buf.put((byte)1); //加可读标记 buf.force(); from = System.nanoTime(); } } public void exit(){ close(raf); } } public static class Linux2{ MappedByteBuffer buf; RandomAccessFile raf; public Linux2(){ try{ raf = new RandomAccessFile(FileName, "rw"); buf = raf.getChannel().map(MapMode.READ_WRITE, 0, 1024*1024); }catch(IOException e){ e.printStackTrace(); } new Thread(()->{ while(true){ checkAndRead(); if(from > 0){ long t = (System.nanoTime()-from); System.out.println("time=" + t); from = -1; } try{ TimeUnit.NANOSECONDS.sleep(1); }catch(Exception e){ e.printStackTrace(); } } }).start(); } private void checkAndRead(){ buf.load(); if(buf.remaining() == 0)return; buf.position(0); byte mark = buf.get(); if(mark == 0)return; int len = buf.getShort(); if(len > 0){ char[] cs = new char[len]; for (int i = 0; i< len; i++){ cs[i] = buf.getChar(); } // /System.out.println("***** " + new String(cs)); } buf.position(0); buf.put((byte)0); } public void exit(){ close(raf); } } public static void exitAll(){ lx1.exit(); lx2.exit(); System.exit(0); } public static void main(String[] args){ lx1 = new Linux1(); lx2 = new Linux2(); } }
测试下来,使用netty走socket传数据,时间在0.5-1ms之间。用RandomAccessFile模式传数据,则依赖于轮询快慢。由于java定时器的误差,及时是用Thread.sleep(0,1)的方式,我们也只能做到平均1ms左右的延迟。但如果是不sleep(现实不能如此,会导致单核被完全占用),那么延迟可以在0.001ms。所以如果能找到一种更有效的轮询方法,那么使用RandomAccessFile进行进程间数据传输的效率会更高,如果不能,那还不如就Socket模式吧,更通用。