You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux环境下JNI顺序调用socket与bind方法失败报errno22问题

JNI分开调用socket和bind函数报错(错误码22)问题

问题描述

我尝试在Linux机器上通过JNI建立socket连接。若将Java_Socket_socket与Java_Socket_bind的逻辑合并到同一个C函数中,JNI调用可正常运行,但按顺序分别调用两个方法时则无法正常工作。
以下是我的实现代码:

Native层C代码

#include <jni.h>        // JNI header provided by JDK
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <fcntl.h>
#include "Socket.h"   // Generated

JNIEXPORT jint JNICALL Java_Socket_socket(JNIEnv *env, jobject thisObj) {
    int sockfd;

    if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
        printf("socket creation failed...\n");
        return (jint)-1;
    } else {
        printf("Socket successfully created..\n");
        printf("Socket descriptor: %d\n", sockfd);
        return (jint) sockfd;
    }
}

JNIEXPORT jint JNICALL Java_Socket_bind(JNIEnv *env, jobject thisObj, jint sockfd, jint port) {
    struct sockaddr_in servaddr;
    bzero(&servaddr, sizeof(servaddr));

    int c_sockfd = (int) sockfd;
    short int c_port = (int) port;

    printf("socket binding sockfd - %d, port - %d\n", c_sockfd, c_port);

    // assign IP, PORT
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(c_port);
    printf("htons - %d\n", htons(c_port));


    int one = 1;
    int res = setsockopt(c_sockfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
    res = setsockopt(c_sockfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
    if (res < 0) {
        perror("setsockopt");
    }

    printf("sizeof - %d\n", sizeof(servaddr));

    if (fcntl(c_sockfd, F_GETFD) != -1 || errno != EBADF) {
        printf("fd is valid\n");
    } else {
        printf("fd is invalid\n");
    }

    // Binding newly created socket to given IP and verification
    if ((bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr))) != 0) {
        printf("socket bind failed...\n");
        printf("Error code: %d\n", errno);
        return (jint)-1;
    }
    else {
        printf("Socket successfully binded..\n");
        return (jint)0;
    }
}

Java侧代码

public class Socket {
    static {
        System.load(".../libsocket.so");
    }

    private native int socket();

    private native int bind(int sockFd, int port);

    public static void main(String[] args) {
        var socket = new Socket();
        int sockFd = socket.socket();
        System.out.println("sockFd " + sockFd);
        int bind = socket.bind(sockFd, 9900);
        System.out.println("Bind " + bind);
    }
}

运行输出

Socket descriptor: 4
socket binding sockfd - 4, port - 9900
htons - 44070
sizeof - 16
fd is valid
socket bind failed...
Error code: 22

若将上述逻辑改写为纯C程序,通过gcc编译后直接运行(执行gcc ... && a.out)不会产生任何错误。请问该问题的产生原因是什么?是否有可能是两次JNI调用间隙文件描述符被关闭导致的?

答案

首先排除文件描述符被关闭的可能:如果fd被关闭,bind会返回错误码EBADF(数值为9),和你拿到的22完全不符。
错误码22对应EINVAL,含义是传入参数无效,核心原因是socket创建时指定的地址族和bind时传入的地址结构的地址族不匹配:

  • 你在Java_Socket_socket中创建socket用的参数是AF_UNIX,这是Unix域套接字,用于本地进程间通信,使用sockaddr_un结构存储地址
  • 但在Java_Socket_bind中填充的是IPv4套接字专用的sockaddr_in结构,指定的地址族是AF_INET
    给AF_UNIX类型的socket传入AF_INET的地址结构调用bind,内核会直接判定参数非法返回EINVAL。

为什么合并函数/纯C代码运行正常

你写纯C测试、或者把两个逻辑合并到同一个JNI函数时,大概率是把socket创建的参数写成了AF_INET,和bind时的地址族匹配,所以不会报错。分开写JNI代码时误写为AF_UNIX才触发了问题。

其他代码问题

  • fd有效性判断逻辑完全错误:
    // 原逻辑:只要fcntl不返回-1,或者errno不是EBADF就判定fd有效,条件写反
    if (fcntl(c_sockfd, F_GETFD) != -1 || errno != EBADF)
    
    正确逻辑是fcntl返回值不为-1时fd才有效,原写法哪怕fd无效,只要errno不是EBADF就会误判为有效,所以打印的"fd is valid"没有参考价值。
  • 端口类型转换有隐患:你把jint类型的端口号强转为有符号short int,如果传入端口大于32767会溢出,应该用uint16_t类型存储端口。
  • 连续两次调用setsockopt设置SO_REUSEADDR属于冗余代码,没有实际意义。

修复方法

把socket创建行的AF_UNIX改为AF_INET即可:

// 修改前
sockfd = socket(AF_UNIX, SOCK_STREAM, 0)
// 修改后
sockfd = socket(AF_INET, SOCK_STREAM, 0)

重新编译动态库后就能正常运行。

内容的提问来源于stack exchange,提问作者Tipok

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 03:36:08