基于 GCDAsyncSocket,简单实现类似《你猜我画》的 socket 数据传输
一、前言
- 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 数据传输的更多相关文章
- 基于 socket.io, 简单实现多平台类似你猜我画 socket 数据传输
一.前言 socket.io 实现了实时双向的基于事件的通讯机制,是基于 webSocket 的封装,但它不仅仅包括 webSocket,还对轮询(Polling)机制以及其它的实时通信方式封装成了通 ...
- 基于Bootstrap简单实用的tags标签插件
http://www.htmleaf.com/jQuery/ jQuery之家 自由分享jQuery.html5和css3的插件库 基于Bootstrap简单实用的tags标签插件
- 基于最简单的FFmpeg包封过程:视频和音频分配器启动(demuxer-simple)
===================================================== 基于最简单的FFmpeg封装工艺的系列文章上市: 最简单的基于FFmpeg的封装格式处理:视 ...
- 基于最简单的FFmpeg采样读取内存读写:存储转
===================================================== 基于最简单的FFmpeg样品系列读写内存列表: 最简单的基于FFmpeg的内存读写的样例:内 ...
- 基于最简单的FFmpeg的AVDevice抽样(屏幕录制)
=====================================================基于最简单的FFmpeg的AVDevice样品文章: 最简单的基于FFmpeg的AVDevic ...
- 基于最简单的FFmpeg采样读取内存读写:内存玩家
===================================================== 基于最简单的FFmpeg样品系列读写内存列表: 最简单的基于FFmpeg的内存读写的样例:内 ...
- 基于Jquery 简单实用的弹出提示框
基于Jquery 简单实用的弹出提示框 引言: 原生的 alert 样子看起来很粗暴,网上也有一大堆相关的插件,但是基本上都是大而全,仅仅几句话可以实现的东西,可能要引入好几十k的文件,所以话了点时间 ...
- 基于PHP——简单的WSDL的创建(WSDL篇)
1.建立WSDL文件 建立WSDL的工具很多,eclipse.zendstudio.vs都可以,我个人建议自己写,熟悉结构,另外自动工具对xml schame类型支持在类型中可能会报错. 下 ...
- Python 基于pykafka简单实现KAFKA消费者
基于pykafka简单实现KAFKA消费者 By: 授客 QQ:1033553122 1.测试环境 python 3.4 zookeeper-3.4.13.tar.gz 下载地址1 ...
随机推荐
- iOS给自定义个model排序
今天有朋友问我怎么给Model排序,我顺便写了一个,伸手党直接复制吧. 例如,我建了一个Person类,要按Person的年龄属性排序: Person *per = [[Person alloc] i ...
- php中设置时区
第一种办法:在php.ini 中设置:date.timezone=Asia/Shanghai(注意不加单引号或双引号) 第二种办法:在程序中ini_set('date.timezone','Asia/ ...
- 开启分布式事物DTC
1.web服务器开启分布式事物配置后,数据库服务器的host文件要设置 “IP web服务器主机名” 的映射,否则会 出现 “与基础事务管理器的通信失败” #跨网段使用TransactionSco ...
- Run Loop简介 分类: ios技术 ios相关 2015-03-11 22:21 73人阅读 评论(0) 收藏
做了一年多的IOS开发,对IOS和Objective-C深层次的了解还十分有限,大多还停留在会用API的级别,这是件挺可悲的事情.想学好一门语言还是需要深层次的了解它,这样才能在使用的时候得心应手,出 ...
- redhat在线安装chrome浏览器
开始的时候是参考吹尽黄沙始到金的文章http://www.cnblogs.com/effective/archive/2012/03/18/2405189.html 1.创建一个文件/etc/yum. ...
- java中的Set的使用以及各种遍历方法(较为全面)
1. 概述 Java 中的Set和正好和数学上直观的集(set)的概念是相同的.Set最大的特性就是不允许在其中存放的元素是重复的.根据这个特点,我们就可以使用Set 这个接口来实现前面提到的关于商品 ...
- PHP 中 static 和 self 的区别
使用 self:: 或者 __CLASS__ 对当前类的静态引用,取决于定义当前方法所在的类: 使用 static:: 不再被解析为定义当前方法所在的类,而是在实际运行时计算的.也可以称之为" ...
- cocharan-Armitage trend test
Cochran-Armitage trend test是我们常说的趋势卡方检验,一般是针对基因型的2*3列联表的.譬如说三种基因型,如果按照某一个allele来看,可以有0.1.2个拷贝,是有序的,我 ...
- js原生设计模式——2面向对象编程之js原生的链式调用
技巧点:对象方法中返回当前对象就可以链式调用了,即方法中写return this; <!DOCTYPE html><html lang="en"><h ...
- javascript中类的属性访问权限研究(1)
本篇文章主要针对javascript的属性进行分析,由于javascript是一种基于对象的语言,本身没有类的概念,所以对于javascript的类的定义有很多名字,例于原型对象,构造函数等,它们都是 ...