Java 使用Windows';使用JNI命名管道

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的标准输入和输出流读写数据时,它

我正在使用JNI创建一个Java类,它允许在不同的Java程序之间使用各种IPC机制

我创建了一个名为
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时说“call
ConnectNamedPipe
时,您的意思是调用一个执行I/O的JNI方法吗?但是程序只是挂起并且不读取—正如预期的那样,因为没有向管道的服务器端写入任何内容。在进一步研究之前,您需要了解这一点,听起来您还不清楚Windows管道是如何工作的。(FWIW,我在回答中添加了另一种可能的解决方案。但您需要了解管道如何有效地使用它。)至于
ConnectNamedPipe
的潜在问题,我建议您暂时利用这样一个事实,即实际上调用
ConnectNamedPipe
是可选的。如果删除该调用,则不再需要使用
PIPE\u NOWAIT
,这是一个不必要的复杂问题。一旦您确定了I/O工作的选项,您就可以回到这里。