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

5

这是关于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函数中吧? - Chris Morgan
抱歉,复制粘贴错误。已经更正。 - Jivings
你需要 spaces = -1 是因为在头部的 : 后面第一个空格不应该被忽略(例如 Sec-WebSocket-Key1: a b 只包含一个空格,就响应而言)。使用 line.partition(": ") 可以避免这种情况。 - dbr
2个回答

5
我发现您存在以下两个问题:
  • 您没有正确计算空格。您的计数器应该从0开始而不是-1。
  • 您的响应标头仍然采用v75样式。在v76中,任何以“WebSocket-”开头的标头(如WebSocket-Origin,WebSocket-Location,WebSocket-Protocol)都应改为以“Sec-WebSocket-”开头。
以下是我在wsproxy(noVNC的一部分,它是一个HTML5 VNC客户端)中计算响应chksum的方法:
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,我没有注意到它们已经更改了。谢谢你的帮助,我会告诉你结果的。 - Jivings
仍然没有运气,但我已经更新了问题以显示请求和响应。 - Jivings
哇嘿!我把我的代码改成了你的回应代码,它运行起来了 :) 尽管我不确定我的代码有什么问题,但还是谢谢你! - Jivings

2
这是一个WebSocket客户端/服务器的工作示例(Javascript客户端,Python 2.6服务器)。
它使用了来自各个地方的例子(包括kanaka的答案/noVNC以及this pagethis page)。
可以在Chrome 10.0.648.127、Safari 5.0.3和iOS 4.3及以上版本的iPad上使用。
这并不是一份良好编写的代码(特别是示例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>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接