首页 > 解决方案 > 为什么从 JNI 调用 SocketCAN bind() 调用总是返回 0?

问题描述

我正在尝试创建要在 Android 环境中使用的 CAN 总线的 Java 包装 C 实现。我使用 SocketCAN 包括在 Linux 内核中来创建一个套接字并将其绑定到 CAN 接口。

开发环境没有物理 CAN 总线,因此我通过sudo ip link add dev vcan0 type vcanand创建了一个虚拟总线sudo ip link set up vcan0

在此环境中运行本机 C 代码按预期工作,当接口存在时套接字绑定,不存在时返回错误。但是,当通过 JNI 运行相同的本机 C 代码时,bind(...)无论接口的状态如何,调用总是返回 0,尽管任何后续write(...)调用都按预期失败。

有什么我忽略的东西意味着这种情况吗?

JNI 代码如下(直接从我的 C 实现中提取,必要时还进行了额外的类型转换):

JNIEXPORT jboolean JNICALL Java_SocketCAN_nativeOpen
  (JNIEnv * env, jobject jobj, jint bus_id)
{
  if ((int) bus_id < MAX_NUMBER_OF_CAN_BUSES)
  {
    int s;

    if((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) == -1) 
    {
      printf("Error while opening socket\n");
      return JNI_FALSE;
    }

    struct sockaddr_can addr;
    struct ifreq ifr;

    strcpy(ifr.ifr_name, "vcan0");
    ioctl(s, SIOCGIFINDEX, &ifr);
    
    addr.can_family  = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;

    if(bind(s, (struct sockaddr *)&addr, sizeof(addr)) == -1) 
    {
      printf("Error in socket bind\n");
      return JNI_FALSE;
    }

    // Set the socketId in the Java class.
    jclass   jcls        = (*env)->FindClass(env, "SocketCAN");
    jfieldID socket_id   = (*env)->GetFieldID(env, jcls, "socket", "I");
    jint     j_socket_id = (*env)->GetIntField(env, jobj, socket_id);
    j_socket_id = s;
    (*env)->SetIntField(env, jobj, socket_id, j_socket_id);

    return JNI_TRUE;
  }
  
  return JNI_FALSE;
}

非常感谢任何帮助,谢谢!

编辑:如果有人似乎遇到了这个奇怪的问题并想要一个解决方法(尽管它可能是正确的方法并且我忽略了它),请检查ioctl(...)函数调用的返回值。当同时运行 C 和 JNI 时未设置“vcan0”时返回 -1。

修改@12431234123412341234123 和@AndrewHenle 提出的建议后,我的更新代码如下:

JNIEXPORT jint JNICALL Java_SocketCAN_nativeOpen
  (JNIEnv * env, jobject jobj, jint bus_id)
{
  if ((int) bus_id < MAX_NUMBER_OF_CAN_BUSES)
  {
    int s;

    if((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) == -1) 
    {
      printf("Error while opening socket\n");
      return -1;
    }

    struct sockaddr_can addr;
    struct ifreq ifr;

    strcpy(ifr.ifr_name, "vcan0");
    if (ioctl(s, SIOCGIFINDEX, &ifr) == -1)
    {
      printf("Error in ioctl\n");
      return -1;
    }
    
    addr.can_family  = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;

    if(bind(s, (struct sockaddr *)&addr, sizeof(addr)) == -1) 
    {
      printf("Error in socket bind\n");
      return -1;
    }

    return (jint) s;
  }
  
  return -1;
}

标签: javacjava-native-interfacecan-bussocketcan

解决方案


bind()调用需要一个适当的接口索引can_ifindex。要获得此值,您可以像您一样使用ioctl()带有 的调用SIOCGIFINDEX。但是,当ioctl()调用失败时,该ifreq结构不一定具有正确的索引,它可能仍然具有来自之前占用相同内存区域的最后一个对象的“随机”值。因为您忽略了 from 的返回值,所以您使用“随机”接口索引进行ioctl()调用。bind()这也意味着绑定可能会失败,也可能不会失败,具体取决于值,因为在大多数情况下使用未初始化的值是 UB。为避免此错误,请检查 from 的返回值ioctl()并相应地处理错误。

对于普通 C 版本,这个“随机”值似乎与 JNI 版本不同。避免这种随机差异的一种方法是将每个新的自动对象直接设置为一个值。在您的情况下,您可以将所有内容设置为 0: struct ifreq ifr={0};,对于addr. 这个额外的步骤可以获得更一致的行为。


推荐阅读