Android JNI开发系列:第十章 POSIX Socket API 本地通信

    xiaoxiao2025-08-14  7

    项目代码:https://github.com/VincentWei95/ndk

    Android JNI开发系列:第一章 JNIEnv接口指针

    Android JNI开发系列:第二章 数据类型

    Android JNI开发系列:第三章 对引用数据类型的操作

    Android JNI开发系列:第四章 异常处理

    Android JNI开发系列:第五章 局部和全局引用

    Android JNI开发系列:第六章 线程

    Android JNI开发系列:第七章 POSIX线程

    Android JNI开发系列:第八章 POSIX Socket API 面向连接的通信

    Android JNI开发系列:第九章 POSIX Socket API 无连接的通信

    Android JNI开发系列:第十章 POSIX Socket API 本地通信

    1 实现原生本地 Socket Server

    1.1 创建Socket

    socket用一个名为 socket 描述符的整数表示。除了创建socket的函数外,Socket API函数需要有效的socket描述符才能正常工作。可以用 socket 函数来创建socket。

    // 创建socket成功会返回相关socket描述符,否则返回-1且全局变量errno被相应地设置成错误值 int socket(int domain, int type, int protocol);

    参数说明:

    int domain:指定将会产生通信的socket域,并且选择将用到的协议族。Android支持以下协议族:

    PF_LOCAL:主机内部通信协议族,该协议族使物理上运行在同一台设备上的应用程序可以用Socket APIs彼此通信

    PF_INET:Internet第4版协议族,该协议族使应用程序可以与网络上其他地方运行的应用程序进行通信

    int type:指定通信的语义。支持以下几种主要的socket类型:

    SOCK_STREAM:提供使用TCP协议的、面向连接的通信Stream socket类型

    SOCK_DGRAM:提供使用UDP协议的、无连接的通信的Datagram socket类型

    int protocol:指定将会用到的协议。对于大多数协议族和类型来说,只能使用一个协议。为了选择默认协议,该参数可以设为0

    1.2 将本地socket与Name绑定

    当用socket函数创建一个socket后,该socket存在一个socket族空间中,且没为该socket分配协议地址。为了使客户能够定位到这个socket并与之相连,它需要先与一个地址绑定,可以用 bind 函数将socket与地址绑定。

    // 如果绑定成功返回0,否则返回-1且errno全局变量被设置为相应的错误值 int bind(int socketDescriptor, const struct sockaddr* address, socklen_t addressLength);

    参数说明:

    int socketDescriptor:socket描述符,指定将绑定到指定地址的socket实例

    const struct sockaddr* address:指定socket被绑定的协议地址。不同协议族使用不同的协议地址。PF_LOCAL协议族使用sockaddr_un指定协议地址:

    struct sockaddr_un { sa_family_t sun_family; char sun_path[UNIX_PATH_MAX]; };

    local socket协议地址只由一个名字构成。它没有IP地址或者端口号,可以在两个不同的命名空间中创建本地socket名。

    1、Abstract namespace:在本地socket通信协议模块中维护,socket名以NULL字符为前缀以绑定socket名

    2、Filesystem namespace:通过文件系统以一个特殊socket文件的形式维护,socket名直接传递给sockaddr_un结构,将socket名与socket绑定

    socklen_t addressLength:指定传递给函数的协议地址结构的大小

    1.3 接受本地Socket

    accept 函数用来显示地输入连接从监听队列里取出并接受它。

    accept 函数是一个阻塞函数,如果在监听队列中没有即将到来的输入连接请求,它会使调用进程进入挂起状态,直到有新的输入连接。

    与TCP Socket相同用同一个函数接收本地socket的输入连接,唯一的区别是由接收函数返回的客户端协议地址是 socketaddr_un 类型。

    // 如果成功返回与该连接实例交互时将会用到的客户socket描述符 // 否则返回-1且全局变量errno被设置为合适的错误值 int accept(int socketDescriptor, struct sockaddr* address, socklen_t* addressLength);

    参数说明:

    int socketDescriptor:指定应用程序想要从其他接受输入连接的socket实例

    struct sockaddr* address:提供了一个地址结构,在该结构中填入被连接的客户协议地址。如果应用程序不需要要该信息,它可以设置为NULL

    socklen_t* addressLength:为要填入的连接客户协议地址提供指定大小的内存空间。如果不需要该信息,它可以被设置为NULL

    2 异步I/O

    大多数socket APIs阻塞函数调用。这些函数挂起调用进程直到满足某些条件。

    socket通过 select 函数提供异步I/O。

    与其他在给定的时间内只能操作一个socket描述符的socket APIs不同,select 函数可以操作多个socket描述符并同时监控它们的状态。如果监控的一个事件发生或者到了指定的时限,则阻塞。

    要使用 select 函数,需要导入 sys/select.h 头文件。

    #include <sys/select.h> // 如果成功返回就绪的描述符数,否则返回-1且errno设置为相应的错误 int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);

    参数说明:

    int nfds:为最高编号的描述符加1,select 函数将监控 nfds 指定数量的描述符

    fd_set* freadfds:设置将被监控可读性的描述符列表集

    fd_set* writefds:设置将被监控可写性的描述符列表集

    fd_set* exceptfds:设置将被监控任何类型错误的描述符列表

    struct timeval* timeout:指定为了完成选择而阻塞当前进程的最大时间间隔。如果不需要,将其设置为NULL

    描述符列表通过fd_set结构提供:

    struct fd_set readfds;

    为了处理manipulate描述符列表,需要提供下面的宏集合:

    FD_ZERO宏:保存指向fd_set结构的指针并清除它

    FD_SET宏:保存指向fd_set结构的指针并将描述符添加到集合中

    FD_CLR宏:保存指向fd_set结构的指针并从集合中删除描述符

    FD_ISSET宏:可以在选择完成后用来检查描述符是否为选择函数返回的集合的一部分

    最新回复(0)