Java 使用Windows';使用JNI命名管道
我正在使用JNI创建一个Java类,它允许在不同的Java程序之间使用各种IPC机制 我创建了一个名为Java 使用Windows';使用JNI命名管道,java,c,winapi,java-native-interface,Java,C,Winapi,Java Native Interface,我正在使用JNI创建一个Java类,它允许在不同的Java程序之间使用各种IPC机制 我创建了一个名为WindowsIPC的类,它包含一个可以访问Windows的命名管道的本机方法。我有一个名为createNamedPipeServer()的本机函数,它调用CreateNamedPipe。它似乎已经正确地创建了管道,因为我可以使用诸如Process Explorer之类的工具查看它 我的问题是,当我在一个单独的Java程序中使用它,并使用一个单独的线程使用Java的标准输入和输出流读写数据时,它
WindowsIPC
的类,它包含一个可以访问Windows的命名管道的本机方法。我有一个名为createNamedPipeServer()
的本机函数,它调用CreateNamedPipe
。它似乎已经正确地创建了管道,因为我可以使用诸如Process Explorer之类的工具查看它
我的问题是,当我在一个单独的Java程序中使用它,并使用一个单独的线程使用Java的标准输入和输出流读写数据时,它失败了。我可以成功地将数据写入管道,但无法读取内容;它返回一个FileNotFoundException(所有管道实例都很忙)
我很难理解这一点,因为我不知道还有什么其他进程在使用管道,也不知道我在创建管道时指定了pipe\u UNLIMITED\u实例。
我已经广泛阅读了reads是如何工作的,我的直觉是,Java中的输入/输出流会处理它,因为它会返回上面提到的错误
如有任何见解,将不胜感激
代码如下:
WindowIPC.java
public class WindowsIPC {
public native int createNamedPipeServer(String pipeName);
static {
System.loadLibrary("WindowsIPC");
}
public static void main(String[] args) {
// some testing..
}
}
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
}
}
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
public class WindowsIPC {
public native int createNamedPipeServer(String pipeName);
static {
System.loadLibrary("WindowsIPC");
}
public static void main(String[] args) {
// some testing..
}
}
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
}
}
出现“all pipe instances is busy”(所有管道实例都忙)错误的原因是您连接到管道两次(一次用于读取,一次用于写入),但您只创建了一个实例。(请注意,使用“管道\无限\实例”选项可以创建任意数量的实例,但仍需自己创建。)
从外观上看,您希望调用FileInputStream
打开管道的服务器端。这不是它的工作原理。必须使用从CreateNamedPipe
返回的句柄才能访问管道的服务器端
在JNI中是否有一种直接的、受支持的方式将句柄转换为流,我不知道(据我所知似乎没有),但请注意,它是一个非阻塞句柄这一事实很可能是一个复杂的问题,因为Java几乎肯定不会想到这一点
一种更有希望的方法是实现调用JNI方法来执行实际I/O的InputStream
和/或OutputStream
类
附录:如果您不想使用JNI,并且找不到任何更可接受的方式将本机句柄转换为流,原则上您可以启动一个(本机)线程,将两个单独管道的服务器端连接在一起,从而允许客户端彼此通信。我不确定这是否会比使用JNI更好,但我认为这可能值得一试
您的代码还有一个技术问题:在非阻塞模式下,您需要反复调用ConnectNamedPipe
,直到它报告管道已连接:
请注意,只有在收到错误\u PIPE\u CONNECTED错误后,客户端和服务器之间才存在良好的连接
实际上,如果您不打算将管道实例重新用于另一个客户机,那么您可能不需要这样做就可以逃脱。Windows隐式连接任何给定管道实例的第一个客户端,因此根本不需要调用ConnectNamedPipe
。但是,您应该注意,这是一个未记录的特性
当Java代码第一次要求您进行I/O时,使用普通I/O并发出对ConnectNamedPipe
的调用可能更有意义;程序员可能希望读和/或写操作无论如何都会被阻塞
如果您不想使用普通I/O,您应该更喜欢异步I/O而不是非阻塞I/O:
为了与Microsoft LAN Manager 2.0版兼容,支持非阻塞模式,并且不应使用非阻塞模式通过命名管道实现异步输入和输出(I/O)
(引用自。)FileOutputStream/FileInputStream可以作为服务器访问命名管道吗?如果它在内部使用CreateFile,它将仅作为客户端进行连接,这意味着没有服务器可以通过命名管道写入任何数据。在createNamedPipeServer()
函数中,即使pipeHandle
无效,您始终调用ConnectNamedPipe
。我不认为这会导致您看到的问题,但您可能无论如何都应该解决它。@AndrewHenle注意到这一点-谢谢。谢谢您的回复。如果我尝试使用createNamedPipeServer
创建另一个管道实例,它将消除所有管道忙的问题,但程序只是挂起而不读取。实际上,我希望尽量避免使用JNI来执行实际的I/O,因为这样做会造成很大的性能损失。当您在Java代码第一次要求您执行I/O时说“callConnectNamedPipe
时,您的意思是调用一个执行I/O的JNI方法吗?但是程序只是挂起并且不读取—正如预期的那样,因为没有向管道的服务器端写入任何内容。在进一步研究之前,您需要了解这一点,听起来您还不清楚Windows管道是如何工作的。(FWIW,我在回答中添加了另一种可能的解决方案。但您需要了解管道如何有效地使用它。)至于ConnectNamedPipe
的潜在问题,我建议您暂时利用这样一个事实,即实际上调用ConnectNamedPipe
是可选的。如果删除该调用,则不再需要使用PIPE\u NOWAIT
,这是一个不必要的复杂问题。一旦您确定了I/O工作的选项,您就可以回到这里。