前言
在看這篇之前,如果您還不瞭解直播原理,請檢視這篇文章如何快速的開發一個完整的iOS直播app(原理篇)
在直播中,聊天和發禮物,需要用到及時通訊技術,市面上的App大多數採用的都是第三方SDK,融雲,環信等,但是本例子採用websocket搭建及時通訊伺服器。
即時通訊
即時通訊(Instant messaging,簡稱IM)是一個終端服務,允許兩人或多人使用網路即時的傳遞文字訊息、檔案、語音與視訊交流
即時通訊技術原理(瞭解Socket)
- Socket介紹: 套接字或者插座,用於描述IP地址和埠號,是一種網路的通訊機制。
- Socket作用: 網路通訊底層都是通過socket建立連線的,因為它包含IP和埠,只要有這兩個就能準確找到一臺主機上的某個應用。
- IM通訊原理(T):
- 客戶端A與客戶端B如何產生通訊?客戶端A不能直接和客戶端B,因為兩者相距太遠。
- 這時就需要通過IM伺服器,讓兩者產生通訊.
- 客戶端A通過socket與IM伺服器產生連線,客戶端B也通過socket與IM伺服器產生連線
- A先把資訊傳送給IM應用伺服器,並且指定傳送給B,伺服器根據A資訊中描述的接收者將它轉發給B,同樣B到A也是這樣。
- 通訊問題: 伺服器是不能主動連線客戶端的,只能客戶端主動連線伺服器
- 那麼當伺服器要推資訊給客戶端B,但是客戶端B這時候沒有與伺服器產生連線,就推送不了.
- 這樣就延遲,不即時了。
即時通訊技術實現
- 即時通訊核心是
即時
`,那怎麼達到即時?- 目前實現即時通訊的有四種方式(短輪詢、長輪詢、SSE、Websocket)
- 短輪詢: 每隔一小段時間就傳送一個請求到伺服器,伺服器返回最新資料,然後客戶端根據獲得的資料來更新介面,這樣就間接實現了即時通訊。優點是簡單,缺點是對伺服器壓力較大,浪費頻寬流量(通常情況下資料都是沒有發生改變的)。
- 短輪詢: 主要是客戶端人員寫程式碼,伺服器人員比較簡單,適於小型應用
- 長輪詢: 客戶端傳送一個請求到伺服器,伺服器檢視客戶端請求的資料(伺服器中資料)是否發生了變化(是否有最新資料),如果發生變化則立即響應返回,否則保持這個連線並定期檢查最新資料,直到發生了資料更新或連線超時。同時客戶端連線一旦斷開,則再次發出請求,這樣在相同時間內大大減少了客戶端請求伺服器的次數.
- 長輪詢底層實現:在伺服器的程式中加入一個死迴圈,在迴圈中監測資料的變動。當發現新資料時,立即將其輸出給瀏覽器並斷開連線,瀏覽器在收到資料後,再次發起請求以進入下一個週期
- 長輪詢弊端:伺服器長時間連線會消耗資源,返回資料順序無保證,難於管理維護
- 長輪詢處理:不能一直持續下去,應該設定一個最長時限,可以通過心跳包的方式,設定多少秒沒有接到心跳包,就關閉當前連線。
心跳包
:就是在客戶端和伺服器間定時通知對方自己狀態的一個自己定義的命令字,按照一定的時間間隔傳送,類似於心跳,所以叫做心跳包- SSE(Server-sent Events伺服器推送事件):為了解決瀏覽器只能夠單向傳輸資料到服務端,HTML5提供了一種新的技術叫做伺服器推送事件SSE,SSE技術提供的是從伺服器單向推送資料給瀏覽器的功能,但是配合瀏覽器主動請求,實際上就實現了客戶端和伺服器的雙向通訊.
- WebSocket:上面的這些解決方案中,都是利用瀏覽器單向請求伺服器或者伺服器單向推送資料到瀏覽器,而在HTML5中,為了加強web的功能,提供了websocket技術,它不僅是一種web通訊方式,也是一種應用層協議。它提供了瀏覽器和伺服器之間原生的全雙工跨域通訊,通過瀏覽器和伺服器之間建立websocket連線,在同一時刻能夠實現客戶端到伺服器和伺服器到客戶端的資料傳送.
WebSocket
- 什麼是websocket?是 HTML5 一種新的協議。它實現了瀏覽器與伺服器全雙工通訊,能更好的節省伺服器資源和頻寬並達到實時通訊,它建立在 TCP 之上,同 HTTP 一樣通過 TCP 來傳輸資料
- WebSocket 是一種雙向通訊協議,在建立連線後,WebSocket 伺服器和 客戶端 都能主動的向對方傳送或接收資料,就像 Socket 一樣。
- WebSocket 需要類似 TCP 的客戶端和伺服器端通過握手連線,連線成功後才能相互通訊
- websocket提供兩種資料傳輸:文字資料和二進位制資料
- websocket協議頭:ws
WebSocket原理
- Websocket流程(T): WB是先進行一次HTTP請求,這個請求頭不同於普通HTTP請求,然後伺服器開始辨認請求頭,如果是WB的請求頭,則開始進行普通的TCP連線,即三次握手。如果不是WB的HTTP請求頭,那就是按普通的HTTP請求處理
- Websocket協議解析:
- 請求頭
1234567GET ws://localhost:12345/websocket/test.html HTTP/1.1Origin: http://localhostConnection: UpgradeHost: localhost:12345Sec-WebSocket-Key: JspZdPxs9MrWCt3j6h7KdQ== //主要這個欄位,這個叫“夢幻字串”,這個也是個金鑰,只有有這個金鑰 伺服器才能通過解碼 認出來,哦~這是個WB的請求,我要建立TCP連線了!!!如果這個字串沒有按照加密規則加密,那服務端就認不出來,就會認為這整個協議就是個HTTP請求。更不會開TCP。其他的欄位都可以隨便設定,但是這個欄位是最重要的欄位,標識WB協議的一個欄位。Upgrade: websocketSec-WebSocket-Version: 13 - 響應頭
123456HTTP/1.1 101 Web Socket Protocol HandshakeWebSocket-Location: ws://localhost:12345/websocket/test.phpConnection: UpgradeUpgrade: websocketSec-WebSocket-Accept: zUyzbJdkVJjhhu8KiAUCDmHtY/o= //這個欄位,叫“夢幻字串”,和上面那個夢幻字串作用一樣。不同的是,這個字串是要讓客戶端辨認的,客戶端拿到後自動解碼。並且辨認是不是一個WB請求。然後進行相應的操作。這個欄位也是重中之重,不可隨便修改的。加密規則,依然是有規則的,可以去百度一下。WebSocket-Origin: http://localhost - Sec-WebSocket-Key:其值採用base64編碼的隨機16位元組長的字元序列
- Sec-WebSocket-Accept如何生成
- 請求頭
Socket.IO簡介
- WebSocket的功能是很強大的,使用起來也靈活,可以適用於不同的場景。不過WebSocket技術也比較複雜,需要加密解密,包裝協議,自己實現3次握手,還需要對資料流進行加密解密處理,伺服器端和瀏覽器端的實現都不同於一般的Web應用,因此自己實現很麻煩,可以使用Socket.IO框架。
- Socket.IO:是一個完全由JavaScript實現、基於Node.js、支援WebSocket的協議用於實時通訊、跨平臺的開源框架。
- Socket.IO:它包括了客戶端(iOS,Android)和伺服器端(Node.js)的程式碼,可以很好的實現iOS即使通訊技術。
- Socket.IO框架地址
Socket.IO教程
Socket.IO建立連線 伺服器程式碼
- 1.如何匯入Socket.IO?
- 和匯入express框架一樣,使用package
- 給package檔案新增依賴
1234"dependencies": {"express": "^4.14.0","socket.io": "^1.4.8"} - 2.如何建立socket
- socket本質還是http協議,所以需要繫結http伺服器,才能啟動socket服務.
- 而且需要通過web伺服器監聽埠,socket不能監聽埠,有人訪問埠才能建立連線,所以先建立web伺服器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
* 1.面向express框架開發,載入express框架,方便處理get,post請求 * 2.因為socket依賴http,建立http伺服器,使用http模組. * 3.可以通過express建立http伺服器http.server(express) * 4.通過http伺服器建立socket * 5.監聽http伺服器 ``` // 引入express var http = require('http'); var express = require('express'); // 建立web伺服器 var server = http.Server(express); // 引入socker var socketIO = require('socket.io'); // 需要傳入伺服器,socket基於http var socket = socketIO(server); // 監聽web伺服器 server.listen(8080); ``` |
- 3.如何建立socket連線(伺服器不需要主動建立連線,建立連線是客戶端的事情,伺服器只需要監聽連線)
- 客戶端主動連線會傳送connection事件,只需要監聽connection事件有沒有傳送,就知道客戶端有沒有主動連線伺服器
- Socket.IO本質是通過
傳送和接受事件
觸發伺服器和客戶端之間的通訊,任何能被編輯成JSON或二進位制的物件都可以傳遞。 - 監聽事件,用socket.on,這個方法會有兩個引數,第一個引數是事件名稱,第二個引數是監聽事件的回撥函式,監聽到就會執行這個回撥函式
- 監聽connection,回撥函式會傳入一個連線好的socket,這個socket就是客戶端的socket
- socket連線原理,就是客戶端和服務端通過socket連線,伺服器有socket,客戶端也有
12345// 監聽socket連線// function引數必填socketsocket.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會自動幫我們生成,無序手動自己生成.
1 |
#import "客戶端-Swift.h" |
- 4.注意工程檔名不能帶有-這個符號,而且有時候會延遲,並不是馬上匯入”工程檔名-Swift.h”就好.
- 5.建立socket物件,然後連線用connect方法,socket物件需要強引用
- 注意協議:ws開頭
- 建立socket物件,需要傳入字典,字典配置如下。
1234567891011121314151617181920所有關於SocketIOClientOption的設定.如果是ObjC,轉換名字lowerCamelCase.case ConnectParams([String: AnyObject]) // 通過字典內容連線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]) // 新增自定義請求頭初始化來請求, 預設為nilcase ForcePolling(Bool) // 是否使用 xhr-polling. Default is `false`case ForceNew(Bool) // 將為每個連線建立一個新的connect, 如果你在重新連線時有bug時使用.case ForceWebsockets(Bool) // 是否使用 WebSockets. Default is `false`case HandleQueue(dispatch_queue_t) // 排程handle的執行佇列. Default is the main queue.case Log(Bool) // 是否列印除錯資訊. Default is false.case Logger(SocketLogger) // 可自定義SocketLogger除錯日誌.預設是系統的.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次握手,不可能馬上建議連線,需要監聽是否連線成功的回撥,使用on方法
- 7.ON方法兩個引數(第一個引數,監聽的事件名稱,第二個引數:監聽事件回撥函式,會自動呼叫)
- 回撥函式也有兩個引數(第一個引數:伺服器傳遞的資料 第二個引數:確認請求資料)
- 在TCP/IP協議中,如果接收方成功的接收到資料,那麼會回覆一個ACK資料。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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]; // 監聽連線成功 [socket on:@"connect" callback:^(NSArray * _Nonnull data, SocketAckEmitter * _Nonnull ask) { NSLog(@"確定與伺服器連線"); NSLog(@"%@ %@",data,ask); }]; |
SocketIO傳送事件,通過事件傳遞資料
SocketIO 客戶端傳送事件程式碼
- 注意:只有連線成功之後,才能傳送事件
- 向伺服器傳送事件(emit:第一引數事件的名稱,第二個引數傳輸的資料,是一個陣列)
1 |
[socket emit:@"chat" with:@[@"你好"]]; |
SocketIO 伺服器監聽事件程式碼
- 監聽客戶端事件,需要巢狀在連線好的connect回撥函式中
- 必須使用回撥函式的socket引數,如function(s)中的s,監聽事件,因此這是客戶端的socket,肯定監聽客戶端發來的事件
- 伺服器監聽連線的回撥函式的引數可以新增多個,具體看客戶端傳遞資料陣列有幾個,每個引數都是與客戶段一一對應,第一個引數對應客戶端陣列第0個資料
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// 監聽socket連線 socket.on('connection',function(s){ console.log('監聽到客戶端連線'); // data:客戶端陣列第0個元素 // data1:客戶端陣列第1個元素 s.on('chat',function(data,data1){ console.log('監聽到chat事件'); console.log(data,data1); }); }); |
SocketIO 伺服器傳送事件程式碼
- 這裡的socket一定要用伺服器端的socket
- 給當前客戶端傳送資料,其他客戶端收不到.
1 |
socket.emit('chat','伺服器'+data); |
- 發給所有客戶端,不包含當前客戶端
1 |
socket.emit.broadcast.emit('chat','發給所有客戶端,不包含當前客戶端'+data); |
- 發給所有客戶端,包含當前客戶端
1 |
socket.emit.sockets.emit('chat','發給所有客戶端,包含當前客戶端'+data); |
SocketIO 客戶端監聽事件程式碼
1 2 3 |
[socket on:@"chat" callback:^(NSArray * _Nonnull data, SocketAckEmitter * _Nonnull ask) { NSLog(@"%@",data[0]); }]; |
SocketIO分組
- 開發中什麼場景需要使用SocketIO分組?(T)
- 一個客戶端和伺服器只會保持一個socket連線,比如直播App中會開很多主播房間,每個房間都有自己的聊天室,那怎麼把資訊推送到對應的房間,比如A使用者要給A主播間傳送資訊,怎麼推送過去,通過伺服器只能給當前客戶端推送,那一推,當前客戶端所有直播間都有A使用者的資訊。
- 怎麼解決多個直播聊天室問題?
- 給每個主播的房間都分組,伺服器就可以給指定組推送資料,就不會影響到其他直播間
- SocketIO如何分組?
- 伺服器程式碼: socket.join(),()裡面放分組名稱,與之對應的 socket.leave()
- 注意這裡的socket是客戶端的socket,也就是連線成功,傳遞過來的socket
socket分組的原理
,只要客戶端socket呼叫join,伺服器就會把客戶端socket和分組的名稱繫結起來,到時候就可以根據分組的名稱找到對應客戶端的socket,就能給指定的客戶端推送資訊.- 注意:一個客戶端socket只能新增到一組,離開的時候,要記得移除.
- 客戶端可以這樣測試,搞兩臺電腦/兩臺手機在同一個區域網內,執行就有兩個客戶端,分別加入不同組.
- 伺服器只給一個客戶端socket傳送資訊,另外一個客戶端收不到
- 伺服器程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// 監聽socket連線 socket.on('connection',function(s){ console.log('監聽到客戶端連線'); s.on('createRoom',function(roomName){ s.join(roomName); rooms.push(roomName); console.log('建立房間'+ roomName); }); s.on('chatRoom',function(data){ console.log(rooms[0] + '說話'); socket.to(rooms[0]).emit('chat','房間1的資料'); }); }); |