本文共 5202 字,大约阅读时间需要 17 分钟。
网络越来越好,手机之间的互动已经是常态,王者荣耀、微信实时视频等,已经将多屏互动推到各到各种应用场景。
为了让大家能清楚地了解多屏互动,我将结合实例对移动设备实时通信进行研究,并系统性地呈现一些解决方案。最开始,我尝试给大家展示如何建立一个最简单的点对点通信。万事开头难,先假定一下需求:局域网内通信。写一个你看我画的程序。纯客户端(一开始,我不打算让服务器参与)。下面对需求进行进一步的分析。
我做了一个简单的原型设计,如下图,其实真正的状态比这个稍复杂,这里提供一下
从原型上看,我们的流程应该是下图的形式。
下面我们进行架构设计与开发选型了。
基于前文的需求假定进行简单设计网络模型,我将目标的网络分拆成3层:
对应的开发架构应该是这样的
基于实时通信的高效性,我将底层库的开发语言选择了C++,协议格式选择为二进制,网络层协议选择UDP(后面会有切换TCP的选择)。
通信协议端口我选择12000.字段 | 注解 |
---|---|
底层库开发语言 | C++ |
协议格式 | 二进制 |
网络层协议 | UDP |
通信端口 | 12000 |
下面我将开始搭建底层库(写到这里还是一行代码都没写,不过现在是国庆节,既然有时间那就开始搞吧)。
工程目录如下
我设计的busi头文件,给上层调用的。(详细的见 github)
#ifndef hello_busi_hpp#define hello_busi_hpp#includenamespace 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\n"); fgets(m_name, 127, stdin); printf("please input your select\n"); printf("1 for link\n"); printf("2 for auto link\n"); int v; scanf("%d", &v); if(v == 1){ printf("please input the dst ip you want link\n"); 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\n"); } }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\n", 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\n", inet_ntoa(addr), srcPort, srcName); m_busi->confirm(m_name, strlen(m_name), srcIndex); printf("now you can send msg to destination\n"); 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\n", srcIndex, srcName); m_dstIndex = srcIndex; printf("now you can send msg to destination\n"); g_linked = 1; return HELLO_STATUS_OK;}int Test::onCancel(int srcIndex){ printf("on cancel from index:%d\n", srcIndex); return HELLO_STATUS_OK;}int Test::onMsg(int srcIndex, const char* msg, int size){ printf("on msg from index:%d, msg: size:%d\n", 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 -O0LDFLAGS += -L/lib64 -pthreadLIB=../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 -O0LDFLAGS += ../lib/libwmd.a -L/lib64 -pthreadAPP=./hello.outdefault:$(APP)clean: -rm -rf $(OBJS) -rm -rf $(APP)$(APP): $(OBJS) $(CXX) -o $@ $^ $(CXXFLAGS) $(LDFLAGS).cpp: $(CXX) -g -o $@ $< $(CXXFLAGS) $(LDFLAGS) $(LIBRARY) $(LIBS)
生成的程序如下:
现在我们来实测一下连通性:
我让mac做主动连接的一方,让linux做被动连接。然后试着各自问候一下吧
可以看到2者已经通了,咱们的底层库搭建OK!
感兴趣并助喜欢动手同学,可以 下载代码 实测一下。为了方便调试,我选择开发一个mac版的你看我画。
在xcode上建立一个spritekit工程
在storyboard上加入按钮元素并绑定ViewController中的变量。
framework为底层库,util为工具目录,adaptor为适配层。
创建一个自定义的view用来实现绘画。
在实现上我用最简单的绘图API, 不过为了区分对手与我画的,我用了2种颜色。
设置自定义鼠标响应事件
并在代码里创建1个scene用于加载自定义view.
object c 调用原生C++,我的做法是加一层代理。
我将代码结构设计如下含义
CoreData | 应用层协议结构 |
---|---|
CoreAdaptor | object c 适配 |
CoreDelegate | 代码接口 |
Core | c++适配 |
在开发的时候,我希望上层在发送消息时,不需要指定IP与端口,而只需要索引就行,因此在framework层
建立一个 地址与索引的对应关系。所以适配层调用接口只需要指定index就行了
结合之前设计的流程图,这个游戏过程的生命周期用viewcontroller 中的代码表示如下:
所以最终连接建立要么在confirm后,要么在onConfirm后。
start函数其实只是负责切换到画图场景
OK,整体代码写完后,我们来演示一下效果。
先看下截图
明天我上传下视频,我画的有点丑,不过没关系,大家可以上github上拉下来自己画。
转载地址:http://dkudl.baihongyu.com/