上层通常要和RILD通信,是通过Socket,在RIL_JAVA层实现;
沿着这样代码流程进行Framework——native:
Phone——RIL_JAVA——>RIL_CPP
那么可不可以直接和RILD(RIL_CPP)进行通信呢?
肯定是可以的,因为通信使用的rild socket,只要通过这个socket就可以和RILD进行通信 ;
但实际中可靠的使用是不可行的,因为RILD在创建的时候,
设计初始化已经决定了RILD同时所支持的客户端的数量:
单卡仅支持一个客户端;
双卡实现方式代码提供了两种方式:
1)双卡两个RIL客户端对应一个RILD服务端,以标签SUB0和SUB1来区分,RILD中数据流程也对应两个实体;
2)一个RIL客户端对应一个RILD服务端,也就是双卡的话,就会有个多个RILD进程
因Phone进程的特殊性,常驻进程开机启动会和RILD建立连接,作为RILD客户端.
所以如果你通过socket与RILD建立连接,就会将原来的Phone与RILD的连接断开掉;
这样就可能会造成冲突,产生异常,除非你将自带的Phone删除掉;
通常第三方的拨号软件也都是在Phone的基础上实现的。
因为所有的上层代码都是通过Framework,再传递带C/C++层进行处理;
之前有一些做法是,从底层将需求发到上层,在通过上层正常的流程去调用,再传递到底层,
这本身就不是很合理的,但是却不得不这样做;
假如现在有这样一个需求,在不启动上层的情况下进行手机的功能测试,或者直接和RILD进行底层通信
比如网络通信功能,怎么做呢?
还得在底层C层直接通过socket与RILD层建立连接,进行通信;
下面就看看这个实现过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
sendAtFd createToRildFd(
void
)
{
sendAtFd fd = -
1
;
//创建socket并连接
fd = socket_local_client(SOCKET_NAME_RILD,
ANDROID_SOCKET_NAMESPACE_RESERVED,
SOCK_STREAM);
if
(fd <
0
) {
perror (
"opening radio socket"
);
exit(-
1
);
}
//保存和RILD通信的FD
s_send_at_fd = fd;
return
fd;
}
|
这里创建套接字是一个UNIX套接字,参数与网络套接字不同,
结构体用sockaddr_un,域参数应该是PF_LOCAL,通讯类型应该是SOCK_STREAM或SOCK_DGRAM
UNIX本地套接字可以参考下面文档:
http://zerocool60.blog.163.com/blog/static/35270508200772955536291/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
sendAtErrnoType setupToRild(
void
)
{
sendAtErrnoType result = E_SUCCESS;
char
recvBuffer[RECV_BUFFER_LEN] = {
0
};
Parcel recvP;
sendAtFd fd = s_send_at_fd;
<strong>
//</strong><strong>向RILD发起连接</strong>
char
* SUB1 =
"SUB1"
;
int
res = send(fd,(
const
void
*)SUB1,strlen(SUB1),
0
);
//接收RILD返回,建建立连接
while
(
1
){
sleep(
10
);
//等待接收服务端数据
int
recvLen = recv(fd,recvBuffer,RECV_BUFFER_HEAD_LEN,
0
);
if
(recvLen ==
0
)
continue
;
<strong>
//读取长度</strong>
int
messageLength = ((recvBuffer[
0
] &
0xff
) <<
24
)
| ((recvBuffer[
1
] &
0xff
) <<
16
)
| ((recvBuffer[
2
] &
0xff
) <<
8
)
| (recvBuffer[
3
] &
0xff
);
ALOGI(
"sendAt messageLength = %d"
,messageLength);
<strong>
//读取内容</strong>
recvLen = recv(fd,recvBuffer,messageLength,
0
);
recvP.setData((uint8_t*)recvBuffer, recvLen);
<strong>
//解析数据</strong>
int
type = recvP.readInt32();
int
response = recvP.readInt32();
//type: RESPONSE_UNSOLICITED / RESPONSE_SOLICITED
ALOGI(
"sendAt type = %d"
,type);
//response ID: 指RESPONSE_UNSOLICITED类型的
//对于RESPONSE_SOLICITED的可能就不是这样的了serial一类的,实际中解析时要区分对待两种类型
//数据格式可以根据RIL_JAVA参考
ALOGI(
"sendAt response = %d"
,response);
//数据内容
ALOGI(
"sendAt recvBuffer = %s"
,recvBuffer);
<strong>
//已建立连接 退出循环</strong>
break
;
}
return
result;
}
|
同样需要接收RILD传递来的消息,也需要按照这种格式进行,可以另起一个线程专门来负责接收RILD发送来的消息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
sendAtErrnoType <strong>sendDataToRild</strong>(Parcel &p)
{
unsigned
char
* sendData = NULL;
uint32 sendDataLen = p.dataSize();
unsigned
char
dataLength[
4
];
int32 res = -
1
;
sendAtErrnoType error = E_SUCCESS;
sendAtFd fd = s_send_at_fd;
<strong>
// parcel length in big endian 转化数据长度为byte数组</strong>
dataLength[
0
] = dataLength[
1
] =
0
;
dataLength[
2
] = (unsigned
char
)((sendDataLen >>
8
) &
0xff
);
dataLength[
3
] = (unsigned
char
)((sendDataLen) &
0xff
);
//获取待发送数据
sendData = (unsigned
char
*)malloc(p.dataSize());
memcpy(sendData, p.data(), sendDataLen);
<strong>
//发送数据长度</strong>
res = send(fd,(
const
void
*)dataLength,
4
,
0
);
<strong>
//发送数据内容</strong>
res = send(fd,(
const
void
*)sendData,sendDataLen,
0
);
free(sendData);
return
error;
}
|
看到RILD接收数据使用Parcel进行相关的解析,
因此数据发送的格式组织依然使用Parcel进行组织;
从上面可以看到数据读取和数据发送,都是先从数据长度开始,然后数据内容
这就是与RILD socket通信的数据格式,具体可以参考RILJ发送数据的格式。
与RILD socket通信的客户端限制权限只能为“Radio”,才可以与之进行通信。
这在RIL_CPP的listenCallback中有做限制。
但是这里如果将UID改为:setuid(AID_RADIO);时,
发现在创建socket时又会出错,不知如何解决,就纳闷了同样是radio权限为什么,
这里却不能打开rild socket。
仅仅是作为测试,于是使用include $(BUILD_EXECUTABLE)编译出来一个可执行文件
只能去更改RILD对于权限的要求。
在向rild发送了数据之后如果,程序立即就退出了,也会报相应的错误;
引起服务端报出:ECONNRESET 104错误 connection reset by peer对方复位连接;
所以要在函数中做一些延迟,不能立即结束程序运行。
在Android中使用Parcel类等某些类,需要在namespace android {}下才行。
namespace 是c++的一个标识符,表示定义一个全局空间。
android代码把整个android工程看作一个namespace。
所以要在同一个空间下才能引用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
int
main(
int
argc,
char
*argv[])
{
send_at_main_init();
send_at_fight_mode(
1
);
<strong>
//不能立即结束程序,否则造成ECONNRESET 104错误 connection reset by peer
</strong>
//sleep的精度是秒,usleep的精度是微妙
sleep(
3
);
//或者不让程序退出,进入死循环
while
(
0
) {
// sleep(UINT32_MAX) seems to return immediately on bionic
sleep(
0x00ffffff
);
}
return
0
;
}
|
下面看几个具体的数据发送流程 和格式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
void
send_at_dispatch_dial(send_at_request_params* handle_params)
{
Parcel p;
status_t status;
uint32 ril_request_dial = RIL_REQUEST_DIAL;
uint32 serial =
0
;
const
char
address[
20
] =
"15816891234"
;
uint32 clirMode =
0
;
uint32 uusInfoNone =
0
;
//打包数据
status = p.writeInt32(ril_request_dial);
status = p.writeInt32(serial);
//字符串写入跟读取方式保持一致,参考RILD中的函数
writeStringToParcel(p,address);
status = p.writeInt32(clirMode);
status = p.writeInt32(uusInfoNone);
//发送数据
<strong> sendDataToRild</strong>(p);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
void
send_at_dispatch_fight_mode(send_at_request_params* handle_params)
{
Parcel p;
status_t status;
atRadioEvent* pRadioEvent = (atRadioEvent*)handle_params->data;
//打包数据
status = p.writeInt32(pRadioEvent->ril_request_radio);
status = p.writeInt32(pRadioEvent->serial);
status = p.writeInt32(pRadioEvent->radio);
status = p.writeInt32(pRadioEvent->on);
//发送数据
<strong> sendDataToRild</strong>(p);
}
|
生成到目录:/system/bin/sendAt
adb push到手机/system/bin目录下,更改权限即可执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
# For sendAt binary
# =======================
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
sendAt.c \
sendAtUtil.cpp \
sendAtEvent.cpp \
sendAtDispatch.cpp
LOCAL_SHARED_LIBRARIES := \
libutils \
libbinder \
libcutils \
libril
LOCAL_CFLAGS := \
<strong>LOCAL_MODULE</strong>:= sendAt
LOCAL_MODULE_TAGS := optional
include $(<strong>BUILD_EXECUTABLE</strong>)
|