見出し画像

非同期通信を実現するWebsocketという選択肢

どうも。たぱすたぱすたです。
技術記事を連載していきたいと思っているのですが、面白そうなものをなかなか思いつかなくて早くもネタ枯渇気味です。

記事を書く意図としては、世の中へ技術共有をしていきたい!なんて大っぴらな信念はなく、正直今後のキャリアを見据えたときにポートフォリオ的なものにならないかな、くらいの気持ちで書いています。

前回の記事同様前置きは駄文なので、興味ない方は次のセクションへ飛ばしてくださいね。

前置き

現在私はメーカーでとある製品のネットワーク評価を行っています。
ネットワーク評価といってもパケット一つ一つを監視して、ハンドシェイクうまくいってるなあなど見ているわけではなくて、アプリ層レベルでうまく動作してるか程度の確認になります。
情報系学部卒で開発はやってないの?と疑問に思うかもしれませんが、自分の所属部署は評価専門の部署となっています。(配属面談でQAに興味あると言ったことから配属されました。開発経験詰めないのがかなり痛いです……)

ただ、評価専門だからといってプログラム書かないかといわれると、まったくそんなことありません。
部署のミッションといいますか、世のQA業界全体で、評価を自動化して作業を効率化しようという風潮があります。
手動で行っていたことをPCに任せることで人件費も浮きますし、ヒューマンエラーも減るわけですから、この流れは理にかなっていると思っています。

今回は評価自動化の際にどうしても非同期通信が必要になったことで実装したものになります。

同期通信と非同期通信

Googleなどで検索するときや、Amazonで買い物するとき、ページの取得のために同期通信が行われており、HTTPという仕様で通信を行っています。

図3

このとき、必ずクライアントがサーバーに要求(リクエスト)を出し、要求に対してサーバーがクライアントに返事(レスポンス)を返します。

ここで1つ不便な点があって、サーバー起点でクライアントに通信を行うことはできないのです。
なら、クライアントが定期的にサーバーにリクエストを出してあげればいいじゃないか、と考えると思います。これはポーリング方式として知られているのですが、ポーリングの場合、間隔が短いと通信量の肥大化につながりますし、間隔が長すぎると情報が欲しいタイミングでの通信できません。
またポーリング間隔が極端に短すぎると、サーバーにDDoS攻撃と見なされることもあるので注意が必要です。
ロングポーリングという手法もありますが、これは疑似的な非同期通信なので今回は触れません。

今回用いたものはWebsocketというプロトコルになります。

> WebSocket(ウェブソケット)は、単一のTCPコネクション上に双方向通信のチャンネルを提供する、コンピュータの通信プロトコルの1つである。
引用:Wikipedia

TCPベースなのでHTTPとは別物ですね。しかし、ハンドシェイク時にHTTPをアップグレードして通信を切り替えます。HTTPに比べてパケットのヘッダが短いデータを大量に送信するのに向いてます。

図4

図のように、クライアントからも、サーバーからも通信をしかけることができます(メッセージといいます)

実装例

今回Websocket通信を行うにあたり、Socket.IOというライブラリを使用しました。

Sokcet.IOについてはリンク先の公式リファレンスが概念を理解しやすいです。
Socket.IOがWebsocketの通信処理をラップしてくれているので、node.jsやpythonで簡単に使うことができます。

今回はクライアントサイドのみ紹介します。
(サーバーサイドは負荷分散の技術と一緒に記事にしたいと思っています)

作成したアプリは、BitCoinの値段をリアルタイムで取得するアプリとなります。
BitCoinだとウェブページを定期的にスクレイピングして値段を取得する方法が定番ですが、Websocketを使った実装だとサーバーが自発的に値動きを教えてくれるので便利ですね(とはいえ、サーバーとはコネクションを張りっぱなしにするので、どちらが省エネかといわれると難しいところです)

前回同様GitHubにコードを掲載しているのでぜひ活用してみてください。

def connect(self):
   self.sio.register_namespace(MyCustomNamespace(
       self.path, self.sio, self.host))
   # transports設定しないとwebsocket通信にならない
   self.sio.connect(self.host, transports=['websocket'])
   self.sio.wait()

ここでポイントとなるのは、transports=['websocket']を指定してあげることです。Socket.IOの通信方法にはpollingとwebsocketの2種類があり、デフォルトでは2つの中からどちらかが選択されます。
今回はWebsocket通信だけをさせたいので、明示的に定義しておきましょう。

    def on_lightning_executions_BTC_JPY(self, msg):
       print('[{}] response : {}'.format(
           datetime.now().strftime('%Y-%m-%d %H:%M:%S'), msg))
       # li = json.loads(msg.replace('\'','\"'))
       li = msg

       for e in li:
           rawdate = e['exec_date'].replace(
               e['exec_date'][e['exec_date'].find('.'):], '+00:00').replace(' ', '')
           gottendate = datetime.fromisoformat(rawdate)
           delta = gottendate - self.last_tweeted
           if delta.seconds >= 300:
               body = 'BTC now price: '+str(e['price'])+'JPY.'
               api.update_status(body)
               self.last_tweeted = gottendate

on_xxxという関数で、サーバーから受け取ったメッセージに対する処理を定義します。上の例ではサーバーからlightning_executions_BTC_JPYというメッセージがきたときに、一緒に送られてきた値段情報をTwitterBotでつぶやくという処理を記述しています。

終わりに

今回はWebsocket通信を用いて非同期通信を行うクライアントアプリを紹介しました。
サーバーサイドとの連携次第ではできることは無限大だと個人的に思っています。

サーバー側の実装もとても簡単なので、次回はそちらについて記事にしたいと思っています。

それでは、次回もよろしくお願いいたします。

この記事が気に入ったらサポートをしてみませんか?