使用 Python 服务器的 Websocket 握手问题

Posted

技术标签:

【中文标题】使用 Python 服务器的 Websocket 握手问题【英文标题】:Websocket handshake problem using Python server 【发布时间】:2010-12-07 01:37:09 【问题描述】:

这是Websocket Protocol 76中关于握手的问题。

我已经编写了客户端和服务器,但无法获取客户端 接受握手。我可以看到它正在被返回,但是客户端立即关闭了连接。我猜我的 md5sum 响应一定是不正确的。

据我所知,我正在遵循正确的程序,谁能告诉我我做错了什么?

def create_handshake_resp(handshake):

  # parse request
  final_line = ""
  lines = handshake.splitlines()
  for line in lines:
    parts = line.partition(":")
    if parts[0] == "Sec-WebSocket-Key1":
      key1 = parts[2]
    elif parts[0] == "Sec-WebSocket-Key2":
      key2 = parts[2]
    final_line = line

  #concat the keys and encrypt
  e = hashlib.md5()
  e.update(parse_key(key1))
  e.update(parse_key(key2))
  e.update(final_line)
  return "HTTP/1.1 101 WebSocket Protocol Handshake\r\nUpgrade: WebSocket\r\nConnection:     Upgrade\r\nWebSocket-Origin: http://%s\r\nWebSocket-Location: ws://%s/\r\nWebSocket-Protocol: sample\r\n\r\n%s" % (httphost, sockethost, e.digest())



def parse_key(key):

  spaces = -1
  digits = ""
  for c in key:
    if c == " ":
      spaces += 1
    if is_number(c):
      digits = digits + c


  new_key = int(digits) / spaces
  return str(new_key)

如您所见,我正在对键执行我认为正确的操作(将数字除以空间计数、连接结果和请求的最后一行,然后是 MD5),并且 16 字节的响应肯定是返回。

任何帮助将不胜感激,一旦我有工作副本,我会在这里发布。

谢谢。

编辑:

更改了标题以符合 kanaka 的响应。客户仍然不接受握手。 我发现了如何在 Chromium 中显示请求,这是给出的请求和响应:

(P) t=1291739663323 [st=3101]     WEB_SOCKET_SEND_REQUEST_HEADERS  
                              --> GET / HTTP/1.1   
                                  Upgrade: WebSocket
                                  Connection: Upgrade
                                  Host: ---
                                  Origin: http://---
                                  Sec-WebSocket-Key1: 3E 203C 220 642;
                                  Sec-WebSocket-Key2: Lg 590 ~5 703O G7  =%t 9
                                                   
                                  \x74\x66\xef\xab\x50\x60\x35\xc6\x0a
(P) t=1291739663324 [st=3102]     SOCKET_STREAM_SENT     
(P) t=1291739663348 [st=3126]     SOCKET_STREAM_RECEIVED  
(P) t=1291739663348 [st=3126]     WEB_SOCKET_READ_RESPONSE_HEADERS  
                              --> HTTP/1.1 101 WebSocket Protocol Handshake
                                  Upgrade: WebSocket
                                  Connection: Upgrade
                                  Sec-WebSocket-Origin: http://---
                                  Sec-WebSocket-Location: ws://---/
                                  Sec-WebSocket-Protocol: sample
                                                   
                                  \xe7\x6f\xb9\xcf\xae\x70\x57\x43\xc6\x20\x85\xe7\x39\x2e\x83\xec\x0

逐字逐句,除非我出于明显的原因删除了 IP 地址。

【问题讨论】:

您的缩进没有意义。我认为#concat..return 的行应该在create_handshake_resp? 对不起。复制粘贴错误。已编辑。 您需要spaces = -1,因为您没有忽略标题中: 之后的第一个空格(例如,就响应而言,Sec-WebSocket-Key1: a b 仅包含一个空格)。做line.partition(": ") 可以防止这种情况发生 【参考方案1】:

你有几个问题马上就跳出来了:

您没有正确计算空格。您的计数器应该从 0 而不是 -1 开始。 您的响应标头仍然是 v75 样式。在 v76 中,任何以“WebSocket-”(WebSocket-Origin、WebSocket-Location、WebSocket-Protocol)开头的标头都应以“Sec-WebSocket-”开头。

这是我在 wsproxy 中计算响应校验和的方法(noVNC 一个 html5 VNC 客户端的一部分):

import struct, md5
...
spaces1 = key1.count(" ")
spaces2 = key2.count(" ")
num1 = int("".join([c for c in key1 if c.isdigit()])) / spaces1
num2 = int("".join([c for c in key2 if c.isdigit()])) / spaces2

return md5(struct.pack('>II8s', num1, num2, key3)).digest()

【讨论】:

从 -1 开始的空格计数是因为当字符串被拆分时,在 : 和键之间有一个空格,不应该计算在内。我将尝试将标题更改为 76,我没有注意到它们已更改。感谢您的帮助,我会让您知道结果。 仍然没有运气,但我已经更新了问题以显示请求和响应。 哇!我将我的代码更改为您的响应代码并且它可以工作:) 虽然我必须在空格上包含 -1。不确定我的问题是什么但无论如何谢谢!【参考方案2】:

这是一个 WebSocket 客户端/服务器的工作示例(javascript 中的客户端,Python 2.6 中的服务器)

它使用了来自不同地方的示例(包括 kanaka 的 answer/noVNC,以及 this page 和 this page)

适用于 ios 4.3 和 iPad 上的 Chrome 10.0.648.127、Safari 5.0.3 和 MobileSafari

这绝不是写得很好的代码(示例 HTML 页面特别糟糕) - 使用风险自负等等..

#!/usr/bin/env python

import socket
import threading
import struct
import hashlib

PORT = 9876


def create_handshake_resp(handshake):
    final_line = ""
    lines = handshake.splitlines()
    for line in lines:
        parts = line.partition(": ")
        if parts[0] == "Sec-WebSocket-Key1":
            key1 = parts[2]
        elif parts[0] == "Sec-WebSocket-Key2":
            key2 = parts[2]
        elif parts[0] == "Host":
            host = parts[2]
        elif parts[0] == "Origin":
            origin = parts[2]
        final_line = line

    spaces1 = key1.count(" ")
    spaces2 = key2.count(" ")
    num1 = int("".join([c for c in key1 if c.isdigit()])) / spaces1
    num2 = int("".join([c for c in key2 if c.isdigit()])) / spaces2

    token = hashlib.md5(struct.pack('>II8s', num1, num2, final_line)).digest()

    return (
        "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
        "Upgrade: WebSocket\r\n"
        "Connection: Upgrade\r\n"
        "Sec-WebSocket-Origin: %s\r\n"
        "Sec-WebSocket-Location: ws://%s/\r\n"
        "\r\n"
        "%s") % (
        origin, host, token)


def handle(s, addr):
    data = s.recv(1024)
    s.send(create_handshake_resp(data))
    lock = threading.Lock()

    while 1:
        print "Waiting for data from", s, addr
        data = s.recv(1024)
        print "Done"
        if not data:
            print "No data"
            break

        print 'Data from', addr, ':', data

        # Broadcast received data to all clients
        lock.acquire()
        [conn.send(data) for conn in clients]
        lock.release()

    print 'Client closed:', addr
    lock.acquire()
    clients.remove(s)
    lock.release()
    s.close()

def start_server():
    s = socket.socket()
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(('', PORT))
    s.listen(1)
    while 1:
        conn, addr = s.accept()
        print 'Connected by', addr
        clients.append(conn)
        threading.Thread(target = handle, args = (conn, addr)).start()

clients = []
start_server()

此外,还有一个糟糕的示例 HTML 页面来展示它的工作原理:

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Test</title>
        <script type="application/javascript">
            var ws;

            function init() 
                var servermsg = document.getElementById("servermsg");

                ws = new WebSocket("ws://localhost:9876/");
                ws.onopen = function()
                    servermsg.innerHTML = servermsg.innerHTML + "<br>Server connected";
                    servermsg.innerHTML = servermsg.innerHTML + "<br>Sending message to server";
                    ws.send("Hello Mr. Server!");
                ;
                ws.onmessage = function(e)
                    servermsg.innerHTML = servermsg.innerHTML + "<br>Recieved data: " + e.data;
                ;
                ws.onclose = function()
                    console.log("Server disconnected");
                    servermsg.innerHTML = servermsg.innerHTML + "<br>Connected";
                ;
            
            function postmsg()
                var text = document.getElementById("message").value;
                ws.send(text);
                servermsg.innerHTML = servermsg.innerHTML + "<br>Sent: " + text;
                return false;
            
        </script>
    </head>
    <body onload="init();">
        <form action="" onSubmit="postmsg()">
            <input type="text" name="message" value="" id="message">
            <input type="submit" name="submit" value="" id="submit">
        </form>
        <div id="servermsg"><h1>Message log:</h1></div>
    </body>
</html>

【讨论】:

以上是关于使用 Python 服务器的 Websocket 握手问题的主要内容,如果未能解决你的问题,请参考以下文章

Python 服务器 - 处理 WebSocket.close()

python怎么连接websocket

使用 Python 和 websocket 创建实时聊天

托管基于 Python 的 Websocket 服务器

python之websocket

Python - 单向 websocket