网络越来越好,手机之间的互动已经是常态,王者荣耀、微信实时视频等,已经将多屏互动推到各到各种应用场景。
为了让大家能清楚地了解多屏互动,我将结合实例对移动设备实时通信进行研究,并系统性地呈现一些解决方案。
最开始,我尝试给大家展示如何建立一个最简单的点对点通信。
万事开头难,先假定一下需求:
局域网内通信。
写一个你看我画的程序。
纯客户端(一开始,我不打算让服务器参与)。
下面对需求进行进一步的分析。
需求分析
我做了一个简单的原型设计,如下图,其实真正的状态比这个稍复杂,这里提供一下 原型链接
需求原型
从原型上看,我们的流程应该是下图的形式。
流程图
下面我们进行架构设计与开发选型了。
架构设计
基于前文的需求假定进行简单设计网络模型,我将目标的网络分拆成3层:
网络模型
对应的开发架构应该是这样的
开发架构
开发规则
基于实时通信的高效性,我将底层库的开发语言选择了C++,协议格式选择为二进制,网络层协议选择UDP(后面会有切换TCP的选择)。
通信协议端口我选择12000.
字段 | 注解 |
---|---|
底层库开发语言 | C++ |
协议格式 | 二进制 |
网络层协议 | UDP |
通信端口 | 12000 |
下面我将开始搭建底层库(写到这里还是一行代码都没写,不过现在是国庆节,既然有时间那就开始搞吧)。
底层库搭建
创建工程
工程目录如下
我设计的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\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 -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)
生成的程序如下:
现在我们来实测一下连通性:
我让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上拉下来自己画。
https://github.com/70207/draw.git