iOS
端聊天室測試Demo
(Instant messaging,簡稱IM)
是一個終端服務,容許兩人或多人使用網路即時的傳遞文字訊息、檔案、語音與視頻交流Connection
爲keep-alive
便可實現長鏈接,而HTTP1.1
默認是長鏈接,也就是默認Connection
的值就是keep-alive
目前實現即時通信的有四種方式(短輪詢、長輪詢、SSE、Websocket
)php
Server-sent Events
服務器推送事件):爲了解決瀏覽器只可以單向傳輸數據到服務端,HTML5提供了一種新的技術叫作服務器推送事件SSEwebsocket
技術,它不只是一種web通訊方式,也是一種應用層協議websocket
鏈接,在同一時刻可以實現客戶端到服務器和服務器到客戶端的數據發送WebSocket
是一種雙向通訊協議,在創建鏈接後,WebSocket
服務器和客戶端都能主動的向對方發送或接收數據WebSocket
是基於HTTP
協議的,或者說借用了HTTP
協議來完成一部分握手(鏈接),在握手(鏈接)階段與HTTP
是相同的,只不過HTTP
不能服務器給客戶端推送,而WebSocket
能夠WebSockets
協議來創建和維護鏈接。WebSockets
鏈接長期存在,與典型的HTTP
鏈接不一樣,對服務器有重要的影響WebSockets
,由於它旨在打開鏈接,儘量快地處理請求,而後關閉鏈接WebSockets
服務器端實現都須要一個異步服務器Websocket
協議協議頭: ws, 服務器根據協議頭判斷是Http
仍是websocket
html
// 請求頭
GET ws://localhost:12345/websocket/test.html HTTP/1.1
Origin: http://localhost
Connection: Upgrade
Host: localhost:12345
Sec-WebSocket-Key: JspZdPxs9MrWCt3j6h7KdQ==
Upgrade: websocket
Sec-WebSocket-Version: 13
// Sec-WebSocket-Key: 叫「夢幻字符串」是個密鑰,只有有這個密鑰 服務器才能經過解碼認出來,這是個WB的請求,要創建TCP鏈接了!!!若是這個字符串沒有按照加密規則加密,那服務端就認不出來,就會認爲這整個協議就是個HTTP請求。更不會開TCP。其餘的字段均可以隨便設置,可是這個字段是最重要的字段,標識WB協議的一個字段
// 響應頭
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=
WebSocket-Origin: http://localhost
// Sec-WebSocket-Accept: 叫「夢幻字符串」,和上面那個夢幻字符串做用同樣。不一樣的是,這個字符串是要讓客戶端辨認的,客戶端拿到後自動解碼。而且辨認是否是一個WB請求。而後進行相應的操做。這個字段也是重中之重,不可隨便修改的。加密規則,依然是有規則的
複製代碼
在客戶端,沒有必要爲WebSockets
使用JavaScript
庫。實現WebSockets
的Web
瀏覽器將經過WebSockets
對象公開全部必需的客戶端功能(主要指支持HTML5
的瀏覽器)git
如下 API 用於建立WebSocket
對象。github
var Socket = new WebSocket(url, [protocol] );
複製代碼
url
, 指定鏈接的URL
protocol
是可選的,指定了可接受的子協議如下是WebSocket
對象的屬性。假定咱們使用了以上代碼建立了Socket
對象web
Socket.readyState
: 只讀屬性readyState
表示鏈接狀態, 能夠是如下值
Socket.bufferedAmount
: 只讀屬性bufferedAmount
send()
放入正在隊列中等待傳輸,可是尚未發出的UTF-8
文本字節數如下是WebSocket
對象的相關事件。假定咱們使用了以上代碼建立了Socket
對象:express
事件 | 事件處理程序 | 描述 |
---|---|---|
open | Socket.onopen | 鏈接創建時觸發 |
message | Socket.onmessage | 客戶端接收服務端數據時觸發 |
error | Socket.onerror | 通訊發生錯誤時觸發 |
close | Socket.onclose | 鏈接關閉時觸發 |
如下是WebSocket
對象的相關方法。假定咱們使用了以上代碼建立了Socket
對象:npm
方法 | 描述 |
---|---|
Socket.send() | 使用鏈接發送數據 |
Socket.close() | 關閉鏈接 |
// 客戶端
var socket = new WebSocket("ws://localhost:9090")
// 創建 web socket 鏈接成功觸發事件
socket.onopen = function () {
// 使用send發送數據
socket.send("發送數據")
console.log(socket.bufferedAmount)
alert('數據發送中')
}
// 接受服務端數據是觸發事件
socket.onmessage = function (evt) {
var received_msg = evt.data
alert('數據已經接受..')
}
// 斷開 websocket 鏈接成功觸發事件
socket.onclose = function () {
alert('連接已經關閉')
console.log(socket.readyState)
}
複製代碼
WebSocket
在服務端的實現很是豐富。Node.js
、Java
、C++
、Python
等多種語言都有本身的解決方案, 其中Node.js
經常使用的有如下三種json
下面就着重研究一下Socket.IO
吧, 由於別的我也不會, 哈哈哈哈......swift
Socket.IO
JavaScript
實現、基於Node.js
、支持WebSocket
的協議用於實時通訊、跨平臺的開源框架iOS,Android
)和服務器端(Node.js
)的代碼,能夠很好的實現iOS即時通信技術WebSocket
的父集,Socket.io
封裝了WebSocket
和輪詢等方法,會根據狀況選擇方法來進行通信socket.io
支持任何形式的二進制文件傳輸,例如圖片、視頻、音頻等Socket.IO
庫Node.js
導入庫和iOS
導入第三方庫性質同樣, 只不過iOS
使用的是pods
管理, Node.js
使用npm
Socket.IO
庫// 1. 進入噹噹前文件夾
cd ...
// 2. 建立package.json文件
npm init
/// 3. 導入庫
npm install socket.io --sava
npm install express --sava
複製代碼
socket
本質仍是http
協議,因此須要綁定http
服務器,才能啓動socket服務.web
服務器監聽端口,socket
不能監聽端口,有人訪問端口才能創建鏈接,因此先建立web
服務器// 引入http模塊
var http = require('http')
// 面向express框架開發,加載express框架,方便處理get,post請求
var express = require('express')
// 建立web服務器
var server = http.Server(express)
// 引入socket.io模塊
var socketio = require('socket.io')
// 建立愛你socket服務器
var serverSocket = socketio(server)
server.listen(9090)
console.log('監聽9090')
複製代碼
connection
事件,服務端只須要監聽connection
事件有沒有發送,就知道客戶端有沒有主動鏈接服務器Socket.IO
本質是經過發送和接受事件觸發服務器和客戶端之間的通信,任何能被編輯成JSON
或二進制的對象均可以傳遞socket.on
: 監聽事件,這個方法會有兩個參數,第一個參數是事件名稱,第二個參數是監聽事件的回調函數,監聽到連接就會執行這個回調函數connection
,回調函數會傳入一個鏈接好的socket
,這個socket
就是客戶端的socket
socket
鏈接原理,就是客戶端和服務端經過socket
鏈接,服務器有socket
,客戶端也有// 監聽客戶端有沒有鏈接成功,若是鏈接成功,服務端會發送connection事件,通知客戶端鏈接成功
// serverSocket: 服務端, clientSocket: 客戶端
serverSocket.on('connection', function (clientSocket) {
// 創建socket鏈接成功
console.log('創建鏈接成功')
console.log(clientSocket)
})
複製代碼
iOS
使用的庫, 目前只有Swift
版本建立SocketIOClient
對象, 兩種建立方式api
// 第一種, SocketIOClientConfiguration: 可選參數
public init(socketURL: URL, config: SocketIOClientConfiguration = [])
// 第二種, 底層仍是使用的第一種方式建立
public convenience init(socketURL: URL, config: [String: Any]?) {
self.init(socketURL: socketURL, config: config?.toSocketConfiguration() ?? [])
}
複製代碼
SocketIOClientConfiguration
: 是一個數組, 等同於[SocketIOClientOption]
SocketIOClientOption
的全部取值以下public enum SocketIOClientOption : ClientOption {
/// 使用壓縮的方式進行傳輸
case compress
/// 經過字典內容鏈接
case connectParams([String: Any])
/// NSHTTPCookies的數組, 在握手過程當中傳遞, Default is nil.
case cookies([HTTPCookie])
/// 添加自定義請求頭初始化來請求, 默認爲nil
case extraHeaders([String: String])
/// 將爲每一個鏈接建立一個新的connect, 若是你在從新鏈接有bug時使用.
case forceNew(Bool)
/// 傳輸是否使用HTTP長輪詢, 默認false
case forcePolling(Bool)
/// 是否使用 WebSockets. Default is `false`
case forceWebsockets(Bool)
/// 調度handle的運行隊列, 默認在主隊列
case handleQueue(DispatchQueue)
/// 是否打印調試信息. Default is false
case log(Bool)
/// 可自定義SocketLogger調試日誌
case logger(SocketLogger)
/// 自定義服務器使用的路徑.
case path(String)
/// 連接失敗時, 是否從新連接, Default is `true`
case reconnects(Bool)
/// 從新鏈接多少次. Default is `-1` (無限次)
case reconnectAttempts(Int)
/// 等待重連時間. Default is `10`
case reconnectWait(Int)
/// 是否使用安全傳輸, Default is false
case secure(Bool)
/// 設置容許那些證書有效
case security(SSLSecurity)
/// 自簽名只能用於開發模式
case selfSigned(Bool)
/// NSURLSessionDelegate 底層引擎設置. 若是你須要處理自簽名證書. Default is nil.
case sessionDelegate(URLSessionDelegate)
}
複製代碼
建立SocketIOClient
// 注意協議:ws開頭
guard let url = URL(string: "ws://localhost:9090") else { return }
let manager = SocketManager(socketURL: url, config: [.log(true), .compress])
// SocketIOClient
let socket = manager.defaultSocket
複製代碼
socket
對象,而後鏈接用connect
方法socket
須要進行3次握手,不可能立刻建議鏈接,須要監聽是否鏈接成功的回調,使用on
方法ON
方法兩個參數
ACK
)TCP/IP
協議中,若是接收方成功的接收到數據,那麼會回覆一個ACK
數據- ACK
只是一個標記,標記是否成功傳輸數據// 回調閉包
public typealias NormalCallback = ([Any], SocketAckEmitter) -> ()
// on方法
@discardableResult
open func on(_ event: String, callback: @escaping NormalCallback) -> UUID
// SocketClientEvent: 接受枚舉類型的on方法
@discardableResult
open func on(clientEvent event: SocketClientEvent, callback: @escaping NormalCallback) -> UUID {
// 這裏調用的是上面的on方法
return on(event.rawValue, callback: callback)
}
複製代碼
完整代碼
guard let url = URL(string: "ws://localhost:9090") else { return }
let manager = SocketManager(socketURL: url, config: [.log(true), .compress])
let socket = manager.defaultSocket
// 監聽連接成功
socket.on(clientEvent: .connect) { (data, ack) in
print("連接成功")
print(data)
print(ack)
}
socket.connect()
複製代碼
SocketIO
經過事件連接服務器和傳遞數據
// 監聽連接成功
socket.on(clientEvent: .connect) { (data, ack) in
print("連接成功")
print(data)
print(ack)
}
複製代碼
只有鏈接成功以後,才能發送事件
// 創建一個鏈接到服務器. 鏈接成功會觸發 "connect"事件
open func connect()
// 鏈接到服務器. 若是鏈接超時,會調用handle
open func connect(timeoutAfter: Double, withHandler handler: (() -> ())?)
// 重開一個斷開鏈接的socket
open func disconnect()
// 向服務器發送事件, 參數一: 事件的名稱,參數二: 傳輸的數據組
open func emit(_ event: String, with items: [Any])
複製代碼
connect
回調函數中socket
參數,如function(s)
中的s,監聽事件,所以這是客戶端的socket
,確定監聽客戶端發來的事件// 監聽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);
});
});
複製代碼
這裏的socket
必定要用服務器端的socket
// 給當前客戶端發送數據,其餘客戶端收不到.
socket.emit('chat', '服務器' + data)
// 發給全部客戶端,不包含當前客戶端
socket.emit.broadcast.emit('chat', '發給全部客戶端,不包含當前客戶端' + data)
// 發給全部客戶端,包含當前客戶端
socket.emit.sockets.emit('chat', '發給全部客戶端,包含當前客戶端' + data)
複製代碼
socket
連接, 那麼怎麼吧每一條信息推送到對應的聊天室, 針對多個聊天室的問題有如何解決socket.io
提供rooms和namespace的APIrooms
的API就能夠實現多房間聊天了,總結出來無外乎就是:join/leave room
和 say to room
socket
是客戶端的socket
,也就是鏈接成功,傳遞過來的socket
// join和leave
io.on('connection', function(socket){
socket.join('some room');
// socket.leave('some room');
});
// say to room
io.to('some room').emit('some event'):
io.in('some room').emit('some event'):
複製代碼
socket
調用join
,服務器就會把客戶端socket
和分組的名稱綁定起來socket
,就能給指定的客戶端推送信息socket
只能添加到一組,離開的時候,要記得移除歡迎您掃一掃下面的微信公衆號,訂閱個人博客!