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
相关产品推荐
相关产品推荐

