socket进程通信命名方式有两种。一是普通的命名,socket会根据此命名创建一个同名的socket文件,客户端连接的时候通过读取该socket文件连接到socket服务端。这种方式的弊端是服务端必须对socket文件的路径具备写权限,客户端必须知道socket文件路径,且必须对该路径有读权限。
另外一种命名方式是抽象命名空间,这种方式不需要创建socket文件,只需要命名一个全局名字,即可让客户端根据此名字进行连接。后者的实现过程与前者的差别是,后者在对地址结构成员sun_path数组赋值的时候,必须把第一个字节置0,即sun_path[0] = 0。
具体实现
#include "com_apress_echo_EchoClientActivity.h"
#include "com_apress_echo_EchoServerActivity.h"
#include "com_apress_echo_LocalEchoActivity.h"
// JNI
#include <jni.h>
// NULL
#include <stdio.h>
// va_list, vsnprintf
#include <stdarg.h>
// errno
#include <errno.h>
// strerror_r, memset
#include <string.h>
// socket, bind, getsockname, listen, accept, recv, send, connect
#include <sys/types.h>
#include <sys/socket.h>
// sockaddr_un
#include <sys/un.h>
// htons, sockaddr_in
#include <netinet/in.h>
// inet_ntop
#include <arpa/inet.h>
// close, unlink
#include <unistd.h>
// offsetof
#include <stddef.h>
// 最大消息长度
#define MAX_LOG_MESSAGE_LENGTH 256
// 最大数据缓冲区大小
#define MAX_BUFFER_SIZE 80
/**
* 将给定的消息记录到应用程序
*
* @param env JNIEnv interface.
* @param obj object instance.
* @param format message format and arguments.
*/
static void LogMessage(
JNIEnv* env,
jobject obj,
const char* format,
...)
{
// 缓存日志方法id
static jmethodID methodID = NULL;
// 如果方法id未缓存
if (NULL == methodID)
{
// 从对象获取类
jclass clazz = env->GetObjectClass(obj);
// 从给定方法获取方法id
methodID = env->GetMethodID(clazz, "logMessage",
"(Ljava/lang/String;)V");
// 释放引用类
env->DeleteLocalRef(clazz);
}
// 如果找到方法
if (NULL != methodID)
{
// 格式化日志消息
char buffer[MAX_LOG_MESSAGE_LENGTH];
va_list ap;
va_start(ap, format);
vsnprintf(buffer, MAX_LOG_MESSAGE_LENGTH, format, ap);
va_end(ap);
// 将缓冲区转换为java字符串
jstring message = env->NewStringUTF(buffer);
// 如果字符串构造正确
if (NULL != message)
{
// 记录消息
env->CallVoidMethod(obj, methodID, message);
// 释放消息引用
env->DeleteLocalRef(message);
}
}
}
/**
*
* 用给定的异常类和异常消息抛出新的异常
*
* @param env JNIEnv interface.
* @param className class name.
* @param message exception message.
*/
static void ThrowException(
JNIEnv* env,
const char* className,
const char* message)
{
// 获取异常类
jclass clazz = env->FindClass(className);
// 如果异常类未找到
if (NULL != clazz)
{
// 抛出异常
env->ThrowNew(clazz, message);
// 释放原生类引用
env->DeleteLocalRef(clazz);
}
}
/**
* 用给定异常类和基于错误号的错误消息抛出新异常
*
*
* @param env JNIEnv interface.
* @param className class name.
* @param errnum error number.
*/
static void ThrowErrnoException(
JNIEnv* env,
const char* className,
int errnum)
{
char buffer[MAX_LOG_MESSAGE_LENGTH];
// 获取错误号消息
if (-1 == strerror_r(errnum, buffer, MAX_LOG_MESSAGE_LENGTH))
{
strerror_r(errno, buffer, MAX_LOG_MESSAGE_LENGTH);
}
// 抛出异常
ThrowException(env, className, buffer);
}
/**
* Constructs a new TCP socket.
*
* @param env JNIEnv interface.
* @param obj object instance.
* @return socket descriptor.
* @throws IOException
*/
static int NewTcpSocket(JNIEnv* env, jobject obj)
{
// 构造socket
LogMessage(env, obj, "Constructing a new TCP socket...");
//socket(int domain,int type,int protocol)
//domain PF_LOCAL:主机内部通信协议族,改协议族使物理上运行在同一设备上的
//应用程序可以用socket apis彼此通信
//PF_INET:internet第4版协议族,改协议族使应用程序可以与网络上其他
//运行的应用程序进行通信
//type:指定通信语义 SOCK_STREAM使用TCP协议
//SOCK_DGRAM:使用UDP协议
int tcpSocket = socket(PF_INET, SOCK_STREAM, 0);
// 检查socket构造是否正确
if (-1 == tcpSocket)
{
// T抛出带错误号的异常
ThrowErrnoException(env, "java/io/IOException", errno);
}
return tcpSocket;
}
/**
* 将socket绑定到某一端口号
*
* @param env JNIEnv interface.
* @param obj object instance.
* @param sd socket descriptor.
* @param port port number or zero for random port.
* @throws IOException
*/
static void BindSocketToPort(
JNIEnv* env,
jobject obj,