20
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

WebSocketクライアント

Last updated at Posted at 2015-02-22

#WebSocketクライアント
今更感がかなりありますが、WebSocketクライアントを少し検証したので、久しぶりに投稿します。
今回は普段WebSocket通信を行う場合、ほとんどの処理をブラウザが行ってくれます。その処理の中身をちょっとだけ実践してみようと思います。

#用意したもの
・tornadoサーバー(さくらクラウド)
・クライアントPC(マイMac)

#WebSocketとは
念のため、WebSocketについてです。
HTTP通信で使用したコネクションをそのままデータ通信に使用するようなプロトコルです。
そのため、HTTP通信でSwitching protocolを行うハンドシェイクを行います。

#サーバー側
tornadoでサーバー起動させましたが、ソースは

import tornado.ioloop
import tornado.web
import tornado.websocket
from tornado import options

class MainHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        self.render("index.html")

class WebSocket(tornado.websocket.WebSocketHandler):
    def open(self):
        print("open websocket connection")

    def on_message(self, message):
        print(message)

    def on_close(self):
        print("close websocket connection")

app = tornado.web.Application([
    (r"/", MainHandler),
    (r"/ws", WebSocket),
])

if __name__ == "__main__":
    options.parse_command_line()
    app.listen(8080)
    tornado.ioloop.IOLoop.instance().start()
index.html
<html>
<head></head>
<body></body>
</html>

htmlは空っぽです。
このサーバーを起動させると8080ポートでlistenし、index.htmlを返却します。
WebSocketは/wsになります。

#クライアント
クライアントは自分のPCから行いました。
python3.4です。

import time
import os
import base64
import socket
import struct
import array


def make_ws_data_frame(data):
    FIN = 0x80
    RSV1 = 0x0
    RSV2 = 0x0
    RSV3 = 0x0
    OPCODE = 0x1
    MASK = 0x80
    payload = 0x0

    frame = struct.pack('B', FIN | RSV1 | RSV2 | OPCODE)
    data_len = len(data)
    if data_len <= 125:
        payload = struct.pack('B', MASK | data_len)
    elif data_len < 0xFFFF:
        payload = struct.pack('!BH', 126 | MASK, data_len)
    else:
        payload = struct.pack('!BQ', 127 | MASK, data_len)

    frame += payload
    masking_key = os.urandom(4)
    mask_array = array.array('B', masking_key)
    unmask_data = array.array('B', data.encode('UTF-8'))

    for i in range(data_len):
        unmask_data[i] = unmask_data[i] ^ masking_key[i % 4]

    mask_data = unmask_data.tobytes()
    frame += masking_key
    frame += mask_data

    return [frame, len(frame)]

HTTP_GET = """GET / HTTP/1.1\r
Connection: Keep-Alive\r
\r\n"""

ws_upgrade_header = {
    'Upgrade' : 'websocket',
    'Connection' : 'Upgrade',
    'Sec-WebSocket-Key' : base64.b64encode(os.urandom(16)).decode('UTF-8'),
    'Sec-WebSocket-Version' : '13',
}

sock = socket.create_connection(['my.domain.com', 8080])

sock.send(HTTP_GET.encode('UTF-8'))

recv_buf = ""
recv_buf = sock.recv(4096)

print(recv_buf.decode('UTF-8'))

headers = "GET /ws HTTP/1.1\r\nHost: my.domain.com\r\n"
for key in ws_upgrade_header:
    headers += key + ": " + ws_upgrade_header[key] + "\r\n"

headers += "\r\n"

print(headers)

sock.send(headers.encode('UTF-8'))

time.sleep(1)

recv_buf = sock.recv(4096)
print(recv_buf.decode('UTF-8'))

time.sleep(1)

message, message_len = make_ws_data_frame('Hello')

sock.send(message)

sock.close()

相変わらずエラー処理はしていません。
WebSocketデータマスクの処理などは、tornadoを参考にしました。
RFCを確認して、データの並びを見てみるといいと思います。

#実行
サーバーを起動後、クライアントからアクセスしてみると、

サーバー側

$ python3 server.py 
[I 150222 15:43:15 web:1811] 200 GET / (XXX.XXX.XXX.XXX) 1.82ms
open websocket connection
Hello
close websocket connection

クライアント

$>python3 ws_client.py 
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Server: TornadoServer/4.0.2
Date: Sun, 22 Feb 2015 06:43:15 GMT
Etag: "7ead2de92161d299969a9eb938ab1bd212bbeb90"
Content-Length: 43

<html>
<head></head>
<body></body>
</html>

GET /ws HTTP/1.1
Host: XXX.XXX.XXX.XXX
Sec-WebSocket-Version: 13
Connection: Upgrade
Sec-WebSocket-Key: /LfLVpUsMF7UevDCeOSwhw==
Upgrade: websocket


HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: b0eu6R73OLCL/9hkzEcIjA8eguQ=

プロトコルがswitchしているのがわかるかと思います。
サーバー側にはクライアントから送信した「Hello」が受信されているのを確認できました。

#最後に
当たり前ですが、クライアント側の最初のGETメッセージで「Connection: Close」を指定するとWebSocketハンドシェイクできません。
Connectionを指定しなくてもおそらく問題ないと思いますが、今回はわかりやすいように指定しました。
いつも通りソースをのせるだけになりましたが、何かの役に立てればと思います。

20
18
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?