發個廣告:ios開發兩年了,一步步走來 關注公衆號一起進步
前言
在看這篇之前,如果您還不瞭解直播原理,請查看這篇文章如何快速的開發一個完整的iOS直播app(原理篇)
在直播中,聊天和發禮物,需要用到及時通訊技術,市面上的App大多數採用的都是第三方SDK,融雲,環信等,但是本例子採用websocket搭建及時通訊服務器。
如果喜歡我的文章,可以關注我微博:袁崢Seemygo,也可以來小碼哥,瞭解下我們的iOS培訓課程。後續還會更新更多內容,有任何問題,歡迎簡書留言袁崢Seemygo。。。
即時通訊
即時通訊(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協議解析:
-
請求頭
GET ws://localhost:12345/websocket/test.html HTTP/1.1 Origin: http://localhost Connection: Upgrade Host: localhost:12345 Sec-WebSocket-Key: JspZdPxs9MrWCt3j6h7KdQ== //主要這個字段,這個叫“夢幻字符串”,這個也是個密鑰,只有有這個密鑰 服務器才能通過解碼 認出來,哦~這是個WB的請求,我要建立TCP連接了!!!如果這個字符串沒有按照加密規則加密,那服務端就認不出來,就會認爲這整個協議就是個HTTP請求。更不會開TCP。其他的字段都可以隨便設置,但是這個字段是最重要的字段,標識WB協議的一個字段。 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請求。然後進行相應的操作。這個字段也是重中之重,不可隨便修改的。加密規則,依然是有規則的,可以去百度一下。 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建立連接 服務器代碼
-
1.如何導入Socket.IO?
- 和導入express框架一樣,使用package
- 給package文件添加依賴
"dependencies": { "express": "^4.14.0", "socket.io": "^1.4.8" }
-
2.如何創建socket
-
socket本質還是http協議,所以需要綁定http服務器,才能啓動socket服務.
-
而且需要通過web服務器監聽端口,socket不能監聽端口,有人訪問端口才能建立連接,所以先創建web服務器
-
* 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,客戶端也有
// 監聽socket連接 // function參數必填socket socket.on('connection',function(clientSocket){ console.log('建立連接',clientSocket); });
- 書寫客戶端代碼,驗證是否能建立連接
Socket.IO建立連接 客戶端代碼
-
- 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.創建socket對象,然後連接用connect方法,socket對象需要強引用
- 注意協議:ws開頭
-
創建socket對象,需要傳入字典,字典配置如下。
所有關於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]) // 添加自定義請求頭初始化來請求, 默認爲nil case 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數據。
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:第一參數事件的名稱,第二個參數傳輸的數據,是一個數組)
[socket emit:@"chat" with:@[@"你好"]];
SocketIO 服務器監聽事件代碼
- 監聽客戶端事件,需要嵌套在連接好的connect回調函數中
- 必須使用回調函數的socket參數,如function(s)中的s,監聽事件,因此這是客戶端的socket,肯定監聽客戶端發來的事件
- 服務器監聽連接的回調函數的參數可以添加多個,具體看客戶端傳遞數據數組有幾個,每個參數都是與客戶段一一對應,第一個參數對應客戶端數組第0個數據
// 監聽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
- 給當前客戶端發送數據,其他客戶端收不到.
socket.emit('chat','服務器'+data);
- 發給所有客戶端,不包含當前客戶端
socket.emit.broadcast.emit('chat','發給所有客戶端,不包含當前客戶端'+data);
- 發給所有客戶端,包含當前客戶端
socket.emit.sockets.emit('chat','發給所有客戶端,包含當前客戶端'+data);
SocketIO 客戶端監聽事件代碼
[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發送信息,另外一個客戶端收不到
- 服務器代碼
// 監聽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的數據');
});
});
原文地址:http://www.jianshu.com/p/6e7fb61c25e1