使用Windows命名管道使用JNI的IPC for Java程序

时间:2016-06-20 17:07:06

标签: java c winapi java-native-interface

我正在使用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
  } 
}

1 个答案:

答案 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)。

(两个引自the MSDN page on ConnectNamedPipe。)