簡單設計並開發一個行動通訊程式

xiaofei發表於2017-10-17

網路越來越好,手機之間的互動已經是常態,王者榮耀、微信實時視訊等,已經將多屏互動推到各到各種應用場景。
為了讓大家能清楚地瞭解多屏互動,我將結合例項對移動裝置實時通訊進行研究,並系統性地呈現一些解決方案。
最開始,我嘗試給大家展示如何建立一個最簡單的點對點通訊。
萬事開頭難,先假定一下需求:
區域網內通訊。
寫一個你看我畫的程式。
純客戶端(一開始,我不打算讓伺服器參與)。

下面對需求進行進一步的分析。

需求分析

我做了一個簡單的原型設計,如下圖,其實真正的狀態比這個稍複雜,這裡提供一下 原型連結

需求原型

image.png

從原型上看,我們的流程應該是下圖的形式。

流程圖

image.png

下面我們進行架構設計與開發選型了。

架構設計

基於前文的需求假定進行簡單設計網路模型,我將目標的網路分拆成3層:

網路模型

image.png

對應的開發架構應該是這樣的

開發架構

image.png

開發規則

基於實時通訊的高效性,我將底層庫的開發語言選擇了C++,協議格式選擇為二進位制,網路層協議選擇UDP(後面會有切換TCP的選擇)。
通訊協議埠我選擇12000.

欄位 註解
底層庫開發語言 C++
協議格式 二進位制
網路層協議 UDP
通訊埠 12000

下面我將開始搭建底層庫(寫到這裡還是一行程式碼都沒寫,不過現在是國慶節,既然有時間那就開始搞吧)。

底層庫搭建

建立工程

工程目錄如下
image.png

我設計的busi標頭檔案,給上層呼叫的。(詳細的見 github)

#ifndef hello_busi_hpp
#define hello_busi_hpp
#include <stdio.h>

namespace hello{
    
    class BusiInterface{
    public:
        
        virtual int onInit(int myIp, int myPort);
        virtual int onLink(int srcIndex, int srcIp, int srcPort, const char* srcName, int nameSize);
        virtual int onConfirm(int srcIndex, const char* srcName, int nameSize);
        virtual int onCancel(int srcIndex);
        virtual int onMsg(int srcIndex, const char* msg, int size);
        
        
       
        
    };
    
    class Busi{
    public:
        Busi();
        virtual ~Busi();
        virtual int init(BusiInterface* itf);
        virtual int link(const char* myName, int nameSize, int dstIp, int dstPort);
        virtual int confirm(const char* myName, int nameSize, int index);
        virtual int cancel(int index);
        virtual int sendMsg(int index, const char* msg, int size);
        
    private:
        
        Busi*                    m_busi;
        
    };
    
}
#endif /* hello_busi_h */

花了半天的時間寫完了底層庫,先來測試一下底層庫的連通性,我寫了一個程試程式,只列下核心檔案, 詳細請看github上。

void Test::testBusi()
{
    m_busi = new Busi();
    m_busi->init(this);
    
 
    char  ip[128];
    
    printf("pleast input your name
");
    fgets(m_name, 127, stdin);
    
    
    printf("please input your select
");
    printf("1 for link
");
    printf("2 for auto link
");
    int v;
    scanf("%d", &v);
    
    if(v == 1){
        printf("please input the dst ip you want link
");
        scanf("%s", ip);
        
        int dstIp = inet_addr(ip);
        
        m_busi->link(m_name, strlen(m_name)+1, dstIp, HELLO_COMM_SERVER_LISTEN_PORT);
    }
    else{
        printf("now you can want link from others
");
    }
    
}

void Test::sendMsg(const char *buffer, int size)
{
    m_busi->sendMsg(m_dstIndex, buffer, size);
}


int Test::onInit(int myIp, int myPort)
{
    struct in_addr addr;
    addr.s_addr = myIp;
    
    printf("on init, my ip:%s, my port:%d
", inet_ntoa(addr), myPort);
    
    return HELLO_STATUS_OK;
    
}
int Test::onLink(int srcIndex, int srcIp, int srcPort, const char* srcName, int nameSize)
{
    struct in_addr addr;
    addr.s_addr = srcIp;
    
    printf("on link from ip:%s, port:%d, name:%s
", inet_ntoa(addr),  srcPort, srcName);
    m_busi->confirm(m_name, strlen(m_name), srcIndex);
    printf("now you can send msg to destination
");
    g_linked = 1;
    m_dstIndex = srcIndex;
    
    return HELLO_STATUS_OK;
}
int Test::onConfirm(int srcIndex, const char* srcName, int nameSize)
{
    printf("on confirm from index:%d, name:%s
", srcIndex, srcName);
    m_dstIndex = srcIndex;
    printf("now you can send msg to destination
");
    g_linked = 1;
    
    
    return HELLO_STATUS_OK;
}
int Test::onCancel(int srcIndex)
{
    printf("on cancel from index:%d
",  srcIndex);
    return HELLO_STATUS_OK;
}
int Test::onMsg(int srcIndex, const char* msg, int size)
{
    printf("on msg from index:%d, msg: size:%d
", srcIndex, size);
    printf("msg:%s", msg);
    return HELLO_STATUS_OK;
}

因為我有一臺mac, 一個ubuntu,所有測試時候,2邊都要編譯,mac是用xcode比較簡單,ubuntu上編寫makefile

framework:

BUSI_SRC=$(wildcard busi/*.cpp)
NET_SRC=$(wildcard net/*.cpp)
PACKAGE_SRC=$(wildcard package/*.cpp)
UTIL_SRC=$(wildcard util/*.cpp)

SRC=$(BUSI_SRC) $(NET_SRC) $(PACKAGE_SRC) $(UTIL_SRC)
OBJS=$(patsubst %.cpp, %.o, $(SRC))
CXXFLAGS += -D_WMD -pthread -std=c++11 -g -O0
LDFLAGS += -L/lib64 -pthread
LIB=../lib/libwmd.a 
default: $(LIB)
$(LIB): $(OBJS)
        rm -rf $@
        ar -rs $@ $(OBJS)
clean:
        -rm -rf $(OBJS)
.cpp:
        $(CXX) -o $@ $< $(CXXFLAGS) $(LDFLAGS) $(LIBRARY) $(LIBS)

test


SRC=$(wildcard *.cpp)
OBJS=$(patsubst %.cpp, %.o, $(SRC))
CXXFLAGS += -std=c++11 -g -O0
LDFLAGS += ../lib/libwmd.a -L/lib64 -pthread
APP=./hello.out
default:$(APP)
clean:
        -rm -rf $(OBJS)
        -rm -rf $(APP)
$(APP): $(OBJS)
        $(CXX) -o $@ $^ $(CXXFLAGS) $(LDFLAGS)
.cpp:
        $(CXX) -g -o $@ $< $(CXXFLAGS) $(LDFLAGS) $(LIBRARY) $(LIBS)

生成的程式如下:
image.png

現在我們來實測一下連通性:
我讓mac做主動連線的一方,讓linux做被動連線。
image.png
image.png

然後試著各自問候一下吧
image.png
image.png

可以看到2者已經通了,我們們的底層庫搭建OK!
感興趣並助喜歡動手同學,可以 下載程式碼 實測一下。

寫一個你看我畫的程式吧

為了方便除錯,我選擇開發一個mac版的你看我畫。

建立工程

在xcode上建立一個spritekit工程
image.png
image.png

在storyboard上加入按鈕元素並繫結ViewController中的變數。
image.png

程式碼目錄設計

image.png

framework為底層庫,util為工具目錄,adaptor為適配層。

實現繪圖功能

建立一個自定義的view用來實現繪畫。
image.png

在實現上我用最簡單的繪圖API, 不過為了區分對手與我畫的,我用了2種顏色。
image.png

設定自定義滑鼠響應事件
image.png

並在程式碼裡建立1個scene用於載入自定義view.

上下層打通

object c 呼叫原生C++,我的做法是加一層代理。
我將程式碼結構設計如下

image.png

含義

CoreData 應用層協議結構
CoreAdaptor object c 適配
CoreDelegate 程式碼介面
Core c++適配

適配架構

image.png

在開發的時候,我希望上層在傳送訊息時,不需要指定IP與埠,而只需要索引就行,因此在framework層
建立一個 地址與索引的對應關係。
image.png

所以適配層呼叫介面只需要指定index就行了
image.png

應用層通訊實現

結合之前設計的流程圖,這個遊戲過程的生命週期用viewcontroller 中的程式碼表示如下:
image.png

所以最終連線建立要麼在confirm後,要麼在onConfirm後。
image.png

image.png

start函式其實只是負責切換到畫圖場景
image.png

OK,整體程式碼寫完後,我們來演示一下效果。

程式演示

先看下截圖

image.png

明天我上傳下視訊,我畫的有點醜,不過沒關係,大家可以上github上拉下來自己畫。
https://github.com/70207/draw.git


相關文章