Android环境下通过SOCKET传递Parcel包并解出数据的例子

之前做过了在android下通过socket发送数据的实验,也做过了parcel包的制作和解包的实验(这两个实验的源程序之前都在本博客的其他文章中贴过)。昨天和今天把这两个过程合并了起来:即在Android环境下,甲程序(C++程序)将数据封装在Parcel中,并把Parcel发送到SOCKET;乙程序(C++程序)通过SOCKET接收到Parcel包,并解出封装在其中数据。

原先认为分开的过程都做过了,合并起来应该不是什么问题。但昨天一下午却未能成功,一直到今天上午才基本上弄了出来。现将程序的源代码放在下面:

程序里面的一些无用的调试信息没有去掉,都已经注释掉了,不影响编译运行。如果觉得影响阅读,可先复制下来,再将其中的调试信息删除即可。

/****************** server program (server.cpp)*****************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <sys/un.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <utils/Parcel.h>
using namespace android;
Parcel p;
void write_Parcel();
int main()
{
    int sockfd,newfd,ret,send_num,send_num_total=0;
    char buf[200];
    struct sockaddr_in server_addr;
//    remove("/data/server.socket");/*不管有没有,先删除一下,否则如果该文件已经存在的的话,bind会失败。*/
//    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family=AF_INET;
//    strcpy(server_addr.sin_path,"/data/server.socket");
    server_addr.sin_addr.s_addr=INADDR_ANY;
    server_addr.sin_port=htons(5678);
    sockfd=socket(AF_INET,SOCK_STREAM,0);
    printf("this is the socket_server *TCP* mode.\n");
    if (sockfd<0)
    {
        printf("调用socket函数建立socket描述符出错!\n");
         exit(1);
    }
    printf("@调用socket函数建立socket描述符成功!\n");
    ret=bind(sockfd,(struct sockaddr *)(&server_addr),sizeof(server_addr));
    if (ret<0)
    {
        printf("调用bind函数绑定套接字与地址出错!\n");
         exit(2);
    }
    printf("调用bind函数绑定套接字与地址成功!\n");
    ret=listen(sockfd,4);
    if (ret<0)
    {
        printf("调用listen函数出错,无法宣告服务器已经可以接受连接!\n");
         exit(3);
    }
    printf("调用listen函数成功,宣告服务器已经可以接受连接请求!\n");
    newfd=accept(sockfd,NULL,NULL);/*newfd连接到调用connect的客户端*/
    if (newfd<0)
    {
        printf("调用accept函数出错,无法接受连接请求,建立连接失败!\n");
         exit(4);
    }
    printf("调用accept函数成功,服务器与客户端建立连接成功!\n");

    write_Parcel();

    while (1)
    {
        send_num=send(newfd,p.data(), p.dataSize(),MSG_DONTWAIT);
        if (send_num<0)
            printf("调用send函数失败!");
        else
        {
            send_num_total+=send_num;
            printf("调用send函数成功,本次发送%d个字节。目前共发送了%d个字节的数据。\n",send_num,send_num_total);
        }
        sleep(2);
    }
}

/****************** 向Parcel内写入数据 ********************/
void write_Parcel()
{
    status_t status;
    size_t parcel_position;
    int intp=987654;
    char charp='g';
    float floatp=3.14159;
    double doublep=12345.6789012;
    long longp=1234567890;
    char *stringp="this is my parcel test! this is my parcel test! this is my parcel test! this is my parcel test!";

//    printf("1:Parcel包的大小是%d字节,Parcel结构体的大小是%d字节\n",sizeof(p),sizeof(Parcel));
//    readParcel(&p);
    parcel_position = p.dataPosition();/*备份位置*/
//    printf("当前Parcel的读写位置是:%d\n",parcel_position);
    /*************写int类型***************/
    status=p.writeInt32(intp);
    if (status==NO_ERROR)
        printf("write int type success!\n");
    else
        printf("write int type fail,the errno=%d\n",errno);
    /*************写char类型***************/
    status=p.writeInt32(charp);
    if (status==NO_ERROR)
        printf("write char type success!\n");
    else
        printf("write char type fail,the errno=%d\n",errno);
    /*************写Float类型***************/
    status=p.writeFloat(floatp);
    if (status==NO_ERROR)
        printf("write Float type success!\n");
    else
        printf("write Float type fail,the errno=%d\n",errno);
    /*************写Double类型***************/
    status=p.writeDouble(doublep);
    if (status==NO_ERROR)
        printf("write Double type success!\n");
    else
        printf("write Double type fail,the errno=%d\n",errno);
    /*************写long类型***************/
    status=p.writeInt64(longp);
    if (status==NO_ERROR)
        printf("write long type success!\n");
    else
        printf("write long type fail,the errno=%d\n",errno);
    /*************写String类型***************/
    status=p.writeCString(stringp);
    if (status==NO_ERROR)
        printf("write String type success!\n");
    else
        printf("write String type fail,the errno=%d\n",errno);

//    printf("2:Parcel包的大小是%d字节,Parcel结构体的大小是%d字节\n",sizeof(p),sizeof(Parcel));
//    readParcel(&p);
    
    /*************将parcel读写位置置回原位***************/
    p.setDataPosition(parcel_position);
    printf("3:Parcel包的大小是%d字节,Parcel结构体的大小是%d字节\n",sizeof(p),sizeof(Parcel));
//    readParcel(&p);
    /*************读出变量***************/
    printf("读出的int类型变量为:%d\n",p.readInt32());
    printf("读出的char类型变量为:%c\n",(char)p.readInt32());
    printf("读出的Float类型变量为:%f\n",(float)p.readFloat());
    printf("读出的Double类型变量为:%f\n",(double)p.readDouble());
    printf("读出的long类型变量为:%ld\n",(long)p.readInt64());
    printf("读出的字符串为:%s\n",p.readCString());
    printf("4:Parcel包的大小是%d字节,Parcel结构体的大小是%d字节\n",sizeof(p),sizeof(Parcel));
//    readParcel(&p);
}


/****************** client program (client.cpp)*****************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <sys/un.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <utils/Parcel.h>
using namespace android;
Parcel p;
int main()
{
    int sockfd,ret,recv_num,recv_num_total=0;
    char buf[ 124 ];
    struct sockaddr_in server_addr;
//    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family=AF_INET;
//    strcpy(server_addr.sin_path,"/data/server.socket");
    server_addr.sin_addr.s_addr=INADDR_ANY;
    server_addr.sin_port=htons(5678);
    sockfd=socket(AF_INET,SOCK_STREAM,0);
    printf("this is the socket_client *TCP* mode.\n");
    if (sockfd<0)
    {
        printf("调用socket函数建立socket描述符出错!\n");
        exit(1);
    }
    printf("调用socket函数建立socket描述符成功!\n");
    ret=connect(sockfd,(struct sockaddr *)(&server_addr),sizeof(server_addr));
    if (ret<0)
    {
        printf("调用connect函数失败,客户端连接服务器失败!\n ");
        exit(2);
    }
    printf("调用connect函数成功,客户端连接服务器成功!\n");
    
    while (1)
    {
        recv_num=recv(sockfd,buf,sizeof(buf),0);
        p.setData((unsigned char *) buf, sizeof(buf));
        if (recv_num<0)
            printf("调用recv接收失败!\n");
        else if(recv_num>0)
        {
            recv_num_total+=recv_num;
            printf("调用recv函数成功,本次接收到%d个字节。共收到%d个字节的数据。\n",recv_num,recv_num_total);
            printf("服务器端:调用recv接收成功!本次接收到%d个字节,共收到%d个字节的数据。\n",recv_num,recv_num_total);
            printf("读出的int类型变量为:%d\n",p.readInt32());
            printf("读出的char类型变量为:%c\n",(char)p.readInt32());
            printf("读出的Float类型变量为:%f\n",(float)p.readFloat());
            printf("读出的Double类型变量为:%f\n",(double)p.readDouble());
            printf("读出的long类型变量为:%ld\n",(long)p.readInt64());
            printf("读出的字符串为:%s\n",p.readCString());
        }
//        else/*收到数据为0,表明服务器与客户端的连接已经中断*/
//        {
//            printf("与客户端的连接已中断,当前共收到%d个字节的数据。服务器将再次等待客户端的连接。\n",recv_num_total);
//            newfd=accept(sockfd,NULL,NULL);/*当客户端退出后,再次开始接收客户端的连接*/
//        }
        sleep(2);
    }
}


/****************** Android Makefile文件 (Android.mk)*****************/
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES:= \
    server.cpp
LOCAL_SHARED_LIBRARIES := \
    libutils \
    libcutils
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE:= socket_server_s
include $(BUILD_EXECUTABLE)


include $(CLEAR_VARS)

LOCAL_SRC_FILES:= \
    client.cpp
LOCAL_SHARED_LIBRARIES := \
    libutils \
    libcutils
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE:= socket_client_s
include $(BUILD_EXECUTABLE)


将这三个文件放在一个文件夹下,我给这个文件夹起了个名字叫parcel_send,其实叫什么都无所谓。然后把parcel_send放到android源代码根目录下的/development目录下面。

用emulator命令启动模拟器后,再另打开一个linux终端,用cd命令进入android源代码根目录下,su取得root权限后,先后执行“make socket_server_s”和“make socket_client_s”对程序进行编译。

编译通过后,将生成的可执行文件送入android环境的/data目录下:
adb push (android源代码根目录)/out/target/product/generic/system/bin/socket_server_s /data
adb push (android源代码根目录)/out/target/product/generic/system/bin/socket_client_s /data

分别再开两个linux终端,用adb shell命令进入模拟器终端,cd /data,先后执行"./socket_server_s"和“./socket_client_s”,即可看到程序运行。


/服务器端程序运行结果/
# ./socket_server_s    
this is the socket_server *TCP* mode.
@调用socket函数建立socket描述符成功!
调用bind函数绑定套接字与地址成功!
调用listen函数成功,宣告服务器已经可以接受连接请求!
调用accept函数成功,服务器与客户端建立连接成功!
write int type success!
write char type success!
write Float type success!
write Double type success!
write long type success!
write String type success!
3:Parcel包的大小是48字节,Parcel结构体的大小是48字节
读出的int类型变量为:987654
读出的char类型变量为:g
读出的Float类型变量为:3.141590
读出的Double类型变量为:12345.678901
读出的long类型变量为:1234567890
读出的字符串为:this is my parcel test! this is my parcel test! this is my parcel test! this is my parcel test!
4:Parcel包的大小是48字节,Parcel结构体的大小是48字节
调用send函数成功,本次发送124个字节。目前共发送了124个字节的数据。
调用send函数成功,本次发送124个字节。目前共发送了248个字节的数据。
调用send函数成功,本次发送124个字节。目前共发送了372个字节的数据。
调用send函数成功,本次发送124个字节。目前共发送了496个字节的数据。
调用send函数成功,本次发送124个字节。目前共发送了620个字节的数据。
调用send函数成功,本次发送124个字节。目前共发送了744个字节的数据。
调用send函数成功,本次发送124个字节。目前共发送了868个字节的数据。
…………………………


/客户端程序运行结果/
# ./socket_client_s
this is the socket_client *TCP* mode.
调用socket函数建立socket描述符成功!
调用connect函数成功,客户端连接服务器成功!
调用recv函数成功,本次接收到124个字节。共收到124个字节的数据。
服务器端:调用recv接收成功!本次接收到124个字节,共收到124个字节的数据。
读出的int类型变量为:987654
读出的char类型变量为:g
读出的Float类型变量为:3.141590
读出的Double类型变量为:12345.678901
读出的long类型变量为:1234567890
读出的字符串为:this is my parcel test! this is my parcel test! this is my parcel test! this is my parcel test!
调用recv函数成功,本次接收到124个字节。共收到248个字节的数据。
服务器端:调用recv接收成功!本次接收到124个字节,共收到248个字节的数据。
读出的int类型变量为:987654
读出的char类型变量为:g
读出的Float类型变量为:3.141590
读出的Double类型变量为:12345.678901
读出的long类型变量为:1234567890
读出的字符串为:this is my parcel test! this is my parcel test! this is my parcel test! this is my parcel test!
调用recv函数成功,本次接收到124个字节。共收到372个字节的数据。
服务器端:调用recv接收成功!本次接收到124个字节,共收到372个字节的数据。
读出的int类型变量为:987654
读出的char类型变量为:g
读出的Float类型变量为:3.141590
读出的Double类型变量为:12345.678901
读出的long类型变量为:1234567890
读出的字符串为:this is my parcel test! this is my parcel test! this is my parcel test! this is my parcel test!
调用recv函数成功,本次接收到124个字节。共收到496个字节的数据。
服务器端:调用recv接收成功!本次接收到124个字节,共收到496个字节的数据。
读出的int类型变量为:987654
读出的char类型变量为:g
读出的Float类型变量为:3.141590
读出的Double类型变量为:12345.678901
读出的long类型变量为:1234567890
读出的字符串为:this is my parcel test! this is my parcel test! this is my parcel test! this is my parcel test!
调用recv函数成功,本次接收到124个字节。共收到620个字节的数据。
服务器端:调用recv接收成功!本次接收到124个字节,共收到620个字节的数据。
读出的int类型变量为:987654
读出的char类型变量为:g
读出的Float类型变量为:3.141590
读出的Double类型变量为:12345.678901
读出的long类型变量为:1234567890
读出的字符串为:this is my parcel test! this is my parcel test! this is my parcel test! this is my parcel test!
调用recv函数成功,本次接收到124个字节。共收到744个字节的数据。
服务器端:调用recv接收成功!本次接收到124个字节,共收到744个字节的数据。
读出的int类型变量为:987654
读出的char类型变量为:g
读出的Float类型变量为:3.141590
读出的Double类型变量为:12345.678901
读出的long类型变量为:1234567890
读出的字符串为:this is my parcel test! this is my parcel test! this is my parcel test! this is my parcel test!
调用recv函数成功,本次接收到124个字节。共收到868个字节的数据。
服务器端:调用recv接收成功!本次接收到124个字节,共收到868个字节的数据。
读出的int类型变量为:987654
读出的char类型变量为:g
读出的Float类型变量为:3.141590
读出的Double类型变量为:12345.678901
读出的long类型变量为:1234567890
读出的字符串为:this is my parcel test! this is my parcel test! this is my parcel test! this is my parcel test!
…………………………………………


=============================
可以看到,例子程序成功实现了将数据封装入parcel、发送到socket、从socket接收、从接收到的parcel中解出数据的过程。

存在的问题: 在客户端程序中,有一句char buf[ 124 ];,这个buf是用来接收socket传过来的parcel包的,但问题是,客户端无法确定parcel包的大小。在实验中,最开始并不是用的 124 这个数据,而是一个小的多的数,结果解出的数据就出现了混乱和丢失。而使用sizeof(p)和sizeof(Parcel)均无法得出正确的parcel包大小(这一点可以从服务器端运行结果中看出,在parcel包大小为124的情况下,仍然显示“Parcel包的大小是48字节,Parcel结构体的大小是48字节“)。因此无奈之下,只能通过查看服务端程序的send函数的返回值,才得知过程中发送了124个字节,因此再修改客户端程序,也才有了这句的设定:char buf[ 124 ];。

当然,如果在实际应用中,接收和发送端程序都是自己编写,自己心里有数的话,那么也无所谓。只要发送多少字节,然后接收端就相应接多少字节就可以了。但是如果不能满足这个条件,也就是说发送端发送的字节数不确定或者不知道的话,就有一定麻烦了。对于这个问题我的解决思路有三种(但未经严格实验验证):
1、把char buf[ 124 ];中的124换为发送端可能发送的最大数值,这样就都可以接收到了,而不会出现丢失数据。
2、发送端在传送parcel数据之前,先把数据包的大小发过来。接收端根据这个信息再做适当调整。
3、参考android源代码的解决方法,android源代码的“/system/core/libcutils/record_stream.c”中有这方面的较好的解决方法,可以直接调用它里面提供的函数,或根据自己的需要在它的基础上加以修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值