[備忘録]SwiftでWebSocketを試してみる
SwiftでWebSocketを試してみた。
サーバーは楽なのでSinatraを使う。。。
・Sinatra側WebSocketサーバー
(1) sinatraでwebsocket使えるようgemをインストール
sudo gem install sinatra-websocket
(2) test_app1.rb
# coding: utf-8
require 'sinatra'
require 'sinatra/reloader'
require 'sinatra-websocket'
require 'json'
# 接続ポート
set :port, 1234
set :server, 'thin' #, group: :development
set :sockets, []
# 標準出力ログ出力
$stdout.sync = true
get '/' do
erb :fuga
end
get '/pingpong' do
if request.websocket? then
puts "request received...:[#{request.inspect}]"
request.websocket do |ws|
ws.onopen do
#puts "ws on open..:[#{ws.inspect}]"
settings.sockets << ws
end
ws.onmessage do |msg|
#puts "ws on receive msg.."
settings.sockets.each do |s|
s.send("gkgk pong:#{msg}")
end
end
ws.onclose do
settings.sockets.delete(ws)
end
end
end
end
/pingpongというURLでWebSocketのリクエストを受ける
クライアント側がメッセージを投げたら、"gkgk pong:#{投げたメッセージ}"を返却する感じ。
(3) views/fuga.erb
<!DOCTYPE HTML>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-Control" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>hoge</title>
</head>
<body>
<form id="form">
<input type="text" id="send_msg">
<input type="submit">
</form>
<ul id="msgs"></ul>
<script type="text/javascript">
(function (){
window.onload = function(){
var msgbox = document.getElementById("msgs");
var form = document.getElementById("form");
var send_msg = document.getElementById("send_msg");
var tttt = 'ws://' + window.location.host + "/pingpong";
console.log(tttt);
var ws = new WebSocket('ws://' + window.location.host + "/pingpong");
ws.onopen = function() {
console.log("connection opened");
}
ws.onclose = function() {
console.log("connection closed");
}
ws.onmessage = function(m) {
var li = document.createElement("li");
li.textContent = m.data;
msgbox.insertBefore(li, msgbox.firstChild);
}
send_msg.onclick = function(){
send_msg.value = "";
}
form.onsubmit = function(){
ws.send(send_msg.value);
send_msg.value = "";
return false;
}
}
})();
</script>
ブラウザでも確認出来るよーに、インデックスページを用意。。。
(4) サーバー起動
ruby ./test_app1.rb
== Sinatra (v2.0.0) has taken the stage on 1234 for development with backup from Thin
Thin web server (v1.7.0 codename Dunder Mifflin)
Maximum connections set to 1024
Listening on 0.0.0.0:1234, CTRL+C to stop
ポート:1234で起動する。
・iOS側WebSocketクライアント
イベントのログを表示するTableViewを画面上部、
真ん中に、送信するメッセージを入力するテキストボックス、
その下に、サーバーとの接続ボタンを配置した。
(1) シングルビューのSwiftのプロジェクトを作成し、cocoapodsで、Swift用のWebSocketのライブラリをインストール
platform :ios, '9.0'
use_frameworks!
target 'WebSockTest1' do
pod 'Reachability'
pod 'XCGLogger'
pod 'SwiftWebSocket'
end
プロジェクト名は、「WebSockTest1」で作成している。
使用したライブラリは、「SwiftWebSocket」。
(2) ViewControllerのソースを以下のように。。。
import UIKit
import SwiftWebSocket
class ViewController: UIViewController,
UITableViewDataSource,
UITableViewDelegate,
UITextFieldDelegate {
static let HOGE_URL = "ws://localhost:1234/pingpong"
@IBOutlet weak var tblChat: UITableView!
@IBOutlet weak var opeContainer: UIView!
@IBOutlet weak var txtMessage: UITextField!
@IBOutlet weak var btnConnect: UIButton!
@IBOutlet weak var opeContainerHeight: NSLayoutConstraint!
var ws: WebSocket!
var flgWsInit = false
var eventLogs = [String]()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
setupWs()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
@IBAction func tapBtnConnect(_ sender: Any) {
if ws == nil || ws.readyState != .open {
if ws == nil {
setupWs()
}
ws.open(ViewController.HOGE_URL)
btnConnect.isEnabled = false
}
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
let msg = textField.text!
if msg == "" {
return true
}
if ws.readyState == .open {
ws.send(msg)
self.addWsEvent(ev: "send: \(msg)")
}
textField.text = ""
return true
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 0.0
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80.0
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: EventCell?
cell = tableView.dequeueReusableCell(withIdentifier: "CELL_EV",
for: indexPath) as? EventCell
if cell == nil {
cell = EventCell.instance()
}
cell!.txtEvent.text = eventLogs[indexPath.item]
return cell!
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return eventLogs.count
}
private func setupUI() {
txtMessage.text = ""
txtMessage.clearButtonMode = .always
txtMessage.returnKeyType = .go
txtMessage.autocapitalizationType = .none
txtMessage.spellCheckingType = .no
txtMessage.delegate = self
if txtMessage.canBecomeFirstResponder {
txtMessage.becomeFirstResponder()
}
tblChat.layer.borderWidth = 1.0
tblChat.layer.borderColor = UIColor.lightGray.cgColor
tblChat.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
tblChat.register(
UINib(nibName: "EventCell", bundle: nil),
forCellReuseIdentifier: "CELL_EV")
tblChat.dataSource = self
tblChat.delegate = self
}
func addWsEvent(ev: String) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {[unowned self] in
self.tblChat.beginUpdates()
self.eventLogs.insert(ev, at: 0)
let indexPath = IndexPath(row: 0, section: 0)
self.tblChat.insertRows(at: [indexPath], with: .automatic)
self.tblChat.endUpdates()
for idx in self.tblChat.indexPathsForVisibleRows! {
if idx.item != 0 {
self.tblChat.cellForRow(at: idx)?.setSelected(false, animated: false)
}
}
self.tblChat.cellForRow(at: indexPath)?.setSelected(true, animated: false)
if self.txtMessage.canBecomeFirstResponder {
self.txtMessage.becomeFirstResponder()
}
}
}
private func setStateBtnConn(isEnable: Bool) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {[unowned self] in
self.btnConnect.isEnabled = isEnable
}
}
private func setupWs() {
ws = WebSocket()
// ws = WebSocket(ViewController.HOGE_URL)
if !flgWsInit {
ws.event.open = {[unowned self] in
log.debug("opened:\(ViewController.HOGE_URL)")
self.addWsEvent(ev: "opened:\(ViewController.HOGE_URL)")
self.setStateBtnConn(isEnable: false)
}
ws.event.close = {[unowned self] code, reason, clean in
log.debug("close")
self.addWsEvent(ev: "close.")
self.setStateBtnConn(isEnable: true)
}
ws.event.error = {[unowned self] error in
log.debug("error \(error)")
self.addWsEvent(ev: "error \(error)")
}
ws.event.message = {[unowned self] message in
if let text = message as? String {
log.debug("recv: \(text)")
self.addWsEvent(ev: "recv: \(text)")
if self.ws.readyState != .open {
self.setStateBtnConn(isEnable: false)
}
}
}
flgWsInit = true
}
}
}
iOSアプリ側の仕様は、
・ConnectボタンでWebSocketサーバーに接続する。
・テキストボックスにメッセージを入力し、エンターキーを入力すると、サーバーにメッセージを送信する。
・サーバーからのレスポンスは、UITableViewのセルを追加して表示する。
(UIスレッドじゃないはずなので、表示はUIスレッドに遅延で更新するようにした。(DispatchQueue.main.asyncAfter))
という感じ。
ローカルでテストするので、Info.plistにセキュアでない通信を許可するよう(ATS)設定しておく。
・sinatraを起動した状態で、エミュレーターで実行してみる。
接続したログが、テーブルビューに追加される。
(3)テキストボックスに「HOGE」を入力し、エンターキーを押す。
「HOGE」を送信したログが追加され、続けて、
「gkgk pong:HOGE」がサーバーから返答されてるログを表示。
(4)インデックスページを立ち上げ、エミュレーターをそのままにし、
インデックスページからメッセージを送信してみる。
(5)「fuga」と入力し、送信ボタンをタップ。
「fuga」を受信している。
これは、
Sinatra上で接続をArrayにキャッシュしていて、いま接続中のコネクションに全てに対して、メッセージを送信しているから。
という感じで、SinatraとCocoaPodsのライブラリで簡単に試すことが出来た。。。
Author And Source
この問題について([備忘録]SwiftでWebSocketを試してみる), 我々は、より多くの情報をここで見つけました https://qiita.com/cyclon2joker/items/c22623f8f86121959c84著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .