Help us understand the problem. What is going on with this article?

WebSocketクライアント

More than 5 years have passed since last update.

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を指定しなくてもおそらく問題ないと思いますが、今回はわかりやすいように指定しました。
いつも通りソースをのせるだけになりましたが、何かの役に立てればと思います。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした