zoukankan      html  css  js  c++  java
  • 基于 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 了。
  • 相关阅读:
    English Dictionary site for ODE and OALD
    vmic environment
    makefile
    the diference between include and import
    windows 工具命令 cmd
    python namespace
    shell cmd args
    ROE, ROC
    IP
    链接及常用软件
  • 原文地址:https://www.cnblogs.com/howie-ch/p/6404543.html
Copyright © 2011-2022 走看看