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
只能添加到一组,离开的时候,要记得移除欢迎您扫一扫下面的微信公众号,订阅个人博客!