基於 GCDAsyncSocket,簡單實現類似《你猜我畫》的 socket 數據傳輸

来源:http://www.cnblogs.com/howie-ch/archive/2017/02/16/6404543.html
-Advertisement-
Play Games

基於 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 接受數據的代理方法,需要判斷接受的二進位文件的類型再進行解析,如果有更好的方式可留言。
  • 只是簡單的功能的嘗試,有時存在畫的一條線過長就傳輸不過去的情況,存在圖片偶爾傳輸不完整的情況
  • 不清楚是否有相關成熟的框架,如果有,請留言。
    

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 紅包互換軟體開發 互換紅包軟體定製開發,聯繫傑森團隊微電188-2624-7572. 1、精彩互換系統模式開發付出關鍵:統統的會員介入精彩互換系統模式付出的金額由平臺在後盾設定,能夠設定一個值,也能夠設定多個值。比如說付出5元的,平臺給你返的紅包額度在1-100元之間,付出20元的,平臺給你返的紅包 ...
  • 之前上傳圖片都是直接將圖片轉化為io流傳給伺服器,沒有用框架傳圖片。 最近做項目,打算換個方法上傳圖片。 Android發展到現在,Okhttp顯得越來越重要,所以,這次我選擇用Okhttp上傳圖片。 Okhttp目前已經更新到Okhttp3版本了,用法跟之前相比,也有一些差別。在網上找了很多資料, ...
  • 今天在項目中碰到一個問題,在一個頁面的頂部的標題欄顯示公司的名字,但由於公司名稱較長,顯示不開,影響美觀。故在網上查閱資料,在此做個小的總結。 TextView中有個ellipsize屬性,作用是當文字過長時,該控制項該如何顯示,解釋如下: 1.Android:ellipsize=”start”—–省 ...
  • 由OpenDigg 出品的iOS開源項目周報第八期來啦。我們的iOS開源周報集合了OpenDigg一周來新收錄的優質的iOS開源項目,方便iOS開發人員便捷的找到自己需要的項目工具等。 ...
  • UIDocumentInteractionController UIActivityViewController Shared Keychain Access Custom URL Scheme Web Service iCloud API UIPasteboard 參考 http://enharm ...
  • iOS UISearchController 的使用方法 UISearchController 讓用戶在 UISearchBar 上輸入搜索關鍵詞,展示搜索結果或者進行其他操作。UISearchController 把兩個控制器(UIViewController)連在一起。父控制器放置 UISear ...
  • 二碼公益軟體開發,二碼公益app開發,二碼公益開發聯繫微電188-2624-7572. 隨著社會的發展、經濟的進步,人口老齡化問題不可避免地日益凸顯,“尊老、敬老、扶老”,成為了每個中國人越發不能忽視的社會責任。 在這樣的大環境下,“二碼公益”應運而生,它將取代傳統的商業模式,無論是作為商家還是消費 ...
  • android中一個對象已經不需要了,但是其他對象還持有他的引用,導致他不能回收,導致這個對象暫存在記憶體中,這樣記憶體泄漏就出現了。 記憶體泄漏出現多了,會是應用占用過多的沒存,當占用的記憶體超過了系統分配的記憶體容量,就會出現記憶體溢出了導致應用Crash. 瞭解了記憶體泄漏的原因及影響後,我們需要做的就是掌 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...