iOS 上的 WebSocket 框架 Starscream
传统的网络技术 (也就是 Berkeley sockets) 被认为是可靠和稳定的。但是 Berkeley socket 在某些 web 技术,比如代理和防火墙下不太好使。WebSocket 出现于 2011 年,是一种在客户端和服务端之间建立双向通讯的新技术。WebSocket 比起多个 HTTP 请求来说更有效率并允许长连接。
在 iOS 上使用 WebSocket 并不是那么容易。iOS 和 Mac 库 Starscream 的出现,极大地简化了 WebSocket 的创建和使用。
注:本文假设你熟悉 CocoaPods。如果你不熟悉,请参考我们的 CocoaPods 教程。
在本文中,你将在一个叫做 Emoji Commuicator 的 App 中完成网络编程部分。Emoji Communicator 允许你将当前心情用一个 emoji 字符发布给所有连接到该服务的人。
Emoji Communicator 原来的开发者原本打算用 HTTP 请求获取新消息,但这个功能使用 WebSocket 显然更合适。你将使用 Starscream 来连接后台的 Web 服务器。
首先需要一个 Web 服务器。在本文中,你会在本机上启动一个 web 服务器。这个示例的 web 服务器运行在 Node.js 下,使用一小个 Javascript 文件来支持它。
当然,首先需要安装 Node.js。如果不确定是否已经安装,可以在终端窗口中输入命令:
node--version
1
如果报错,请按照下列步骤下载和安装 Node.js。否则,你会看到 Node.js 的版本号,你就可以跳过下一节 Node.js 的下载。
在 https://nodejs.org/ 下载 Node.js 的最新安装包 (当前 2016.9.22 的最新版本是)。下载完安装包 (例如 node-v6.6.0.pkg),双击进行安装。参考提示进行,选择默认选项就行了。
安装完后,在终端中检查 Node.js 是否工作正常:
node--version
1
如果你没有看到 6.6.0 (或者你所安装的版本),或者报错,再次检查安装是否正确。
你将用到一个聊天服务器。从这里下载示例 iOS app 和 Web 服务器代码。解压缩 zip 包到桌面或者某个文件夹。在终端中,切换到该目录,并进入 nodeapp 子目录。
这个程序需要一个第三方模块,需要用 Node.js 的包管理器 npm 来安装。在这个目录中,执行:
npminstallwebsocket
1
输入下列命令启动聊天服务器:
node chat-server.js
1
你会看到类似如下输出:
Tue Sep13201618:54:44GMT-0500(CDT) Serverislisteningonport1337
1
然后在浏览器 Safari 或 Chrome 中打开 frontend.html。
输入一个昵称,发送一条测试消息。如果要在第二个客户端进行测试,重新打开一个浏览器或标签页,用同一个 Url。用另一个昵称登录,发送一条消息;你会看到消息立即出现在另一个浏览器里。
这充分说明了 WebSocket 的强大之处。每个浏览器都和 web 服务器有一个单独的长连接——没有刷新。当消息到达,服务器会自动向所有连接着的客户端进行广播。返回终端,你可以看到所有的聊天活动:
$nodechat-server.jsTueSep13 2016 18:54:44GMT-0500(CDT)Serverislisteningonport1337TueSep13 2016 18:55:19GMT-0500(CDT)Connectionfromoriginnull.TueSep13 2016 18:55:19GMT-0500(CDT)Connectionaccepted.TueSep13 2016 18:55:34GMT-0500(CDT)Userisknownas:Aaronwithgreencolor.TueSep13 2016 18:55:37GMT-0500(CDT)ReceivedMessagefromAaron:HelloTueSep13 2016 18:58:49GMT-0500(CDT)Connectionfromoriginnull.TueSep13 2016 18:58:49GMT-0500(CDT)Connectionaccepted.TueSep13 2016 18:58:51GMT-0500(CDT)Userisknownas:Jameswithredcolor.TueSep13 2016 18:58:55GMT-0500(CDT)ReceivedMessagefromJames:Thisisprettyslick!TueSep13 2016 18:59:03GMT-0500(CDT)ReceivedMessagefromJames: :]TueSep13 2016 18:59:27GMT-0500(CDT)Peerundefineddisconnected.
HTTP 第一次出现是 1991 年,它设计为一种请求/响应式的通讯机制。Web 浏览器用这种机制工作良好,用户请求 web 页,服务器返回内容。但某些时候,需要有新数据时不经过用户请求就通知用户——也就是,服务器推。
HTTP 协议无法很好地解决推模型。在 websocket 出现前,web 服务通过一系列浏览器刷新机制来实现推模型,但效率无法让人满意。
webSocket 实现了服务端推机制。新的 web 浏览器全都支持 WebSocket,这使得它的使用超级简单。通过 WebSocket 能够打开持久连接,大部分网络都能轻松处理 WebSocket 连接。
WebSocket 通常应用在某些数据经常性或频繁改变的场景。例如 Facebook 中的 web 通知、Slack 中的实时聊天、交易系统中的变化的股票价格。
在 iOS 中使用 WebSocket 比较麻烦,你必须进行大量的设置,而且内置的 API 根本帮不上忙。这时 Starscream 出现了——这个小巧、易于使用的库让你所有的烦恼不翼而飞。
打开 EmojiTransmitter.xcodeproj。在模拟器中运行程序,程序很简单,它需要用户输入一个昵称,然后显示一个界面,让用户选择一个 emoji 发送,并显示任何接收到的 emoji。
这个 App 还没有完成网络部分。你将使用 Starscream 来执行所有的 WebSocket 网络请求。
有许多方法可以将 Starscream 集成到你的项目。CocoaPods 和 Carthage 是两种最常见的包管理器。你两种都可以用,但本文将使用 CocoaPods。
首先,关闭打开的项目。开启终端窗口,将目录切换至项目文件夹。在这个项目中已经有一个配置了 Starscream pod 的 Podfile 文件了。你可以直接安装 pod:
pod repoupdate;pod install
1
当 CocoaPods 结束安装,在 Xcode 8 中打开 EmojiTransmitter.xcworkspace 文件。运行程序,检查 App 是否能够运行。
打开 ViewController.swift,在 import UIKit 后加入:
importStarscream
1
然后,在 ViewController 类的 username 属性后增加一个属性:
varsocket= WebSocket(url:URL(string:"ws://localhost:1337/")!, protocols: ["chat"])
1
这是创建 WebSocket 连接的核心。注意 URL 的歌声,协议是 ws 而不是 Http/https。protocols 参数指定为 chat,这取决于服务端的实现,这个协议可以被使用,也可能被忽略。在本 demo 中,忽略它即可。
接着在 viewDidLoad 方法后加入:
deinit {socket.disconnect(forceTimeout:0)socket.delegate = nil}
1
2
3
4
当 View Controller 被销毁时,强制关闭 WebSocket 连接。
在 Starscream 中所有的工作都放在 delegate 中进行。Starscream 也支持闭包,如果你不愿意使用委托的话。
在 ViewController.swift,在 fileprivate 扩展后增加一个扩展:
// MARK: - WebSocketDelegateextension ViewController : WebSocketDelegate {publicfuncwebsocketDidConnect(_ socket: Starscream.WebSocket) { }publicfuncwebsocketDidDisconnect(_ socket: Starscream.WebSocket, error: NSError?) { }publicfuncwebsocketDidReceiveMessage(_ socket: Starscream.WebSocket, text: String) { }publicfuncwebsocketDidReceiveData(_ socket: Starscream.WebSocket, data: Data) { }}
这 4 个委托方法必须实现,否则代码无法通过编译。
然后,在 viewDidLoad 方法的 super.viewDidLoad() 后面添加:
socket.delegate = selfsocket.connect()
运行程序,输入昵称,点击 Next。返回 Node.js 控制台你将看到有一个连接通知。
现在,你已经能够连接到 Node.js App 了,接下来是发送消息到服务器。
首先,将 sendMessage(_:) 方法修改为:
func sendMessage(_message:String) { socket.write(string:message)}
这会发送消息(本例中,就是 emoji)到 Node.js 服务器。
然后,在 websocketDidConnect(_:) 方法中加入:
socket.write(string: username)
1
这会在连接建立后发送你在第一个界面中输入的昵称。这个服务器会将第一个接收到的消息当做用户名称。
在 websocketDidDisconnect(_:error:) 中加入:
performSegue(withIdentifier:"websocketDisconnected", sender: self)
1
无论什么原因,只要 socket 被断开,这都会让用户返回到输入昵称界面。如果在你自己的 App 中,你应当在这里进行更健全的错误处理。
接着,在 websocketDidReceiveMessage(_:text:) 方法中:
// 1guardletdata = text.data(using: .utf16),letjsonData =try? JSONSerialization.jsonObject(with: data),letjsonDict = jsonDataas? [String: Any],letmessageType = jsonDict["type"]as? Stringelse{return}// 2ifmessageType =="message",letmessageData = jsonDict["data"]as? [String: Any],letmessageAuthor = messageData["author"]as? String,letmessageText = messageData["text"]as? String { messageReceived(messageText, senderName: messageAuthor)}
收到的文字消息是可读的字符串——如果是 JSON,尝试将其转为集合对象。代码解释如下:
首先将字符串转为 NSData,然后将 NSData 传给 JSONSerialization 对象以将其转为载体并返回一个有效的对象。最后还检查了几个重要的 key,并设置对应的值。如果对象无效,直接通过 guard 语句退出。
过滤消息的 messageType,然后将数据传递给 messageReceived(messageText:, senderName:) 方法。
下面是一个从 Node.js 收到的 JSON 格式的消息示例:
{ "type":"message", "data":{ "time":1472513071731, "text":":]", "author":"iPhone Simulator", "color":"orange"}}
运行 app,每当你发送一条消息,emoji 将会用你选择的 emoji 和昵称刷新。返回 web 控制台,你的 emoji 消息也会显示。
这就是 Starscream 的使用!
在这里下载最终完成的项目。
Emoji Communicator 是一个使用 WebSocket 的最简单的例子。如果你想在已经存在的服务中使用 Starscream,你可以参考更多资料:
参考 Starscream 在 GitHub 上的项目主页 。
参考 Mozilla 开发者网络 上关于 WebSocket 的介绍和如何实现 WebSocket。