我正在使用JNI创建一个Java类,它允许在不同的Java程序之间使用各种IPC机制。
我创建了一个名为WindowsIPC
的类,其中包含一个可以访问Windows的命名管道的本机方法。我有一个名为createNamedPipeServer()
的本机函数调用CreateNamedPipe
。它似乎已正确创建管道,因为我可以使用Process Explorer等工具查看它。
我的问题是,当我在一个单独的Java程序中使用它并利用一个单独的线程来使用Java的标准输入和输出流来读写数据时,它就会失败。我可以将数据成功写入管道,但无法读取内容;它以FileNotFoundException (All pipe instances are busy)
返回。
我正在努力解决这个问题,因为我无法弄清楚其他进程正在使用管道以及我在创建管道时指定PIPE_UNLIMITED_INSTANCES
的事实。
我已经大量阅读了如何读取工作,我的预感是Java中的输入/输出流处理它,因为它返回了上面提到的错误。
非常感谢任何见解。
以下是代码:
WindowIPC.java
public class WindowsIPC {
public native int createNamedPipeServer(String pipeName);
static {
System.loadLibrary("WindowsIPC");
}
public static void main(String[] args) {
// some testing..
}
}
WindowsIPC.c
const jbyte *nameOfPipe; // global variable representing the named pipe
HANDLE pipeHandle; // global handle..
JNIEXPORT jint JNICALL Java_WindowsIPC_createNamedPipeServer
(JNIEnv * env, jobject obj, jstring pipeName) {
jint retval = 0;
char buffer[1024]; // data buffer of 1K
DWORD cbBytes;
// Get the name of the pipe
nameOfPipe = (*env)->GetStringUTFChars(env, pipeName, NULL);
pipeHandle = CreateNamedPipe (
nameOfPipe, // name of the pipe
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE |
PIPE_READMODE_BYTE |
PIPE_NOWAIT, // forces a return, so thread doesn't block
PIPE_UNLIMITED_INSTANCES,
1024,
1024,
0,
NULL
);
// error creating server
if (pipeHandle == INVALID_HANDLE_VALUE) retval = -1;
else printf("Server created successfully: name:%s\n", nameOfPipe);
// waits for a client -- currently in ASYC mode so returns immediately
jboolean clientConnected = ConnectNamedPipe(pipeHandle, NULL);
(*env)->ReleaseStringUTFChars(env, pipeName, nameOfPipe);
return retval;
}
最后 TestWinIPC.java
import java.io.*;
import java.util.Scanner;
public class TestWinIPC {
public static void main (String[] args)
{
WindowsIPC winIPC = new WindowsIPC();
// TEST NAMED PIPES
final String pipeName = "\\\\.\\Pipe\\JavaPipe";
if (winIPC.createNamedPipeServer(pipeName) == 0) {
System.out.println("named pipe creation succeeded");
Thread t = new Thread(new NamedPipeThread(pipeName));
t.start();
try {
System.out.println("opening pipe for input");
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(pipeName)));
System.out.println("waiting to read");
String line = br.readLine();
System.out.println("Read from pipe OK: " + line);
br.close();
}
catch (IOException exc) {
System.err.println("I/O Error: " + exc);
exc.printStackTrace();
}
}
} //main
private static class NamedPipeThread implements Runnable {
private String pipeName;
public NamedPipeThread (String pipeName) {
this.pipeName = pipeName;
} // constructor
public void run () {
try {
PrintWriter pw = new PrintWriter(new FileOutputStream(pipeName));
pw.println("Hello Pipe");
System.out.println("Wrote to named pipe OK");
pw.close();
}
catch (IOException exc) {
System.err.println("I/O Error: " + exc);
exc.printStackTrace();
}
} // run
}
}
答案 0 :(得分:2)
你得到一个"所有管道实例都很忙的原因"错误是您连接管道两次(一次用于读取,一次用于写入),但您只创建了一个实例。 (请注意,使用PIPE_UNLIMITED_INSTANCES选项可以创建任意数量的实例,但您仍然必须自己创建它们。)
从它的外观来看,你期望调用FileInputStream
来打开管道的服务器端。这不是它的工作原理。您必须使用从CreateNamedPipe
返回的句柄才能访问管道的服务器端。
是否有一种直接的,支持的方式将句柄转换为JNI中的流我不知道(似乎没有我能说的那么多)但请注意它是一个事实非阻塞句柄很可能是一个复杂因素,因为Java几乎肯定不会期待它。
更有前途的方法是实现调用JNI方法来执行实际I / O的InputStream
和/或OutputStream
类。
附录:如果您不想使用JNI,并且无法找到将原生句柄转换为流的更可接受的方式,您原则上可以启动(本机)线程将两个独立管道的服务器端绑定在一起,允许客户端彼此通信。我不确定它会比使用JNI更好,但我想这可能值得尝试。
您的代码还有另外一个技术问题:在非阻止模式下,您需要反复调用ConnectNamedPipe
,直到它报告管道已连接为止:
请注意,只有在收到ERROR_PIPE_CONNECTED错误后,客户端和服务器之间才能建立良好的连接。
在实践中,如果您不打算将管道实例重用于其他客户端,则可能无法执行此操作。 Windows隐式连接任何给定管道实例的第一个客户端,因此您根本不需要调用ConnectNamedPipe
。但是,您应该注意这是一个未记录的功能。
使用普通I / O并在Java代码第一次要求您执行I / O时向ConnectNamedPipe
发出调用可能更有意义;据推测,程序员无论如何都会期望读取和/或写入操作。
如果您不想使用普通I / O,您应该更喜欢异步I / O到非阻塞I / O:
支持非阻塞模式以与Microsoft LAN Manager 2.0版兼容,并且不应使用它来实现具有命名管道的异步输入和输出(I / O)。