6

我正在尝试学习套接字编程以及 WebSocket 协议。我知道存在 python web socket 客户端,但我希望构建一个玩具版本供我自己学习。为此,我创建了一个非常简单的 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 如何工作或如何发送初始握手方面存在一些误解。

4

1 回答 1

12

1)当您通过套接字发送消息时,您不知道它将被分成多少块。它可能会立即发送;或者可以发送前 3 个字母,然后发送其余的消息;或者消息可能被分成 10 段。

2)给定1)服务器应该如何知道它何时收到了客户端发送的所有块?例如,假设服务器接收到客户端消息的 1 块。服务器如何知道这是整个消息还是还有 9 个块即将到来?

3)我建议你读这个:

http://docs.python.org/2/howto/sockets.html

(加上评论中的链接)

4) 现在,你为什么不使用 python 创建 HTTP 服务器?

蟒蛇3:

import http.server
import socketserver

PORT = 8000
handler = http.server.SimpleHTTPRequestHandler

httpd = socketserver.TCPServer(("", PORT), handler)

print("serving at port", PORT)
httpd.serve_forever()

蟒蛇2:

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'。

于 2013-06-08T06:38:11.607 回答