【如何快速的開發(fā)一個完整的iOS直播app】(搭建Socket即時通訊服務器)

前言

在看這篇之前,如果您還不了解直播原理,請查看這篇文章如何快速的開發(fā)一個完整的iOS直播app(原理篇)

在直播中,聊天和發(fā)禮物,需要用到及時通訊技術,市面上的App大多數(shù)采用的都是第三方SDK,融云,環(huán)信等,但是本例子采用websocket搭建及時通訊服務器。

如果喜歡我的文章,可以關注我微博:袁崢Seemygo

即時通訊

即時通訊(Instant messaging,簡稱IM)是一個終端服務,允許兩人或多人使用網(wǎng)路即時的傳遞文字訊息、檔案、語音與視頻交流

即時通訊技術原理(了解Socket)

  • Socket介紹: 套接字或者插座,用于描述IP地址和端口號,是一種網(wǎng)絡的通信機制。
  • Socket作用: 網(wǎng)絡通信底層都是通過socket建立連接的,因為它包含IP和端口,只要有這兩個就能準確找到一臺主機上的某個應用。
  • IM通信原理(T):
    • 客戶端A與客戶端B如何產(chǎn)生通信?客戶端A不能直接和客戶端B,因為兩者相距太遠。
    • 這時就需要通過IM服務器,讓兩者產(chǎn)生通信.
    • 客戶端A通過socket與IM服務器產(chǎn)生連接,客戶端B也通過socket與IM服務器產(chǎn)生連接
    • A先把信息發(fā)送給IM應用服務器,并且指定發(fā)送給B,服務器根據(jù)A信息中描述的接收者將它轉(zhuǎn)發(fā)給B,同樣B到A也是這樣。
    • 通訊問題: 服務器是不能主動連接客戶端的,只能客戶端主動連接服務器
    • 那么當服務器要推信息給客戶端B,但是客戶端B這時候沒有與服務器產(chǎn)生連接,就推送不了.
    • 這樣就延遲,不即時了。

即時通訊連接原理

  • 即時通訊都是長連接,基本上都是HTTP1.1協(xié)議,設置Connection為keep-alive即可實現(xiàn)長連接,而HTTP1.1默認是長連接,也就是默認Connection的值就是keep-alive。
  • HTTP分為長連接和短連接,其實本質(zhì)上是TCP連接,HTTP協(xié)議是應用層的協(xié)議,而TCP才是真正的傳輸層協(xié)議,只有負責傳輸?shù)倪@一層才需要建立連接。
  • 就拿網(wǎng)上購物來說,HTTP協(xié)議指的那個快遞單,你寄件的時候填的單子就像是發(fā)了一個HTTP請求,等貨物運到地方了,快遞員會根據(jù)你發(fā)的請求把貨物送給相應的收貨人。而TCP協(xié)議就是中間運貨的那個大貨車,也可能是火車或者飛機,但不管是什么,它是負責運輸?shù)?,因此必須要有路,不管是地上還是天上。那么這個路就是所謂的TCP連接
  • Http連接:只要服務端給了響應,本次HTTP連接就結束,本質(zhì)不存在沒有長連接。
  • htpp長連接指的是:長連接是為了復用,長連接是指的TCP連接,也就是說復用的是TCP連接,長連接情況下,多個HTTP請求可以復用同一個TCP連接,這就節(jié)省了很多TCP連接建立和斷開的消耗,要不然每個Http請求都產(chǎn)生一個TCP連接,浪費很多資源

即時通訊(數(shù)據(jù)即時傳遞原理)

  • 即時通訊核心是`即時``,那怎么達到即時?
    • 目前實現(xiàn)即時通訊的有四種方式(短輪詢、長輪詢、SSE、Websocket)
    • 短輪詢: 每隔一小段時間就發(fā)送一個請求到服務器,服務器返回最新數(shù)據(jù),然后客戶端根據(jù)獲得的數(shù)據(jù)來更新界面,這樣就間接實現(xiàn)了即時通信。優(yōu)點是簡單,缺點是對服務器壓力較大,浪費帶寬流量(通常情況下數(shù)據(jù)都是沒有發(fā)生改變的)。
    • 短輪詢: 主要是客戶端人員寫代碼,服務器人員比較簡單,適于小型應用
    • 長輪詢: 客戶端發(fā)送一個請求到服務器,服務器查看客戶端請求的數(shù)據(jù)(服務器中數(shù)據(jù))是否發(fā)生了變化(是否有最新數(shù)據(jù)),如果發(fā)生變化則立即響應返回,否則保持這個連接并定期檢查最新數(shù)據(jù),直到發(fā)生了數(shù)據(jù)更新或連接超時。同時客戶端連接一旦斷開,則再次發(fā)出請求,這樣在相同時間內(nèi)大大減少了客戶端請求服務器的次數(shù).
    • 長輪詢底層實現(xiàn):在服務器的程序中加入一個死循環(huán),在循環(huán)中監(jiān)測數(shù)據(jù)的變動。當發(fā)現(xiàn)新數(shù)據(jù)時,立即將其輸出給瀏覽器并斷開連接,瀏覽器在收到數(shù)據(jù)后,再次發(fā)起請求以進入下一個周期
    • 長輪詢弊端:服務器長時間連接會消耗資源,返回數(shù)據(jù)順序無保證,難于管理維護
    • 長輪詢處理:不能一直持續(xù)下去,應該設定一個最長時限,可以通過心跳包的方式,設置多少秒沒有接到心跳包,就關閉當前連接。
    • 心跳包:就是在客戶端和服務器間定時通知對方自己狀態(tài)的一個自己定義的命令字,按照一定的時間間隔發(fā)送,類似于心跳,所以叫做心跳包
    • SSE(Server-sent Events服務器推送事件):為了解決瀏覽器只能夠單向傳輸數(shù)據(jù)到服務端,HTML5提供了一種新的技術叫做服務器推送事件SSE,SSE技術提供的是從服務器單向推送數(shù)據(jù)給瀏覽器的功能,加上配合瀏覽器主動Http請求,兩者結合起來,實際上就實現(xiàn)了客戶端和服務器的雙向通信.
    • WebSocket:上面的這些解決方案中,都是利用瀏覽器單向請求服務器或者服務器單向推送數(shù)據(jù)到瀏覽器,而在HTML5中,為了加強web的功能,提供了websocket技術,它不僅是一種web通信方式,也是一種應用層協(xié)議。它提供了瀏覽器和服務器之間原生的全雙工跨域通信,通過瀏覽器和服務器之間建立websocket連接,在同一時刻能夠?qū)崿F(xiàn)客戶端到服務器和服務器到客戶端的數(shù)據(jù)發(fā)送.

WebSocket

  •  什么是websocket?WebSocket 是一種雙向通信協(xié)議,在建立連接后,WebSocket 服務器和 客戶端 都能主動的向?qū)Ψ桨l(fā)送或接收數(shù)據(jù)。
    
  • websocket原理?WebSocket是基于Http協(xié)議的,或者說借用了Http協(xié)議來完成一部分握手(連接),在握手(連接)階段與Http是相同的,只不過Http不能服務器給客戶端推送,而websocket可以。
  • websocket協(xié)議頭:ws
  •  服務器根據(jù)協(xié)議頭判斷是Http,還是websocket、
    
  • Websocket協(xié)議解析:
    • 請求頭

           GET ws://localhost:12345/websocket/test.html HTTP/1.1
           Origin: http://localhost
           Connection: Upgrade
           Host: localhost:12345
           Sec-WebSocket-Key: JspZdPxs9MrWCt3j6h7KdQ==  //主要這個字段,這個叫“夢幻字符串”,這個也是個密鑰,只有有這個密鑰 服務器才能通過解碼 認出來,哦~這是個WB的請求,我要建立TCP連接了!??!如果這個字符串沒有按照加密規(guī)則加密,那服務端就認不出來,就會認為這整個協(xié)議就是個HTTP請求。更不會開TCP。其他的字段都可以隨便設置,但是這個字段是最重要的字段,標識WB協(xié)議的一個字段。
           Upgrade: websocket 
           Sec-WebSocket-Version: 13
      
    • 響應頭

           HTTP/1.1 101 Web Socket Protocol Handshake
           WebSocket-Location: ws://localhost:12345/websocket/test.php
           Connection: Upgrade
           Upgrade: websocket
           Sec-WebSocket-Accept: zUyzbJdkVJjhhu8KiAUCDmHtY/o= //這個字段,叫“夢幻字符串”,和上面那個夢幻字符串作用一樣。不同的是,這個字符串是要讓客戶端辨認的,客戶端拿到后自動解碼。并且辨認是不是一個WB請求。然后進行相應的操作。這個字段也是重中之重,不可隨便修改的。加密規(guī)則,依然是有規(guī)則的,可以去百度一下。
           WebSocket-Origin: http://localhost
      
    • Sec-WebSocket-Key:其值采用base64編碼的隨機16字節(jié)長的字符序列

    • Sec-WebSocket-Accept如何生成

Socket.IO簡介

  • 為什么要使用Socket.IO?WebSocket的功能是很強大的,使用起來也靈活,可以適用于不同的場景。不過WebSocket技術也比較復雜,需要加密解密,包裝協(xié)議,自己實現(xiàn)3次握手,還需要對數(shù)據(jù)流進行加密解密處理,服務器端和瀏覽器端的實現(xiàn)都不同于一般的Web應用,因此自己實現(xiàn)很麻煩,可以使用Socket.IO框架。

  • Socket.IO:是一個完全由JavaScript實現(xiàn)、基于Node.js、支持WebSocket的協(xié)議用于實時通信、跨平臺的開源框架。

  • Socket.IO:它包括了客戶端(iOS,Android)和服務器端(Node.js)的代碼,可以很好的實現(xiàn)iOS即使通訊技術。

  • Socket.IO框架地址

Socket.IO教程

Socket.IO建立連接 服務器代碼

  • 1.如何導入Socket.IO?

    • 和導入express框架一樣,使用package
    • 給package文件添加依賴
        "dependencies": {
                        "express": "^4.14.0",
                        "socket.io": "^1.4.8"
                        }
    
  • 2.如何創(chuàng)建socket

    • socket本質(zhì)還是http協(xié)議,所以需要綁定http服務器,才能啟動socket服務.

    • 而且需要通過web服務器監(jiān)聽端口,socket不能監(jiān)聽端口,有人訪問端口才能建立連接,所以先創(chuàng)建web服務器

*   1.面向express框架開發(fā),加載express框架,方便處理get,post請求

*   2.因為socket依賴http,創(chuàng)建http服務器,使用http模塊.

*   3.可以通過express創(chuàng)建http服務器http.server(express)

*   4.通過http服務器創(chuàng)建socket

*   5.監(jiān)聽http服務器

```
    // 引入express
var http = require('http');

var express = require('express');

// 創(chuàng)建web服務器
var server = http.Server(express);

// 引入socker
var socketIO = require('socket.io');

// 需要傳入服務器,socket基于http
var socket = socketIO(server);

// 監(jiān)聽web服務器
server.listen(8080);
```
  • 3.如何建立socket連接(服務器不需要主動建立連接,建立連接是客戶端的事情,服務器只需要監(jiān)聽連接)

    • 客戶端主動連接會發(fā)送connection事件,只需要監(jiān)聽connection事件有沒有發(fā)送,就知道客戶端有沒有主動連接服務器
    • Socket.IO本質(zhì)是通過發(fā)送和接受事件觸發(fā)服務器和客戶端之間的通訊,任何能被編輯成JSON或二進制的對象都可以傳遞。
    • 監(jiān)聽事件,用socket.on,這個方法會有兩個參數(shù),第一個參數(shù)是事件名稱,第二個參數(shù)是監(jiān)聽事件的回調(diào)函數(shù),監(jiān)聽到就會執(zhí)行這個回調(diào)函數(shù)
    • 監(jiān)聽connection,回調(diào)函數(shù)會傳入一個連接好的socket,這個socket就是客戶端的socket
    • socket連接原理,就是客戶端和服務端通過socket連接,服務器有socket,客戶端也有
        // 監(jiān)聽socket連接
        // function參數(shù)必填socket
        socket.on('connection',function(clientSocket){
            console.log('建立連接',clientSocket);
        });
    
    
    • 書寫客戶端代碼,驗證是否能建立連接

Socket.IO建立連接 客戶端代碼

  • 1.下載Socket.IO-Client-Swift

    • Socket.IO只有swift,如果需要用OC代碼,需要swift和OC混編
    • 還有如果代碼是OC,并且使用cocoapods,就不要使用cocoapods導入swift代碼,會有問題.
  • 2.下載完了,直接把Source文件夾拖入到自己工程中.

    • 會報錯,說當前swift版本過時,需要更新。點擊Xcode頂部Edit => Convert => TO Current Swift Syntas 就好了。
  • 3.OC和Swift混編,Swift代碼怎么在OC中使用,直接導入"工程文件名-Swift.h"就可以使用,這個文件Xcode會自動幫我們生成,無序手動自己生成.

    #import "客戶端-Swift.h"
  • 4.注意工程文件名不能帶有-這個符號,而且有時候會延遲,并不是馬上導入"工程文件名-Swift.h"就好.

  • 5.創(chuàng)建socket對象,然后連接用connect方法,socket對象需要強引用

    • 注意協(xié)議:ws開頭

    • 創(chuàng)建socket對象,需要傳入字典,字典配置如下。

          所有關于SocketIOClientOption的設置.如果是ObjC,轉(zhuǎn)換名字lowerCamelCase.
          case ConnectParams([String: AnyObject]) // 通過字典內(nèi)容連接
          case Cookies([NSHTTPCookie]) // An array of NSHTTPCookies. Passed during the handshake. Default is nil.
          case DoubleEncodeUTF8(Bool) // Whether or not to double encode utf8. If using the node based server this should be true. Default is true.
          case ExtraHeaders([String: String]) // 添加自定義請求頭初始化來請求, 默認為nil
          case ForcePolling(Bool) // 是否使用 xhr-polling. Default is `false`
          case ForceNew(Bool) // 將為每個連接創(chuàng)建一個新的connect, 如果你在重新連接時有bug時使用.
          case ForceWebsockets(Bool) // 是否使用 WebSockets. Default is `false`
          case HandleQueue(dispatch_queue_t) // 調(diào)度handle的運行隊列. Default is the main queue.
          case Log(Bool) // 是否打印調(diào)試信息. Default is false.
          case Logger(SocketLogger) // 可自定義SocketLogger調(diào)試日志.默認是系統(tǒng)的.
          case Nsp(String) // 如果使用命名空間連接. Must begin with /. Default is `/`
          case Path(String) // 如果服務器使用一個自定義路徑. 例如: `"/swift/"`. Default is `""`
          case Reconnects(Bool) // 是否重新連接服務器失敗. Default is `true`
          case ReconnectAttempts(Int) // 重新連接多少次. Default is `-1` (無限次)
          case ReconnectWait(Int) // 等待重連時間. Default is `10`
          case SessionDelegate(NSURLSessionDelegate) // NSURLSessionDelegate 底層引擎設置. 如果你需要處理自簽名證書. Default is nil.
          case Secure(Bool) // 如果連接要使用TLS. Default is false.
          case SelfSigned(Bool) //  WebSocket.selfSignedSSL設置 (Don't do this, iOS will yell at you)
          case VoipEnabled(Bool) // 如果你的客戶端使用VoIP服務,只有用這個選項,Default is false  
      
  • 6.因為需要進行3次握手,不可能馬上建議連接,需要監(jiān)聽是否連接成功的回調(diào),使用on方法

  • 7.ON方法兩個參數(shù)(第一個參數(shù),監(jiān)聽的事件名稱,第二個參數(shù):監(jiān)聽事件回調(diào)函數(shù),會自動調(diào)用)

    • 回調(diào)函數(shù)也有兩個參數(shù)(第一個參數(shù):服務器傳遞的數(shù)據(jù) 第二個參數(shù):確認請求數(shù)據(jù))
    • 在TCP/IP協(xié)議中,如果接收方成功的接收到數(shù)據(jù),那么會回復一個ACK數(shù)據(jù)。
    •  ACK只是一個標記,標記是否成功傳輸數(shù)據(jù)。
      
    NSURL *url = [NSURL URLWithString:@"ws://192.168.0.100:8080"];
    
    SocketIOClient *socket = [[SocketIOClient alloc] initWithSocketURL:url config:@{@"log": @YES, @"forcePolling": @YES}];
    _socket = socket;
    
    [socket connect];
    
    // 監(jiān)聽連接成功
    [socket on:@"connect" callback:^(NSArray * _Nonnull data, SocketAckEmitter * _Nonnull ask) {
        
        NSLog(@"確定與服務器連接");
        
        NSLog(@"%@ %@",data,ask);
        
    }];

SocketIO發(fā)送事件,通過事件傳遞數(shù)據(jù)

SocketIO 客戶端發(fā)送事件代碼

  • 注意:只有連接成功之后,才能發(fā)送事件
  • 向服務器發(fā)送事件(emit:第一參數(shù)事件的名稱,第二個參數(shù)傳輸?shù)臄?shù)據(jù),是一個數(shù)組)
    [socket emit:@"chat" with:@[@"你好"]];

SocketIO 服務器監(jiān)聽事件代碼

  • 監(jiān)聽客戶端事件,需要嵌套在連接好的connect回調(diào)函數(shù)中
  • 必須使用回調(diào)函數(shù)的socket參數(shù),如function(s)中的s,監(jiān)聽事件,因此這是客戶端的socket,肯定監(jiān)聽客戶端發(fā)來的事件
  • 服務器監(jiān)聽連接的回調(diào)函數(shù)的參數(shù)可以添加多個,具體看客戶端傳遞數(shù)據(jù)數(shù)組有幾個,每個參數(shù)都是與客戶段一一對應,第一個參數(shù)對應客戶端數(shù)組第0個數(shù)據(jù)
// 監(jiān)聽socket連接
socket.on('connection',function(s){

    console.log('監(jiān)聽到客戶端連接');

    // data:客戶端數(shù)組第0個元素
    // data1:客戶端數(shù)組第1個元素
    s.on('chat',function(data,data1){

        console.log('監(jiān)聽到chat事件');

        console.log(data,data1);
        
    });
    
});

SocketIO 服務器發(fā)送事件代碼

  • 這里的socket一定要用服務器端的socket
  • 給當前客戶端發(fā)送數(shù)據(jù),其他客戶端收不到.
    socket.emit('chat','服務器'+data);
  • 發(fā)給所有客戶端,不包含當前客戶端
    socket.emit.broadcast.emit('chat','發(fā)給所有客戶端,不包含當前客戶端'+data);
  • 發(fā)給所有客戶端,包含當前客戶端
    socket.emit.sockets.emit('chat','發(fā)給所有客戶端,包含當前客戶端'+data);

SocketIO 客戶端監(jiān)聽事件代碼

    [socket on:@"chat" callback:^(NSArray * _Nonnull data, SocketAckEmitter * _Nonnull ask) {
        NSLog(@"%@",data[0]);
    }];

SocketIO分組

  • 開發(fā)中什么場景需要使用SocketIO分組?(T)
  • 一個客戶端和服務器只會保持一個socket連接,比如直播App中會開很多主播房間,每個房間都有自己的聊天室,那怎么把信息推送到對應的房間,比如A用戶要給A主播間發(fā)送信息,怎么推送過去,通過服務器只能給當前客戶端推送,那一推,當前客戶端所有直播間都有A用戶的信息。
  • 怎么解決多個直播聊天室問題?
  • 給每個主播的房間都分組,服務器就可以給指定組推送數(shù)據(jù),就不會影響到其他直播間
  • SocketIO如何分組?
    • 服務器代碼: socket.join(),()里面放分組名稱,與之對應的 socket.leave()
    • 注意這里的socket是客戶端的socket,也就是連接成功,傳遞過來的socket
  • socket分組的原理,只要客戶端socket調(diào)用join,服務器就會把客戶端socket和分組的名稱綁定起來,到時候就可以根據(jù)分組的名稱找到對應客戶端的socket,就能給指定的客戶端推送信息.
  • 注意:一個客戶端socket只能添加到一組,離開的時候,要記得移除.
  • 客戶端可以這樣測試,搞兩臺電腦/兩臺手機在同一個局域網(wǎng)內(nèi),運行就有兩個客戶端,分別加入不同組.
  • 服務器只給一個客戶端socket發(fā)送信息,另外一個客戶端收不到
  • 服務器代碼
// 監(jiān)聽socket連接
socket.on('connection',function(s){

    console.log('監(jiān)聽到客戶端連接');

    s.on('createRoom',function(roomName){

        s.join(roomName);
        rooms.push(roomName);
        console.log('創(chuàng)建房間'+ roomName);
    });

    s.on('chatRoom',function(data){
        console.log(rooms[0] + '說話');
        socket.to(rooms[0]).emit('chat','房間1的數(shù)據(jù)');
    });

});
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 初涉IM,首先我有這么幾個問題需要弄明白: Socket 和 WebSocket 有哪些區(qū)別和聯(lián)系? WebSoc...
    夜幕青雨閱讀 11,475評論 8 39
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,693評論 19 139
  • 昨日拉了六次,今日一次,我揪著的心稍微放下一些了??赡苁俏逡煌骈_了,這兩日的你出門玩都不愿意回家了??茨阍谕饷婺敲?..
    菜鳥快跑閱讀 368評論 0 0
  • 那年從北京結束流浪記者生涯回到濮陽,女友跑路、工作無著、夢想破滅,曾經(jīng)壯懷激烈的光榮與夢想瞬間碎了一地,頹廢得連死...
    任艾軍閱讀 610評論 0 2
  • 對于股票的清理 1,股市十人做,一人平,九人虧 2,散戶弄不過莊家 3,我的判斷總是有誤的 4,做股票是不會賺錢的...
    Jo_b005閱讀 211評論 0 0

友情鏈接更多精彩內(nèi)容