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

    xiaoxiao2025-07-21  18

    项目代码: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 实现原生TCP 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与一个地址绑定

    当用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_INET协议族使用sockaddr_in_structure指定协议地址:

    struct sockaddr_in { sa_family)t sin_family; unsigned short int sin_port; struct in_addr sin_addr; }; socklen_t addressLength:指定传递给函数的协议地址结构的大小

    1.3 网络字节排序

    在硬件层上,不同的机器体系结构使用不同的数据排序和表示规则,者被称为机器字节排序或字节序。例如:

    Big-endian字节顺序首先存储最重要的字节

    Little-endian字节顺序首先存储不重要的字节

    字节排序规则不同的机器不能直接交换数据。为了使字节排序规则不同的机器能在网络上通信,IP将big-endian字节排序声明为官方的数据传输网络字节排序规则。

    由于java虚拟机已经在使用big-endian字节排序,java应用程序在进行跨网络通信时,不一定要做数据转换。与此相反,因为java虚拟机不执行原生组件,所以它们使用机器字节排序:

    ARM和x86机器结构使用little-endian字节排序

    MIPS机器结构使用big-endian字节排序

    在网络上通信时,原生代码需要在机器字节排序和网络字节排序间做必要的转换。

    socket库提供了一组便利函数,使原生应用程序可以透明地处理字节排序转换,函数通过 sys/endian.h 头文件声明。

    #include <sys/endian.h>

    头文件提供了以下便利函数:

    htons函数:将 unsigned short 从主机字节排序转换到网络字节排序

    ntohs函数:和 htons 函数相反,将 unsigned short 从网络字节排序转换到主机字节排序

    htonl函数:将 unsigned integer 从主机字节排序转换到网络字节排序

    ntohl函数:和 htonl 函数相反,将 unsigned integer 从网络字节排序到主机字节排序

    1.4 监听进入的连接

    启动给定socket上输入连接的监听。

    // 如果成功返回0,否则返回-1且errno全局变量被设置为相应的错误值 int listen(int socketDescriptor, int backlog);

    参数说明:

    int socketDescriptor:指定应用程序想要监听的输入连接socket实例

    int backlog:指定保存挂起的输入连接的队列大小。如果应用程序正在忙于为客户服务,其他输入连接就要排队,队列中挂起的连接数的最大值由 backlog 指定。当输入连接达到 backlog 所限定的值时,其他的输入连接将被拒绝

    1.5 接受传入连接

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

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

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

    参数说明:

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

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

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

    1.6 从socket接收数据

    recv 函数也是一个阻塞函数,如果没有从给定的socket接收到数据,它会调用进程进入挂起状态,直到接收到可用数据。

    // 如果成功返回从socket那里接收到的字节数,否则返回-1且全局变量errno将被设置为相应的错误值 // 如果返回0表示socket连接失败 ssize_t recv(int socketDescriptor, void* buffer, size_t bufferLength, int flags);

    参数说明:

    int socketDescriptor:指定应用程序想要从中接收数据的socket实例

    void* buffer:指向内存地址的指针,该内存用来保存从socket接收的数据

    size_t bufferLength:指定缓冲区大小,recv 函数只会向缓冲区写入该参数指定大小的内容然后返回

    int flags:指定接收所需要的额外标志

    1.7 向socket发送数据

    send 函数也是一个阻塞函数,如果socket在忙着发送数据,它会使调用进程进入挂起状态直到socket可以传输数据。

    // 如果成功返回传送的字节数,否则返回-1且全局变量errno将被设置为相应的错误值 // 如果返回0表示socket连接失败 ssize_t send(int socketDescriptor, void* buffer, size_t bufferLength, int flags);

    参数说明:

    int socketDescriptor:指定应用程序想要向其发送数据的socket实例

    void* buffer:指向内存地址的buffer指针,该内存是给定的socket发送数据的目的地

    size_t bufferLength:指定缓冲区大小。send 函数只会向缓冲区传输该参数所指定大小的数据然后返回

    int flags:指定发送所需要的额外标志

    2 实现原生TCP Client

    通过提供协议地址来连接socket和server socket,由 connect 函数完成。

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

    参数说明:

    int socketDescriptor:指定应用程序想要连接协议地址的socket实例

    const struct sockaddr* address:指定socket要连接的协议地址

    socklen_t addressLength:指定所提供的地址结构的长度

    最新回复(0)