jicmp 添加 ping 超时

发布时间 2024-01-11 17:05:54作者: YangDanMua

把 jicmp 中 src 目录下的代码 copy 新建一个 maven 项目
将编译后的 jicmp 下的 pom 文件内容复制过来

IcmpSocket 只加载一次 lib

将加载 lib 的代码移动到静态代码块里,原来是 new 的时候每次都处理

static {
  String property = System.getProperty(PROPERTY_NAME);

  boolean loaded = false;
  if (property != null) {
    log().debug("System property '" + PROPERTY_NAME + "' set to '" + property + ".  Attempting to load " + LIBRARY_NAME + " library from this location.");
    try {
      System.load(property);
      loaded = true;
    } catch (final UnsatisfiedLinkError e) {
      log().info("Failed to load library " + property + ".");
    }
  }

  if (!loaded) {
    log().debug("Attempting to load library using System.loadLibrary(\"" + LIBRARY_NAME + "\").");
    System.loadLibrary(LIBRARY_NAME);
  }

  log().debug("Successfully loaded " + LIBRARY_NAME + " library.");
}

超时 receive

我打算超时时抛出 SocketTimeoutException

/**
 * This method is used to receive the next ICMP datagram from the operating
 * system. The returned datagram packet's address is set to the sending
 * host's address. The port number is always set to Zero, and the buffer is
 * set to the contents of the raw ICMP message.
 *
 * @param timeout 读取超时时间,ms
 * @throws IOException Thrown if an error occurs reading the next ICMP message.
 */
public native DatagramPacket receive(int timeout) throws IOException, SocketTimeoutException;

生成 jni 头文件代码

cd 到 src\main\java 目录下
javah -classpath . org.opennms.protocols.icmp.IcmpSocket 会在当前目录生成 org_opennms_protocols_icmp_IcmpSocket.h,复制生成新增的 C 方法声明

/*
 * Class:     org_opennms_protocols_icmp_IcmpSocket
 * Method:    receive
 * Signature: (I)Ljava/net/DatagramPacket;
 */
JNIEXPORT jobject JNICALL Java_org_opennms_protocols_icmp_IcmpSocket_receive__I
  (JNIEnv *, jobject, jint);

C 代码改动

相应方法声明放到 C++ 项目 org_opennms_protocols_icmp_IcmpSocket.h 头文件里

IcmpSocket.c 改动代码

/*
* Class:     org_opennms_protocols_icmp_IcmpSocket
* Method:    receive
* Signature: ()Ljava/net/DatagramPacket;
*/
JNIEXPORT jobject JNICALL
Java_org_opennms_protocols_icmp_IcmpSocket_receive (JNIEnv *env, jobject instance)
{
	return receive(env, instance, 0);
}

/*
 * Class:     org_opennms_protocols_icmp_IcmpSocket
 * Method:    receive
 * Signature: (I)Ljava/net/DatagramPacket;
 */
JNIEXPORT jobject JNICALL Java_org_opennms_protocols_icmp_IcmpSocket_receive__I
(JNIEnv* env, jobject instance, jint timeout)
{
	return receive(env, instance, timeout);
}

将原来的 receive 和带超时时间的 receive 同时调用 receive,原来的代码移动到抽取出的方法里,然后在 recvfrom 之前设置超时时间参数

然后在本身就有的 SOCKET_ERROR 错误处理里面单独处理超时错误
setsockopt(fd_value, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(jint)); 超时设置,本来使用结构体 TIMEVAL,设置秒和微秒字段,但是发现不行,不知道为啥。

// receive timeout set
if (timeout > 0)
{
  iRC = setsockopt(fd_value, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(jint));
  if (iRC == SOCKET_ERROR)
  {
    // error set socket config
    char errBuf[256];
    int savedErrno = errno;
    snprintf(errBuf, sizeof(errBuf), "Error set receive timeout (iRC = %d, fd_value = %d, %d, %s)", iRC, fd_value, savedErrno, strerror(savedErrno));
    throwError(env, "java/io/IOException", errBuf);
    goto end_recv;
  }
}


/**
* Receive the data from the operating system. This
* will also include the IP header that precedes
* the ICMP data, we'll strip that off later.
*/
iRC = recvfrom(fd_value, inBuf, MAX_PACKET, 0, (struct sockaddr*)&inAddr, &inAddrLen);
// 出错
if (iRC == SOCKET_ERROR)
{
  int savedErrno = errno;
  char errBuf[256];
  if (timeout > 0)
  {
    // 超时错误
    if (savedErrno == WSAETIMEDOUT)
    {
      // 错误信息复制原来的,一般应该实际不会暴露出详细信息
      snprintf(errBuf, sizeof(errBuf), "Reading data timeout (iRC = %d, fd_value = %d, %d, %s)", iRC, fd_value, savedErrno, strerror(savedErrno));
      // 抛出超时异常
      throwError(env, "java/net/SocketTimeoutException", errBuf);
      goto end_recv;
    }
  }

  /*
  * Error reading the information from the socket
  */
  snprintf(errBuf, sizeof(errBuf), "Error reading data from the socket descriptor (iRC = %d, fd_value = %d, %d, %s)", iRC, fd_value, savedErrno, strerror(savedErrno));
  throwError(env, "java/io/IOException", errBuf);
  goto end_recv;
}

问题:如果我不删掉 C 里面原来的 Java_org_opennms_protocols_icmp_IcmpSocket_receive 方法,则无论 Java 是否传参都会调用它,而我删掉这个方法时才会显式调用有参方法

解决:经过对比发现,新增的方法是重载原来的,于是原来的那个 C 的方法名也变了,Java_org_opennms_protocols_icmp_IcmpSocket_receive -> Java_org_opennms_protocols_icmp_IcmpSocket_receive__

于是在 Win 上终于可以了