在 Python 中创建 WebSocket 客户端
Posted
技术标签:
【中文标题】在 Python 中创建 WebSocket 客户端【英文标题】:Creating a WebSocket Client in Python 【发布时间】:2013-06-08 06:32:33 【问题描述】:我正在尝试学习套接字编程以及 WebSocket 协议。我知道存在 Python Web 套接字客户端,但我希望构建一个玩具版本供我自己学习。为此,我创建了一个非常简单的 Tornado websocket 服务器,我在 localhost:8888
上运行。它所做的只是在客户端连接时打印一条消息。
这是整个服务器 - 它可以工作(我已经在我的浏览器中使用一个小的 javascript 脚本对其进行了测试)
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web
class WSHandler(tornado.websocket.WebSocketHandler):
def open(self):
print('new connection')
self.write_message("Hello World")
def on_message(self, message):
print('message received %s' % message)
def on_close(self):
print('connection closed')
application = tornado.web.Application([
(r'/ws', WSHandler),
])
if __name__ == "__main__":
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(8888)
tornado.ioloop.IOLoop.instance().start()
所以一旦我启动服务器,我会尝试运行以下脚本
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((socket.gethostbyname('localhost'), 8888))
msg = '''GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13'''.encode('ascii')
print(len(msg))
sent_count = sock.send(msg)
print('sent this many bytes:', sent_count)
recv_value = sock.recv(1)
print('recvieved:', recv_value)
我希望服务器会按照 RFC 中的规定发回响应标头。相反, sock.recv 挂起。这让我相信服务器没有确认 websocket 初始握手。此握手也从 RFC 中删除。我知道 websocket 密钥应该是随机的,但我认为这不会导致服务器忽略握手(websocket 密钥是有效的)。我想一旦我可以启动握手,我就可以解决剩下的问题,所以我希望在 websockets 如何工作或如何发送初始握手方面存在一些误解。
【问题讨论】:
【参考方案1】:1) 当您通过套接字发送消息时,您不知道它将被分成多少块。它可能会立即发送;或者可以发送前 3 个字母,然后发送其余的消息;或者消息可能会被分成 10 段。
2) 给定 1) 服务器应该如何知道它何时收到了客户端发送的所有块?例如,假设服务器接收到客户端消息的 1 块。服务器如何知道这是整个消息还是还有 9 个块即将到来?
3) 我建议您阅读以下内容:
http://docs.python.org/2/howto/sockets.html
(加上 cmets 中的链接)
4) 现在,你为什么不使用 python 来创建 HTTP 服务器?
python3:
import http.server
import socketserver
PORT = 8000
handler = http.server.SimpleHTTPRequestHandler
httpd = socketserver.TCPServer(("", PORT), handler)
print("serving at port", PORT)
httpd.serve_forever()
python2:
import SimpleHTTPServer
import SocketServer
PORT = 8000
handler = SimpleHTTPServer.SimpleHTTPRequestHandler
httpd = SocketServer.TCPServer(("", PORT), handler)
print "serving at port", PORT
httpd.serve_forever()
SimpleHTTPRequestHandler 从服务器程序的目录和下面提供文件,将请求 url 与您创建的目录结构相匹配。如果您请求“/”,服务器将在服务器所在的同一目录中提供一个 index.html 文件。这是一个用于 python 3 的客户端套接字示例(下面的 python 2 示例):
import socket
import sys
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error:
print('Failed to create socket')
sys.exit()
print('Socket Created')
#To allow you to immediately reuse the same port after
#killing your server:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
host = 'localhost';
port = 8000;
s.connect((host , port))
print('Socket Connected to ' + host + ' on port ', port)
#Send some data to server
message = "GET / HTTP/1.1\r\n\r\n"
try :
#Send the whole string(sendall() handles the looping for you)
s.sendall(message.encode('utf8') )
except socket.error:
print('Send failed')
sys.exit()
print('Message sent successfully')
#Now receive data
data = []
while True:
chunk = s.recv(4096) #blocks while waiting for data
if chunk: data.append(chunk.decode("utf8"))
#If the recv() returns a blank string, then the other side
#closed the socket, and no more data will be sent:
else: break
print("".join(data))
--output:--
Socket Created
Socket Connected to localhost on port 8000
Message sent successfully
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.2.3
Date: Sat, 08 Jun 2013 09:15:18 GMT
Content-type: text/html
Content-Length: 23
Last-Modified: Sat, 08 Jun 2013 08:29:01 GMT
<div>hello world</div>
在 python 3 中,你必须使用带有套接字的字节字符串,否则你会得到可怕的:
TypeError: 'str' does not support the buffer interface
在 python 2.x 中:
import socket
import sys
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error:
print 'Failed to create socket'
sys.exit()
print('Socket Created')
#To allow you to immediately reuse the same port after
#killing your server:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
host = 'localhost';
port = 8000;
s.connect((host , port))
print('Socket Connected to ' + host + ' on port ', port)
#Send some data to server
message = "GET / HTTP/1.1\r\n\r\n"
try :
#Send the whole string(handles the looping for you)
s.sendall(message)
except socket.error:
print 'Send failed'
sys.exit()
print 'Message sent successfully'
#Now receive data
data = []
while True:
chunk = s.recv(4096) #blocks while waiting for data
if chunk: data.append(chunk)
#If recv() returns a blank string, then the other side
#closed the socket, and no more data will be sent:
else: break
print("".join(data))
--output:--
Message sent successfully
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/2.7.3
Date: Sat, 08 Jun 2013 10:06:04 GMT
Content-type: text/html
Content-Length: 23
Last-Modified: Sat, 08 Jun 2013 08:29:01 GMT
<div>hello world</div>
请注意,GET 请求的标头告诉服务器 HTTP 1.1 将是协议,即管理对话的规则。正如 HTTP 1.1 的 RFC 所描述的,请求中必须有两个 '\r\n' 序列。所以服务器正在寻找第二个 '\r\n' 序列。如果您从请求中删除了 '\r\n' 序列之一,客户端将挂在 recv() 上,因为服务器仍在等待更多数据,因为服务器尚未读取第二个 '\r\n'顺序。
还请注意,您将以字节形式发送数据(在 python 3 中),因此不会有任何自动“\n”转换,服务器将期待序列“\r\n”。
【讨论】:
您是否可以发送一个控制字节来告诉服务器它已收到整个标头?也许像 0x00 之类的?刚刚看到您的编辑,感谢您提供的链接,我会阅读它。 好的,现在你开始做事了。服务器是否期望 0x00 字节来表示消息的结束?还是服务器希望看到“消息结束”?还是换行?这些制度中的每一个都称为协议,您必须提前就协议达成一致,以便套接字和服务器知道如何相互通信。阅读我发布的链接。 是的,我在你的链接之前已经发布了。不过你说的有道理。我需要做的是让服务器知道我的消息已经结束,以便它可以处理/响应。我现在正在阅读 HOWTO,希望这能让我走上正轨。 下一个阅读:1)HTTP请求的定义:w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5。请注意,Request-Line 的定义在该行的末尾包含一个 CRLF(对于所有 OS 都是 '\r\n' )。此外,一般请求定义需要另一个 CRLF,如果存在,它可能出现在任何标头之后。 2) 连接http服务器的python客户端socket示例:binarytides.com/python-socket-programming-tutorial 我将阅读这些资源中的每一个。它们绝对是我一直在寻找的,并希望了解更多(我参加的网络课程完全没有价值,根本没有谈论这些)。 http 部分将有助于了解客户端的工作方式并对其进行扩展。以上是关于在 Python 中创建 WebSocket 客户端的主要内容,如果未能解决你的问题,请参考以下文章
如何在 C# Asp.net MVC 中创建 websocket 客户端?
如何在 Python 中创建 Socket.io 客户端以与 Sails 服务器通信
如何在 Tornado 中创建多个 websocket 聊天?