【问题标题】:Sending / receiving WebSocket message over Python socket / WebSocket Client通过 Python 套接字/WebSocket 客户端发送/接收 WebSocket 消息
【发布时间】:2017-09-30 14:18:51
【问题描述】:

我写了一个简单的 WebSocket 客户端。我在这里使用了我在 SO 上找到的代码:How can I send and receive WebSocket messages on the server side?

我正在使用Python 2.7,我的服务器是echo.websocket.org,位于80 TCP 端口。基本上,我认为我在接收消息时遇到了问题。 (或者发送也有问题?)

至少我确定握手一切正常,因为我收到了良好的握手响应:

HTTP/1.1 101 Web Socket Protocol Handshake
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: content-type
Access-Control-Allow-Headers: authorization
Access-Control-Allow-Headers: x-websocket-extensions
Access-Control-Allow-Headers: x-websocket-version
Access-Control-Allow-Headers: x-websocket-protocol
Access-Control-Allow-Origin: http://example.com
Connection: Upgrade
Date: Tue, 02 May 2017 21:54:31 GMT
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Server: Kaazing Gateway
Upgrade: websocket

还有我的代码:

#!/usr/bin/env python
import socket

def encode_text_msg_websocket(data):
    bytesFormatted = []
    bytesFormatted.append(129)

    bytesRaw = data.encode()
    bytesLength = len(bytesRaw)

    if bytesLength <= 125:
        bytesFormatted.append(bytesLength)
    elif 126 <= bytesLength <= 65535:
        bytesFormatted.append(126)
        bytesFormatted.append((bytesLength >> 8) & 255)
        bytesFormatted.append(bytesLength & 255)
    else:
        bytesFormatted.append(127)
        bytesFormatted.append((bytesLength >> 56) & 255)
        bytesFormatted.append((bytesLength >> 48) & 255)
        bytesFormatted.append((bytesLength >> 40) & 255)
        bytesFormatted.append((bytesLength >> 32) & 255)
        bytesFormatted.append((bytesLength >> 24) & 255)
        bytesFormatted.append((bytesLength >> 16) & 255)
        bytesFormatted.append((bytesLength >> 8) & 255)
        bytesFormatted.append(bytesLength & 255)

    bytesFormatted = bytes(bytesFormatted)
    bytesFormatted = bytesFormatted + bytesRaw
    return bytesFormatted


def dencode_text_msg_websocket(stringStreamIn):
    byteArray = [ord(character) for character in stringStreamIn]
    datalength = byteArray[1] & 127
    indexFirstMask = 2
    if datalength == 126:
        indexFirstMask = 4
    elif datalength == 127:
        indexFirstMask = 10
    masks = [m for m in byteArray[indexFirstMask: indexFirstMask + 4]]
    indexFirstDataByte = indexFirstMask + 4
    decodedChars = []
    i = indexFirstDataByte
    j = 0
    while i < len(byteArray):
        decodedChars.append(chr(byteArray[i] ^ masks[j % 4]))
        i += 1
        j += 1
    return ''.join(decodedChars)

# connect 
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((socket.gethostbyname('echo.websocket.org'), 80))

# handshake
handshake = 'GET / HTTP/1.1\r\nHost: echo.websocket.org\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: gfhjgfhjfj\r\nOrigin: http://example.com\r\nSec-WebSocket-Protocol: echo\r\n' \
        'Sec-WebSocket-Version: 13\r\n\r\n'
sock.send(handshake)
print sock.recv(1024)

# send test msg
msg = encode_text_msg_websocket('hello world!')
sock.sendall(msg)

# receive it back
response = dencode_text_msg_websocket(sock.recv(1024))
print '--%s--' % response

sock.close()

这里有什么问题?握手后变得复杂。

dencode_text_msg_websocket 方法返回一个空字符串,但它应该返回与我发送到服务器的字符串相同的字符串,即hello world!

我不想使用库(我知道如何使用它们)。问题是关于在没有库的情况下仅使用套接字来实现相同的事情。

我只想向echo.websocket.org server 发送消息并收到回复,仅此而已。我不想修改标头,只需构建此服务器使用的标头即可。我使用 Wireshark 检查了它们的外观,并尝试使用 Python 构建相同的数据包。

对于以下测试,我使用了我的浏览器:

未屏蔽的数据,从服务器到客户端:

屏蔽数据,从客户端到服务器:

【问题讨论】:

  • 您的套接字层级别为 HIGH,您无法访问所有标头进行重新配置。在准备使用套接字连接时仅选择 TCP 或 UDP。
  • @dsgdfg:我想我不明白你的意思。我只想向echo.websocket.org 服务器发送消息,仅此而已。我不想修改标头,只需构建此服务器使用的标头即可。我使用 Wireshark 检查了它们的外观,并尝试使用 Python 构建相同的数据包。请查看我的编辑。
  • 在解码定义中,您的代码与您基于它的代码之间存在一个根本区别。您不转换输入 byteArray = stringStreamIn 而是依靠简单地转换单个字符来获取长度。原代码转换整个输入字符串byteArray = [ord(character) for character in stringStreamIn]
  • @RolfofSaxony:即使我将代码更改为您建议的内容,最后一个 print 也会打印一个空字符串作为响应。
  • 我怀疑是你的数据编码,看websocket-client中的python代码。 sudo pip install websocket-client

标签: python sockets websocket protocols decode


【解决方案1】:

我已将您的代码修改为至少发送回复并接收回复的内容,方法是将编码更改为使用chr() 在标题中插入字节字符串而不是小数。我独自留下的解码,但这里的另一个答案有一个解决方案。
真正的胆量在这里详细介绍https://www.rfc-editor.org/rfc/rfc6455.txt
其中详细说明了您必须做什么

#!/usr/bin/env python
import socket
def encode_text_msg_websocket(data):
    bytesFormatted = []
    bytesFormatted.append(chr(129))
    bytesRaw = data.encode()
    bytesLength = len(bytesRaw)
    if bytesLength <= 125:
        bytesFormatted.append(chr(bytesLength))
    elif 126 <= bytesLength <= 65535:
        bytesFormatted.append(chr(126))
        bytesFormatted.append((chr(bytesLength >> 8)) & 255)
        bytesFormatted.append(chr(bytesLength) & 255)
    else:
        bytesFormatted.append(chr(127))
        bytesFormatted.append(chr((bytesLength >> 56)) & 255)
        bytesFormatted.append(chr((bytesLength >> 48)) & 255)
        bytesFormatted.append(chr((bytesLength >> 40)) & 255)
        bytesFormatted.append(chr((bytesLength >> 32)) & 255)
        bytesFormatted.append(chr((bytesLength >> 24)) & 255)
        bytesFormatted.append(chr((bytesLength >> 16)) & 255)
        bytesFormatted.append(chr((bytesLength >> 8)) & 255)
        bytesFormatted.append(chr(bytesLength) & 255)
    send_str = ""
    for i in bytesFormatted:
        send_str+=i
    send_str += bytesRaw
    return send_str

# connect 
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5.0)
try:
    sock.connect((socket.gethostbyname('ws.websocket.org'), 80))
except:
    print "Connection failed"
handshake = '\
GET /echo HTTP/1.1\r\n\
Host: echo.websocket.org\r\n\
Upgrade: websocket\r\n\
Connection: Upgrade\r\n\
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n\
Origin: http://example.com\r\n\
WebSocket-Protocol: echo\r\n\
Sec-WebSocket-Version: 13\r\n\r\n\
'
sock.send(bytes(handshake))
data = sock.recv(1024).decode('UTF-8')
print data

# send test msg
msg = encode_text_msg_websocket('Now is the winter of our discontent, made glorious Summer by this son of York')
print "Sent: ",repr(msg)
sock.sendall(bytes(msg))
# receive it back
response = sock.recv(1024)
#decode not sorted so ignore the first 2 bytes
print "\nReceived: ", response[2:].decode()
sock.close()

结果:

HTTP/1.1 101 Web Socket Protocol Handshake
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: content-type
Access-Control-Allow-Headers: authorization
Access-Control-Allow-Headers: x-websocket-extensions
Access-Control-Allow-Headers: x-websocket-version
Access-Control-Allow-Headers: x-websocket-protocol
Access-Control-Allow-Origin: http://example.com
Connection: Upgrade
Date: Mon, 08 May 2017 15:08:33 GMT
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Server: Kaazing Gateway
Upgrade: websocket


Sent:  '\x81MNow is the winter of our discontent, made glorious Summer by this son of York'

Received:  Now is the winter of our discontent, made glorious Summer by this son of York

我应该在这里指出,这将是一头猪,而无需像@gushitong 所做的那样引入一些额外的库。

【讨论】:

    【解决方案2】:

    编码为https://www.rfc-editor.org/rfc/rfc6455#section-5.1:

    您应该屏蔽客户端框架。 (而且服务器框架根本没有被屏蔽。)

    • 客户端必须屏蔽它的所有帧 发送到服务器(有关详细信息,请参阅第 5.3 节)。 (笔记 无论 WebSocket 协议是否正在运行,都会完成屏蔽 通过 TLS。)服务器必须在收到一个 未屏蔽的帧。在这种情况下,服务器可以发送关闭 状态码为 1002(协议错误)的帧,如 第 7.4.1 节。 服务器不得屏蔽它发送到的任何帧 客户端。 如果客户端检测到被屏蔽的连接,它必须关闭连接 框架。

    这是一个工作版本:

    import os
    import array
    import six
    import socket
    import struct
    
    OPCODE_TEXT = 0x1
    
    try:
        # If wsaccel is available we use compiled routines to mask data.
        from wsaccel.xormask import XorMaskerSimple
        
        def _mask(_m, _d):
            return XorMaskerSimple(_m).process(_d)
    
    except ImportError:
        # wsaccel is not available, we rely on python implementations.
        def _mask(_m, _d):
            for i in range(len(_d)):
                _d[i] ^= _m[i % 4]
    
            if six.PY3:
                return _d.tobytes()
            else:
                return _d.tostring()
    
    
    def get_masked(data):
        mask_key = os.urandom(4)
        if data is None:
            data = ""
    
        bin_mask_key = mask_key
        if isinstance(mask_key, six.text_type):
            bin_mask_key = six.b(mask_key)
    
        if isinstance(data, six.text_type):
            data = six.b(data)
    
        _m = array.array("B", bin_mask_key)
        _d = array.array("B", data)
        s = _mask(_m, _d)
    
        if isinstance(mask_key, six.text_type):
            mask_key = mask_key.encode('utf-8')
        return mask_key + s
    
    
    def ws_encode(data="", opcode=OPCODE_TEXT, mask=1):
        if opcode == OPCODE_TEXT and isinstance(data, six.text_type):
            data = data.encode('utf-8')
    
        length = len(data)
        fin, rsv1, rsv2, rsv3, opcode = 1, 0, 0, 0, opcode
    
        frame_header = chr(fin << 7 | rsv1 << 6 | rsv2 << 5 | rsv3 << 4 | opcode)
    
        if length < 0x7e:
            frame_header += chr(mask << 7 | length)
            frame_header = six.b(frame_header)
        elif length < 1 << 16:
            frame_header += chr(mask << 7 | 0x7e)
            frame_header = six.b(frame_header)
            frame_header += struct.pack("!H", length)
        else:
            frame_header += chr(mask << 7 | 0x7f)
            frame_header = six.b(frame_header)
            frame_header += struct.pack("!Q", length)
    
        if not mask:
            return frame_header + data
        return frame_header + get_masked(data)
    
    
    def ws_decode(data):
        """
        ws frame decode.
        :param data:
        :return:
        """
        _data = [ord(character) for character in data]
        length = _data[1] & 127
        index = 2
        if length < 126:
            index = 2
        if length == 126:
            index = 4
        elif length == 127:
            index = 10
        return array.array('B', _data[index:]).tostring()
    
    
    # connect
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((socket.gethostbyname('echo.websocket.org'), 80))
    
    # handshake
    handshake = 'GET / HTTP/1.1\r\nHost: echo.websocket.org\r\nUpgrade: websocket\r\nConnection: ' \
                'Upgrade\r\nSec-WebSocket-Key: gfhjgfhjfj\r\nOrigin: http://example.com\r\nSec-WebSocket-Protocol: ' \
                'echo\r\n' \
                'Sec-WebSocket-Version: 13\r\n\r\n'
    
    sock.send(handshake)
    print(sock.recv(1024))
    
    sock.sendall(ws_encode(data='Hello, China!', opcode=OPCODE_TEXT))
    
    # receive it back
    response = ws_decode(sock.recv(1024))
    print('--%s--' % response)
    
    sock.close()
    

    【讨论】:

    • 谢谢。它现在完美运行。但是,我不明白一些代码部分。你能帮忙吗?这个:frame_header = chr(fin &lt;&lt; 7 | rsv1 &lt;&lt; 6 | rsv2 &lt;&lt; 5 | rsv3 &lt;&lt; 4 | opcode) 和这个:chr(mask &lt;&lt; 7 | length) 等等。它们是做什么的?
    • @yak 见 5.2。我提供的链接中的基本框架协议
    • @RolfofSaxony:我知道它们应该是什么样子。问题是,我看不懂代码。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-10
    相关资源
    最近更新 更多