java进程间通讯机制代码
•socket
•RMI远程调用
•共享内存
•管道
进程间利用socket来通信
客户端每隔500毫秒向服务器发送一个奇数,服务器将已收到的奇数乘以2再发回给客户。
客户端程序
import java.io.*;
import java.net.*;
public class client {
public static void main(String[] args) {
String s = null;
Socket mysocket;
DataInputStream in = null;
DataOutputStream out = null;
try{
mysocket = new Socket("127.0.0.1", 4331);
in = new DataInputStream(mysocket.getInputStream());
//
/*mysocket可以使用getInputStream()方法获得一个输入流,然后用这个输入流读取服务器放入“线路”的信息,还可以使用getOutputStream()方法获得一个输出流,然后用这个输出流将信息写入“线路”*/
out = new DataOutputStream(mysocket.getOutputStream());
for(int k=1; k<10; k=k+2) { out.writeUTF("" + k);
s=in.readUTF();
System.out.println("客户收到:" + s);
Thread.sleep(500);
}
} catch(Exception e) {
System.out.println("服务器已断开" + e);
}
}
}
服务器端程序
import java.io.*;
import java.net.*;
public class server {
public static void main(String[] args) {
ServerSocket server = null;
Socket you = null;
String s = null;
DataOutputStream out = null;
DataInputStream in = null;
try{
server = new ServerSocket(4331);
}catch(IOException e) {
System.out.println(e);
}//有备注
try{
System.out.println("等待客户呼叫");
you = server.accept();
out = new DataOutputStream(you.getOutputStream());
in = new DataInputStream(you.getInputStream());
while(true) {
s = in.readUTF();
int m=Integer.parseInt(s);
out.writeUTF("你好:我是服务器");
out.writeUTF("你说的数" + m + "乘2后是:" + 2*m);
System.out.println("服务器收到:" + s);
Thread.sleep(500);
}
} catch(Exception e) {
System.out.println("客户以断开" + e);
}
}
}//有备注
先运行服务器端程序,再运行客户端程序
RMI远程方法调用
RMI(Remote MethodInvocation)是一种基于Java的分布式编程模型,为java程序提供远程访问服务接口。它允许允许在一个java虚拟机上的对象调用允许在另一台java虚拟机上的对象的方法,就像调用本地方法一样。
//创建远程接口及声明远程方法(HelloInterface.java)
import java.rmi.*;
public interface HelloInterface extends Remote {
public String say() throws RemoteException;
}
/* 远程接口必须扩展java.rmi.remote
*/
/*远程接口方法必须抛出* java.rmi.RemoteException
*/
//实现远程接口及远程方法(继承UnicastRemoteObject)(Hello.java)
import java.rmi.*;
import java.rmi,server.*;
public class Hello extends UnicastRemoteObject implements HelloInterface {
private String message;
public Hello(String msg) throws RemoteException {
Message = msg;
}
/** 远程接口方法的实现 */
public String say() throws RemoteException {
System.out.println("CalledbyHelloClient");
return message;
} //备注
}
//启动RMI注册服务,并注册远程对象(HelloServer.java)
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
public class HelloServer {
/** 启动RMI注册服务并进行对象注册 */
public static void main(String args[]) {
try{
LocateRegistry.createRegistry(1099);
HelloInterface hello = new Hello("Hello, world!");
Naming.rebind("Hello", hello);//如果要把hello实例注册到另一台启动了RMI注册服务的机器上
//Naming.rebind("//192.168.1.105:1099/Hello", hello);
System.out.println("Hello Server is ready");
} catch(Exception e) {
System.out.println("Hello Server failed: " + e);
}
}
}//备注
//客户端查找远程对象,并调用远程方法(HelloClient)
import java.rmi.Naming;
public class HelloClient {
/** 查找远程对象并调用远程方法 */
public static void main(String[] argv) {
try{
HelloInterface hello = (HelloInterface) Naming.lookup("Hello");//如果要从另一台启动了RMI注册服务的机器上查找hello实例
//HelloInterface hello=(HelloInterface)Naming.lookup("//192.168.1.105:1099/Hello");//调用远程方法
System.out.println(hello.say());
} catch(Exception e) {
System.out.println("HelloClient exception: " + e);
}
}
}
客户端查找远程对象,并调用远程方法,在客户端输出Hello, world! 在服务器端输出Called by HelloClient
内存映射
在 Windows中内存映射文件可以是脱离物理文件而存在的一块命名的内存区域,使用相同的内存映射名就能在不同的进程中共享同一片内存。然后,Java NIO的内存映射文件(MappedByteBuffer)总是与某个物理文件相关的,因为不管你是从 FileInputStream、FileOutputStream还是 RandomAccessFile 得来的 FileChannel,再 map() 得到的内存映射文件 MappedByteBuffer,如果在构造FileInputStream、FileOutputStream、RandomAccessFile 对象时不指定物理文件便会有FileNotFoundException 异常。 所以 Java NIO来实现共享内存的办法就是让不同进程的内存映射文件关联到同一个物理文件,因为 MappedByteBuffer能让内存与文件即时的同步内容。严格说来,称之为内存共享是不准确的,其实就是两个 Java进程通过中间文件来交换数据,用中间文件使得两个进程的两块内存区域的内容得到及时的同步。
知道了实现原理之后,下面用代码来演示两个进程间用内存映射文件来进行数据通信。
代码 WriteShareMemory.java往映射文件中依次写入 A、B、C ... Z。
ReadShareMemory.java逐个读出来,打印到屏幕上。代码对交换文件 swap.mm 的第一个字节作了读写标志,分别是 0-可读,1-正在写,2-可读。RandomAccessFile得到的 Channel 能够灵活的进行读或写,并且不会破坏原有文件内容,而 FileInputStream 或 FileOutputStream 取得的Channel 则很难达到这一功效,所以使用了 RandomAccessFile 来获得 FileChannel。
WriteShareMemory.java往映射文件中依次写入 A、B、C ... Z。
<pre name="code" class="java">import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
public class WriteShareMemory{
public static void main(String args[]) throws Exception {
//获得一个只读的随机存取文件对象
RandomAccessFile raf = new RandomAccessFile("D:/1.mm", "rw");
//获得相应的文件通道
FileChannel fc = raf.getChannel();
//获得共享内存缓冲区,可读写
MappedByteBuffer mbb = fc.map(MapMode.READ_WRITE, 0, 1024);
for(int i=0; i<1024; i++) {
mbb.put(i, (byte)0);
}
for(int i=65; i<91; i++) {
int index = i-63;//从文件的第二个字节开始,依次写入A-Z字母,第一个字节指明了当前操作的位置
int flag = mbb.get(0);//可读标志第一个字节为0;
if(flag != 0) {
i--;
continue;
}
mbb.put(0, (byte)1);//正在写数据,标志第一个字节为1
mbb.put(1, (byte)(index));//写数据的位置
System.out.println("程序 WriteShareMemory:" + System.currentTimeMillis() + ": 位置: " + index + " 写入数据:" + (char)i);
mbb.put(index, (byte)i);
mbb.put(0, (byte)2);
Thread.sleep(513);
}
}
}
ReadShareMemory.java 逐个读出来,打印到屏幕上。
<pre name="code" class="java">import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
public class ReadShareMemory {
public static void main(String args[]) throws Exception {
RandomAccessFile raf = new RandomAccessFile("D:/1.mm", "rw");
FileChannel fc = raf.getChannel();
MappedByteBuffer mbb = fc.map(MapMode.READ_WRITE, 0,1024);
int lastIndex = 0;
for(int i=1; i<27; i++) {
int flag = mbb.get(0); //取读写数据的标志
int index = mbb.get(1); //读取数据的位置,2为可读
if(flag != 2 || index == lastIndex) {
//加入不可读,或未写入新数据时重复循环
i--;
continue;
}
lastIndex = index;
System.out.println("程序 ReadShareMemory:" + System.currentTimeMillis() + ":位置: " + index + " 读出数据:" + (char)mbb.get(index));
mbb.put(0, (byte)0); //置第一个字节为可读标志为0
if(index == 27) {
//读完数据后退出
break;
}
}
}
}
管道
•Java程序中可以启动其他的应用程序,这种在Java中启动的进程称为子进程,启动子进程的Java程序称为父进程,其实这个父进程就是一个Java虚拟机。在Java程序中可以用Process类的实例对象来表示子进程,子进程的标准输入和输出不在连接到键盘和显示器(也就是不再接收键盘输入,和显示器输出),而是以管道流的形式连接到父进程的一个输出流和输入流对象上。
•调用Process类的getOutputStream和getInputStream方法可以获得连接到子进程的输出流和输入流对象。子进程从标准输入读到的内容就是父进程通过输出流对象写入到它们俩之间的进程管道中的数据,子进程写入的标准输出的数据通过它们之间的进程管道传递到了父进程的输入流对象中,父进程从这个输入流对象中读取到的内容就是子进程写入到标准输出的数据编程实例。
以下程序在TestInOut类中启动java.exe命令执行另外一个MyTest类,TestInOut和MyTest通过进程间的管道互相传递数据。TestInOut启动两个线程,一个不停的向MyTest中发送数据,另一个它不听的读取MyTest写会的数据。
TestInOut程序如下:
import java.io.*;
class TestInOut implements Runnable {
Process p = null;
public TestInOut() throws Exception {
p = Runtime.getRuntime().exec("java MyTest"); //启动子进程,这个程序不存在会出现问题!
new Thread(this).start();
}
public void send() throws Exception {
int i=0;
try{
OutputStream ops = p.getOutputStream(); //发送,首先要连接一个输出流对象
while(true) {
i++;
System.out.println("" + i);
if(i > 1000) {
break;
}
ops.write(("help\r\n"+(new Integer(i)).toString()).getBytes()); //写入字符串
}
} catch(Exception e) {
e.printStackTrace();
}
}
public void run() {
try{
InputStream in = p.getInputStream();
BufferedReader bfr = new BufferedReader(newInputStreamReader(in));
while(true) {
String strLine = bfr.readLine();
System.out.println(strLine);
}
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String args[]) throws Exception {
TestInOut ts = new TestInOut();
//创建对象时,子进程就启动了,接收线程启动了。
ts.send();
}
}
<pre name="code" class="java"><pre name="code" class="java"><pre name="code" class="java"><pre name="code" class="java">import java.io.*;
public class MyTest {
public static void main(String args[]) {
BufferedReader bfr = new BufferedReader(new InputStreamReader(System.in));
while(true) {
try{
String strLine = bfr.readLine();
if(strLine != null) {
System.out.println("hi:" + strLine);
} else
return ;
} catch(Exception e) {
e.printStackTrace();
}
}
}
}
允许TestInOut.java程序后的结果