一、前言

  • Socket

    • Socket 是对 TCP/IP 协议的封装,其中IP协议对应为网络层,TCP 协议对应为传输层,而我们常用的HTTP协议,是位于应用层,在七层模型中HTTP协议是基于 TCP/IP 的,我们想要使用 TCP/IP 协议,则要通过 Socket
  • Socket 编程用途(其他待补充)
    • 长连接
    • 端到端的即时通讯
  • Socket 和 Http(来源网络)
    • socket 一般用于比较即时的通信和实时性较高的情况,比如推送,聊天,保持心跳长连接等,http 一般用于实时性要求不那么高的情况,比如信息反馈,图片上传,获取新闻信息等。

二、类似《你猜我画》简易效果说明

  • 效果(分别是模拟器和手机截图)



  • 工作中碰到类似需求,但没找到类似的成熟的第三方框架,只有先看看原理性的东西了。其实也就基于 socket 即时传输图片数据、笔画数据,还有聊天文字,也可以拓展做其他的指令控制

  • 没有做注册登录,没有做用户管理,只是简单原理性的探讨

  • 基于 GCDAsyncSocket 框架进行,关于 GCDAsyncSocket 的介绍可自行了解

三、服务端部分代码

  • 直接用 mac 程序作为服务端

    • Server 类
/*!
@method  开启服务
@abstract 开启服务器 TCP 连接服务
*/
- (void)startServer { self.serverSocket = [[GCDAsyncSocket alloc]initWithDelegate:self
delegateQueue:dispatch_get_main_queue()];
NSError *error = nil;
[self.serverSocket acceptOnPort:5555
error:&error];
if (error) {
NSLog(@"服务开启失败");
} else {
NSLog(@"服务开启成功");
} }
#pragma mark - GCDAsyncSocketDelegate
/*!
@method  收到socket端连接回调
@abstract 服务器收到socket端连接回调
*/
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket {
[self.clientSocketArray addObject:newSocket];
[newSocket readDataWithTimeout:-1
tag:self.clientSocketArray.count];
}
/*!
@method  收到socket端数据的回调
@abstract 服务器收到socket端数据的回调
*/
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
// 直接进行转发数据
for (GCDAsyncSocket *clientSocket in self.clientSocketArray) {
if (sock != clientSocket) { [clientSocket writeData:data
withTimeout:-1
tag:0];
}
}
[sock readDataWithTimeout:-1
tag:0]; }
  • main 中
int main(int argc, const char * argv[]) {
@autoreleasepool { Server *chatServer = [[Server alloc]init];
[chatServer startServer];
// 开启主运行循环
[[NSRunLoop mainRunLoop] run];
}
return 0;
}

四、移动端部分代码

  • 基于 GCDAsyncSocket 的接受数据代理方法及发送数据方法
  • 图片数据的发送
// 回调 发送图片
__weak typeof(self) weakSelf = self;
bgImgView.block = ^(UIImage *img) { weakSelf.drawView.drawImg = img;
// image
NSData *imgData = UIImageJPEGRepresentation(weakSelf.drawView.drawImg, 0.2);
NSMutableData *dat = [NSMutableData data];
[dat appendData:imgData];
// 拼接二进制数据流的结束符
NSData *endData = [@"$" dataUsingEncoding:NSUTF8StringEncoding];
[dat appendData:endData];
// 发送数据
[weakSelf.clientSocket writeData:dat
withTimeout:-1
tag:111111]; };
  • 图片二进制数据的传输是基于流的,一段一段的,避免断包缺包等问题,需要拼接结束符,图片数据结束
  • 图片数据的接受接受
        // 拼接数据 转成图片

       [self.socketReadData appendData:data];

       NSData *endData = [data subdataWithRange:NSMakeRange(data.length -1, 1)];

       NSString *end= [[NSString alloc] initWithData:endData
encoding:NSUTF8StringEncoding]; if ([end isEqualToString:@"$"]) { UIImage *tmpImg = [UIImage imageWithData:self.socketReadData]; self.drawView.drawImg = tmpImg; [self.drawView setNeedsDisplay]; [self.clientSocket readDataWithTimeout:-1
tag:111111];
// 拼完图片 恢复默认
self.socketReadData = nil; }
  • 画布笔画数据的传输

    • 因为传输的是二进制数据,所以采取将贝塞尔曲线转换成 CGPoint 坐标数组,再加上线宽和线的颜色,最后组成一个字典,转换为二进制进行传输
    • 考虑到坐标点在不同屏幕上需要适配,因此需要把当前手机端的屏幕高宽一起传输
/*!
@method  发送路径
@abstract 通过socket 发送路径信息
*/
- (void)sendPath {
// path 坐标点及 转换
NSArray *points = [(UIBezierPath *)self.dataModel.path points];
NSMutableArray *tmp = [NSMutableArray array];
for (id value in points) {
CGPoint point = [value CGPointValue];
NSDictionary *dic = @{@"x" : @(point.x), @"y": @(point.y)};
[tmp addObject:dic];
} // 颜色类别
NSInteger colorNum = 0; if (CGColorEqualToColor(self.drawView.color.CGColor, [UIColor redColor].CGColor)) {
colorNum = 1;
}
else if (CGColorEqualToColor(self.drawView.color.CGColor, [UIColor blueColor].CGColor) ){ colorNum = 2;
} else if (CGColorEqualToColor(self.drawView.color.CGColor, [UIColor greenColor].CGColor) ) {
colorNum = 3;
} // 传递数据格式
NSDictionary *pathDataDict = @{
@"path" : tmp,
@"width" : @(self.drawView.width),
@"color" : @(colorNum),
@"screenW": @([UIScreen mainScreen].bounds.size.width),
@"screenH": @([UIScreen mainScreen].bounds.size.height)
}; NSData *pathData = [NSJSONSerialization
dataWithJSONObject:pathDataDict
options:NSJSONWritingPrettyPrinted
error:nil]; [self.clientSocket writeData:pathData
withTimeout:-1
tag:111111];
}
  • 笔画数据的接受

    • 需要转换坐标,解析自定义传输的数据格式
       // 1、接受坐标点
NSInteger w = [tmpDict[@"screenW"] integerValue];
NSInteger h = [tmpDict[@"screenH"] integerValue];
CGFloat scaleW = [UIScreen mainScreen].bounds.size.width / w;
CGFloat scaleH = [UIScreen mainScreen].bounds.size.height / h;
// 处理点
NSArray *pointDict = tmpDict[@"path"];
DIYBezierPath *path = [[DIYBezierPath alloc]init];
for (NSDictionary *tmpDict in pointDict) {
CGPoint point = CGPointMake([tmpDict[@"x"] floatValue] * scaleW, [tmpDict[@"y"] floatValue] * scaleH);
NSInteger index = [pointDict indexOfObject:tmpDict];
if (index == 0) {
[path moveToPoint:point];
} else {
[path addLineToPoint:point];
} }
switch ([tmpDict[@"color"] integerValue]) {
case 0:
self.drawView.color = [UIColor blackColor];
break;
case 1:
self.drawView.color = [UIColor redColor];
break;
case 2:
self.drawView.color = [UIColor blueColor];
break;
case 3:
self.drawView.color = [UIColor greenColor];
break; default:
break;
}
self.drawView.width = [tmpDict[@"width"] floatValue];
self.drawView.currentPath = path;
self.drawView.currentPath.pathColor = self.drawView.color;
self.drawView.currentPath.lineWidth = self.drawView.width;
[self.drawView.pathArray addObject:path];
[self.drawView setNeedsDisplay];

五、小demo地址

https://github.com/HOWIE-CH/-You-guess-I-painted-_socket.git

六、问题

  • 定义了图片文件二进制数据、笔画路径二进制数据、聊天字符串二进制数据,三种格式的二进制数据,在 GCDAsyncSocket 接受数据的代理方法,需要判断接受的二进制文件的类型再进行解析,如果有更好的方式可留言。
  • 只是简单的功能的尝试,有时存在画的一条线过长就传输不过去的情况,存在图片偶尔传输不完整的情况
  • 不清楚是否有相关成熟的框架,如果有,请留言。
  • 最近试过服务端是 NodeJs 用 socket.io 的话,iOS 用 GCDAsyncSocket,感觉这样是通讯不了的。像这样要实现 Android、iOS 跨平台 socket 传输数据,那 socket 选择什么框架呢,服务端选择什么 socket 框架? 之前即时通讯都是 XMPP ,现在貌似是 webSocket socket.io 了。

基于 GCDAsyncSocket,简单实现类似《你猜我画》的 socket 数据传输的更多相关文章

  1. 基于 socket.io, 简单实现多平台类似你猜我画 socket 数据传输

    一.前言 socket.io 实现了实时双向的基于事件的通讯机制,是基于 webSocket 的封装,但它不仅仅包括 webSocket,还对轮询(Polling)机制以及其它的实时通信方式封装成了通 ...

  2. 基于Bootstrap简单实用的tags标签插件

    http://www.htmleaf.com/jQuery/ jQuery之家 自由分享jQuery.html5和css3的插件库 基于Bootstrap简单实用的tags标签插件

  3. 基于最简单的FFmpeg包封过程:视频和音频分配器启动(demuxer-simple)

    ===================================================== 基于最简单的FFmpeg封装工艺的系列文章上市: 最简单的基于FFmpeg的封装格式处理:视 ...

  4. 基于最简单的FFmpeg采样读取内存读写:存储转

    ===================================================== 基于最简单的FFmpeg样品系列读写内存列表: 最简单的基于FFmpeg的内存读写的样例:内 ...

  5. 基于最简单的FFmpeg的AVDevice抽样(屏幕录制)

    =====================================================基于最简单的FFmpeg的AVDevice样品文章: 最简单的基于FFmpeg的AVDevic ...

  6. 基于最简单的FFmpeg采样读取内存读写:内存玩家

    ===================================================== 基于最简单的FFmpeg样品系列读写内存列表: 最简单的基于FFmpeg的内存读写的样例:内 ...

  7. 基于Jquery 简单实用的弹出提示框

    基于Jquery 简单实用的弹出提示框 引言: 原生的 alert 样子看起来很粗暴,网上也有一大堆相关的插件,但是基本上都是大而全,仅仅几句话可以实现的东西,可能要引入好几十k的文件,所以话了点时间 ...

  8. 基于PHP——简单的WSDL的创建(WSDL篇)

    1.建立WSDL文件      建立WSDL的工具很多,eclipse.zendstudio.vs都可以,我个人建议自己写,熟悉结构,另外自动工具对xml schame类型支持在类型中可能会报错. 下 ...

  9. Python 基于pykafka简单实现KAFKA消费者

    基于pykafka简单实现KAFKA消费者   By: 授客 QQ:1033553122         1.测试环境 python 3.4 zookeeper-3.4.13.tar.gz 下载地址1 ...

随机推荐

  1. Linux SSL 双向认证 浅解

    请求方的操作:此步骤是为了验证CA的发证过程. 1.生成私钥:     Openssl genrsa 1024 > private.key  生成私钥并保存到private.key文件中    ...

  2. MapReduce 矩阵相乘

    对于矩阵A[mn]*B[nl]=C[ml].这里可以并行起来的就是每个Cij,对于Cij而言,他是由A的第i行和B的第j列相乘得到.由于大的矩阵中经常是稀疏矩阵,所以一般用行列值表示 例如对于A: 1 ...

  3. FZU 2101 大三的美好时光

    DP+离散化. 首先需要把时间离散化,剩下的就是简单DP. 还要判断哪些选修课与必修课时间有重合,我用了前缀和来处理. 注意:这题时间端点也不能重合. #include<cstdio> # ...

  4. ps--记录几个方法步骤

    1.图片文字去掉 1.1 矩形工具-->吸管-->alt+delete 1.2 钢笔工具-->Ctrl+回车(变换选区)-->吸管-->alt+delete 2.图层锁不 ...

  5. Object修改链表

    以前学习过链表的时候由于类型的接收不同,每次要重写链表 下面修改可用链表 class Link{ private class Node{ private Object data ; private N ...

  6. R语言中的if-else语句写法

    结构  1 :  if()  xx  else    yy    一行: 结构  2:   if()  {xx} else  {yy} 或者   if(){ xx }else    #此处不能两行写 ...

  7. iOS 解决一个复杂bug 之 计分卡

    由于该模块界面和业务逻辑都很复杂,并且整个界面设计和业务逻辑都在ViewController(下面简称为VC)里面完成.该VC共有3000多行,一个函数几百张的也有.所以,解决起来真是头疼. 1. 问 ...

  8. Unicode范围

    unicode编码范围: 汉字:[0x4e00,0x9fa5](或十进制[19968,40869]) 数字:[0x30,0x39](或十进制[48, 57]) 小写字母:[0x61,0x7a](或十进 ...

  9. jq hide show

    var $ = function (id) { return document.getElementById(id); } //返回dom元素的当前某css值 var getCss = functio ...

  10. 关于IE6、IE7、IE8实现盒子阴影shadow的几个注意点

    通常,我们实现盒阴影都是通过这段代码来实现的 -moz-box-shadow: 3px 3px 4px #000; -webkit-box-shadow: 3px 3px 4px #000; box- ...