WebSocket握手问题

7
我正在使用Python实现一个简单的WebSocket服务器。 我使用的握手来自http://en.wikipedia.org/w/index.php?title=WebSockets&oldid=372387414。 握手本身似乎是有效的,但当我点击发送时,我会收到JavaScript错误:

Uncaught Error: INVALID_STATE_ERR: DOM Exception 11

这是HTML代码:
<!doctype html>
<html>
    <head>
        <title>ws_json</title>

    </head>
    <body onload="handleLoad();" onunload="handleUnload();">
        <input type="text" id='input' />
        <input type="button" value="submit" onclick="handleSubmit()" />
        <div id="display"></div>

        <script type="text/javascript">
            function showmsg(str){
                display = document.getElementById("display");
                display.innerHTML += "<p>" + str + "</p>";
            }

            function send(str){
                ws.send(str.length);
                ws.send(str);
            }

            function handleSubmit(){
                input = document.getElementById('input');
                send(input.value);
                input.focus();
                input.value = '';
            }

            function handleLoad(){
                ws = new WebSocket("ws://localhost:8888/");
                ws.onopen = function(){
                    showmsg("websocket opened.");
                }

                ws.onclose = function(){
                    showmsg("websocket closed.");
                }
            }

            function handleUnload(){
                ws.close();
            }
        </script>
    </body>
</html>

这里是Python代码:

import socket
import threading
import json

PORT = 8888
LOCATION = "localhost:8888"

def handler(s):

    print " in handler "

    ip, _ = s.getpeername()
    print "New connection from %s" % ip
    request = s.recv(1024)

    print "\n%s\n" % request
    print s.getpeername()

    # send response
    response = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
    response += "Upgrade: WebSocket\r\n"
    response += "Connection: Upgrade\r\n"
    try:
        peername = s.getpeername()
        response += "Sec-WebSocket-Origin: http://%s\r\n" % peername[0] # % request[request.index("Origin: ")+8:-4]
    except ValueError:
        print "Bad Request"
        raise socket.error
    response += "Sec-WebSocket-Location: ws://%s\r\n" % LOCATION
    response += "Sec-WebSocket-Protocol: sample"
    response = response.strip() + "\r\n\r\n"

    print response
    s.send(response)

    while True:
        length = s.recv(1)
        print length
        if not length:
            break
        length = int(length)
        print "Length: %i" % length
        data = s.recv(length)
        print "Received: %s" % data
        print ""

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('localhost', PORT))
s.listen(5)

print "server is running..."
while True:
    sock, addr = s.accept()
    threading.Thread(target=handler, args=(sock, )).start()

有人知道我在这里做错了什么吗?


我无法测试您的代码,因为缺少WebSocket类。它在哪里定义?Firefox 3.6.3似乎不知道它是什么。 - Nathan
Chrome Dev 已经有了,Firefox 4 应该也会有。 - lowerkey
如果你没有100声望分数,你怎么能提供赏金呢? - Nathan
这个ws对象是如何被handleLoad()和send(str)这两个函数引用的?它是在其他地方声明的全局变量吗,我没有看到吗? - abelito
在Javascript中,全局变量只是命名的,而局部变量则使用var关键字创建。 - Nathan
@Nathan:我原本有超过100的声望值,发了悬赏之后,现在我的声望值不到100了。 - lowerkey
2个回答

6

我在Firefox 4上测试了你的代码,当点击发送时出现了相同的错误,在此之前我得到了以下提示:

Firefox无法建立与ws://localhost:8888/的服务器的连接。

这可能是WebSocket对象被销毁的原因。我怀疑你的握手响应缺少某些内容,因此Firefox关闭了该套接字。

从Wikipedia关于Websockets的文章中可以看到:

Sec-WebSocket-Key1和Sec-WebSocket-Key2字段以及字段后的八个字节是随机令牌,服务器使用它们来构造一个16字节的令牌,在握手结束时证明它已经读取了客户端的握手。

你的服务器响应没有这个特殊的数字,所以我认为我们需要找出如何生成它,并将其包含在其中。

编辑:如何生成那个数字

让我们从key1、key2和握手结束时的8个字节开始

key1 = "18x 6]8vM;54 *(5:  {   U1]8  z [  8"
key2 = "1_ tx7X d  <  nw  334J702) 7]o}` 0"
end8 = "Tm[K T2u"

我们通过忽略除0-9之外的每个字符为每个键创建一个数字。在Python中:

def numFromKey(key):
    return int(filter(lambda c: c in map(str,range(10)),key))

接下来,我们将原始密钥字符串中的空格数量除以空格数,因此这里是一个函数,用于计算字符串中的空格数。

def spacesIn(key):
    return len(filter(lambda c: c==' ',key))

这些键生成的两个数字是:
pkey1 = numFromKey(key1)/spacesIn(key1)
pkey2 = numFromKey(key2)/spacesIn(key2)

现在我们需要将pkey1、pkey2和end8的字节连接起来。处理后的密钥需要表示为32位大端序数字。
from struct import pack
catstring = pack('>L',pkey1) + pack('>L',pkey2) + end8

然后,我们对这些字节进行md5哈希,以获得我们添加到握手末尾的魔数。

import md5
magic = md5.new(catstring).digest()

这至少是我认为它的工作原理。

谢谢提供的信息,我自己永远也想不到。我在Google上找到了这个链接:http://golang.org/src/pkg/websocket/server.go 它描述了如何生成密钥。我正在努力理解它。 - lowerkey
这是握手的更好描述:http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76#page-7 - lowerkey
1
我找到了类似的解决方案,但是在将MD5算法的结果编码成UTF-8时遇到了问题。 - lowerkey
这是我的握手代码,但正如我所说,utf-8编码存在问题。 - lowerkey
这里有一个Ruby实现:http://github.com/igrigorik/em-websocket/blob/master/lib/em-websocket/handler76.rb。至于将MD5哈希编码为UTF-8,这是不必要的,因为ASCII是UTF-8的子集。因此,ASCII字符串(MD5哈希仅包含[0-9a-f])自动有效地成为UTF-8。 - igorw
我也无法将magic(返回令牌)添加到头响应中。 我一直收到UnicodeDecodeError 'ascii' codec can't decode byte XXXX in position Y: ordinal not in range(128) 错误,其中 XXXX 是32位字节,Y 是字节位置。 有什么想法吗? 这可能是个简单的问题 :/ - gthmb

0

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