1. 程式人生 > >Android環境下通過SOCKET傳遞Parcel包並解出資料的例子

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”中有這方面的較好的解決方法,可以直接呼叫它裡面提供的函式,或根據自己的需要在它的基礎上加以修改。

相關推薦

Android環境通過SOCKET傳遞Parcel資料例子

之前做過了在android下通過socket傳送資料的實驗,也做過了parcel包的製作和解包的實驗(這兩個實驗的源程式之前都在本部落格的其他文章中貼過)。昨天和今天把這兩個過程合併了起來:即在Android環境下,甲程式(C++程式)將資料封裝在Parcel中,並把Par

Windows環境Ruby離線安裝gem

ruby gem 離線安裝 mongo 在上一篇博文中,我記錄了如何在Windows環境下進行Ruby操作MongoDB數據庫的環境配置。其中在最後一步講述了安裝MongoDB的驅動包。使用的是gem在線安裝方式。本文章的目的是為了在目標機器或環境無法連接互聯網時,如何使用gem進行gem工具

關於maven環境使用pom.xml引入名.lastUpdate的解決辦法

文件 date clip 下使用 console -o maven pom i-o 今天在導入POI-OOXML的時候老師缺失xmlbeans包,而且刷新pom文件總是生成一個lastupdate文件,大小為1KB,終於找到解決辦法。 1.首先刪除想要的jar包所在文件夾

Linux環境通過rpm安裝gcc的順序

首先檢視Linux版本: [[email protected] ~]# lsb_release -a LSB Version: :core-3.1-amd64:core-3.1-ia32:core-3.1-noarch:graphics-3.1-amd64:graphics-3

Mac及Android環境的JNI學習

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Ubuntu16.04環境通過Cmake管理Opencv專案

Ubuntu16.04環境下通過Cmake管理Opencv專案 1、新建qt cmake工程 New Project -> Non-Qt Project -> Plain C++ Application 2、CMakeLists.txt檔案內

Android環境的GDB除錯

gdb是GNU開發的針對Linux/Unix環境下程式的除錯工具。為了節約目標系統的資源,gdb通常採用gdb+gdbserver的方式進行除錯。 在Android GDB除錯場景下,gdb執行在PC端,gdbserver執行在Android系統中。在實際的除錯過程中,PC端的gdb參照

pthon3環境利用socket實現server,client互動例項

1、例項要求 使用socket實現一個基於C/S架構的通訊程式 (1)客戶端傳送給伺服器請求,傳送表徵身份的使用者名稱和密碼(“admin”,“123456”); (2)伺服器根據客戶端發來的資訊驗證身份,如果驗證錯誤,返回“refuse”字串,並且斷開連線通道;

Solaris環境使用snoop命令抓

(1)報文抓取 Solaris中自帶有snoop抓包工具,通過執行相應的命令抓取。 抓取目的地址為10.8.3.250的資料包,並存放到/opt/cap250的檔案裡 snoop -o /opt/cap250 host 10.8.3.250 (2)報文下載 方法

Linux環境通過docker搭建PHP的LAMP開發環境

想必作為一個Web開發的程式設計師,近些年對docker一定不會陌生,Docker 是一個開源的應用容器引擎,讓開發者可以打包他們的應用以及依賴包到一個可移植的容器中,然後釋出到任何流行的 Linux 機器上,也可以實現虛擬化。容器是完全使用沙箱機制,相互之間不會有任何介面。使用dock

【順序表】純C環境,函式傳遞的指標指向報錯及解決

之前開始學順序表的時候,就沒有很好地弄懂,函式裡指標的傳遞這一塊,今天把錯誤範例和一些解決方式拿出來分析一下。 網上有很多掛羊頭賣狗肉的c語言教程,函式是引用呼叫的,就很誤導人。 Wrong: typedef struct { int *elem; in

windows環境socket程式設計(tcp檔案傳輸的實現)

開發環境 使用codeclock軟體進行程式設計 新建專案選擇console application完成相應的步驟即可。在專案下有main.c的檔案只需要將程式碼寫入其中即可。 程式碼設計 客戶端 client #include <std

windows環境pip安裝python的時候提示invalid syntax

剛學python,用pip安裝出錯,如下圖: 檢查了一下環境變數,然而並沒沒問題。在stackowerflow上逛了一圈,找到了解決方法: 在命令列下cd 進對應python的script檔案目錄,再執行pip指令即可。

Openstack學習筆記(七)-在Win環境通過XManager(xshell)遠端開啟eclipse

1.首先關閉防火牆 sudo ufw disable 2.安裝協議 sudo apt-get install xrdp sudo apt-get install vnc4server tightvncserver sudo apt-get in

SUSE環境通過YaST安裝軟體

為了提升使用者在雲伺服器上的軟體安裝效率,減少下載和安裝軟體的成本,騰訊雲提供了YaST下載源。作業系統為SUSE10的雲伺服器,開發者可通過YaST快速安裝軟體。 1. 安裝步驟 1. 列出軟體源 登入作業系統為Linux的雲伺服器後,預設已獲取root許可權:注意:嚴

windows環境通過xshell把檔案傳遞給Linux虛擬機器

第一步,下載並xshell遠端登入軟體,開啟xshell,點選左上角的“新建”選項 得到如下介面 請在住主機選項出填寫linux虛擬機器的IP地址,在選擇“確定”,如果不知道IP地址,可以先返回虛擬機器,並在終端介面書寫命令#ifconfig,便可以檢視IP地址 第二步,遠端登入linux系統 第一步

生產環境通過pm2部署node簡單例項

首先應該有一個要部署的專案 (1)建立一個資料夾project (2)在資料夾project中建立app.js檔案 (3)app.js內容如下 const http=require('http') http.createServer(function(req,res){ r

Edison平臺eclipse環境通過MRAA庫使用IIC、SPI、UART例程(BMI160、MS8607)

MRAA庫IIC例程:以下程式是操作MS8607感測器mraa_init();/* initialize I2C on bus 0 */m_i2c = mraa_i2c_init(1);mraa_i2c_frequency(m_i2c,MRAA_I2C_FAST);//400

打包GolangAndroid環境執行

參考連結 打包在android下的可執行程式 打包的程式碼如下,作用是輸出執行的作業系統的資訊: func main() { fmt.Print("Go runs on ") s

CentOS7環境通過Docker安裝sentry

一,安裝Docker1 解除安裝舊版本sudo yum remove docker docker-common docker-selinux docker-engine2 安裝依賴包sudo yum install -y yum-utils device-mapper-pe