• 通过Socket直接与RILD进行通信


    1 RIL_J与RIL_C通信

      上层通常要和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层建立连接,进行通信;

    下面就看看这个实现过程:

    2 创建socket并连接

    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/

    3 向RILD发起连接

    sendAtErrnoType setupToRild(void)
    {
            sendAtErrnoType result = E_SUCCESS;
            char recvBuffer[RECV_BUFFER_LEN] = {0};
            Parcel recvP;
            sendAtFd fd = s_send_at_fd;
    
            //向RILD发起连接        
            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;
    
                //读取长度
                int messageLength = ((recvBuffer[0] & 0xff) << 24)
                    | ((recvBuffer[1] & 0xff) << 16)
                    | ((recvBuffer[2] & 0xff) << 8)
                    | (recvBuffer[3] & 0xff);
                ALOGI("sendAt messageLength = %d",messageLength);
    
                //读取内容
                recvLen = recv(fd,recvBuffer,messageLength,0);
                recvP.setData((uint8_t*)recvBuffer,   recvLen);
    
                //解析数据
                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);
    
                //已建立连接 退出循环
                break;
            }
    
            return result;
        }

      同样需要接收RILD传递来的消息,也需要按照这种格式进行,可以另起一个线程专门来负责接收RILD发送来的消息。

     

    4 发送数据

    sendAtErrnoType sendDataToRild(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;
            
            // parcel length in big endian 转化数据长度为byte数组
            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);
    
            //发送数据长度
            res = send(fd,(const void *)dataLength,4,0);
    
            //发送数据内容
            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。

      所以要在同一个空间下才能引用。

     

    5 实现代码片段

    int main(int argc, char *argv[])
    {    
            send_at_main_init();
            
            send_at_fight_mode(1);
            
            //不能立即结束程序,否则造成ECONNRESET 104错误 connection reset by peer
            //sleep的精度是秒,usleep的精度是微妙
            sleep(3);
            
            //或者不让程序退出,进入死循环
            while(0) {
                // sleep(UINT32_MAX) seems to return immediately on bionic
                sleep(0x00ffffff);
                
            }
            return 0;
    }

    下面看几个具体的数据发送流程 和格式:

    6 拨打电话

    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);
    
            //发送数据
            sendDataToRild(p);
    }

    7 设置Radio状态

    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);
    
            //发送数据
            sendDataToRild(p);
    }

    8 编译一个可执行二进制文件

    生成到目录:/system/bin/sendAt

    adb push到手机/system/bin目录下,更改权限即可执行

    # 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 := \
    
    LOCAL_MODULE:= sendAt
    LOCAL_MODULE_TAGS := optional
    
    include $(BUILD_EXECUTABLE)
  • 相关阅读:
    揉碎HTTP编码过程,从此不乱码
    Eclipse与IDEA配置tomcat
    JavaWEB入门
    网络编程-socket
    Java
    Mysql存储过程 —— SEQUENCE的实现
    Java Servlet 2.5 设置 cookie httponly
    CountDownLatch和CyclicBarrier 区别
    ply python 图片压缩 图片裁剪 旋转
    各种正则大杂烩,正则手机,正则邮箱
  • 原文地址:https://www.cnblogs.com/bastard/p/3055043.html
Copyright © 2020-2023  润新知