WebSocket协议
WebSocket是基于HTML5规范的网络协议,它实现了浏览器与服务器之间全双工full-duplex
通信,即允许服务器主动发送消息给客户端。
WebSocket通信协议于2011年被IETF定为标准RFC 6455,并被RFC 7936所补充规范。在WebSocket的API中,浏览器和服务器只需要做一个握手动作。然后,浏览器和服务器之间就形成了一条快速通道,两者之间就直接可以用传输数据了。
WebSocket是HTML5规范中新提出的客户端与服务器之间的通信协议,协议本身使用新的ws://URL
的格式。
WebSocket是独立的创建在TCP之上的协议,和HTTP唯一的关系是使用HTTP的101状态码进行协议转换,默认使用TCP端口是80,因此可以绕过大多数防火墙的限制。
WebSocket使客户端和服务器之间的数据交换变得更加简单,它允许服务器直接向客户端推送数据而无需客户端进行请求,两者之间可以创建持久化的连接,并允许数据进行双向传递。
WebSocket是一种网络通信协议,与HTTP协议不同的是,WebSocket连接允许客户端和服务器之间进行全双工通信,以便任意一方都可以通过建立的连接将数据推送到另一端。WebSocket仅需要建立一次连接就可以一直保持连接状态。
WebSocket客户端编程
浏览器通过JavaScript向服务器发出建立WebSocket连接的请求,连接建立以后,客户端和服务器就可以通过TCP连接直接交换数据。当你获取WebSocket连接后,可以通过send()
方法向服务器发送数据,并通过onmessage
事件来接收服务器返回的数据。
$ mkdir static
$ mkdir template
$ vim server.py
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
from tornado.options import define, options
from tornado.web import Application, RequestHandler
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.websocket import WebSocketHandler
import os, datetime
define("port", type=int, default=8000)
class IndexHandler(RequestHandler):
def get(self):
self.render("index.html")
class MessageHandler(WebSocketHandler):
connections = set()
def open(self):
print("message open")
self.connections.add(self)
def on_message(self, message):
print("message send: %s" % message)
ip = self.request.remote_ip
dt = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%s")
print(ip)
for conn in self.connections:
conn.write_message("[%s] %s : %s " % (dt, ip, message))
def on_close(self):
print("message close")
if conn in self.connections:
self.connections.remove(conn)
class App(Application):
def __init__(self):
handlers = [
(r"/index", IndexHandler),
(r"/message", MessageHandler)
]
settings = dict(
debug = True,
cookie_secret = "m2ho0d9",
static_path = os.path.join(os.path.dirname(__file__), "static"),
template_path = os.path.join(os.path.dirname(__file__), "template")
)
Application.__init__(self, handlers, **settings)
def main():
options.parse_command_line()
app = App()
server = HTTPServer(app)
server.listen(options.port)
IOLoop.current().start()
if __name__ == "__main__":
main()
$ vim template/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>websocket</title>
<link href="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
<div class="container">
<div class="form-group">
<label>消息</label>
<textarea class="form-control" rows="3" id="message"></textarea>
</div>
<button type="button" class="btn btn-primary" id="send">发送</button>
<div id="result"></div>
</div>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>""
<script src="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/js/bootstrap.min.js"></script>
<script>
if("WebSocket" in window){
var ws = new WebSocket("ws://192.168.56.103:8000/message");
ws.onopen = function(){
console.log("websocket open");
};
ws.onmessage = function(evt){
var data = evt.data;
console.log(data);
};
ws.onclose = function(){
console.log("websocket close");
};
$("#send").on("click", function(){
var message = $("#message").val();
console.log(message);
if(message !== ""){
ws.send(message);
}
});
}else{
console.log("browser not support WebSocket");
}
</script>
</body>
</html>
CentOS防火墙开启指定端口供外部访问
# 查看状态
$ firewall-cmd --state
$ firewall-cmd --get-services
$ firewall-cmd --reload
$ firewall-cmd --version
$ firewall-cmd --help
# 防火墙添加端口
$ firewall-cmd --zone=public --add-port=8000/tcp --permanent
# 重新载入
$ firewall-cmd --reload
# 查看
$ firewall-cmd --zone=public --query-port=8000/tcp
$ firewall-cmd --list-services
$ firewall-cmd --zone=public --list-ports
# 删除
$ firewall-cmd --zone=public --remove-port=8000/tcp --permanent
Tornado的WebSocket模块
Tornado提供支持WebSocket的模块是tornado.websocket
,并提供了WebSocketHandler
类用于处理通信。
WebSocketHandler
类提供的方法包括
# 当一个WebSocket连接建立后被调用
WebSocketHandler.open()
# 当客户端发送消息过来时被调用,此方法必须被重写。
WebSocketHandler.on_message(message)
# 当WebSocket连接关闭后调用
WebSocketHandler.on_close()
# 向客户端发送消息,消息可以是字符串或字典(字典会被转换为JSON字符串),若binary为False则消息会以utf8编码发送,否则以二进制格式发送。
WebSocketHandler.write_message(message, binary=False)
# 关闭WebSocket连接
WebSocketHandler.close()
# 判断请求源,对于符合条件的请求源允许其连接,否则返回403。可重写此方法来解决WebSocket的跨域请求。
WebSocketHandler.check_origin(origin)
例如:使用浏览器中WebSocket协议访问服务器
$ mkdir static
$ mkdir template
$ vim server.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import os
from tornado.options import define, options
from tornado.web import Application, RequestHandler
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.websocket import WebSocketHandler
define("port", type=int, default=8000, help="defualt port 8000")
class IndexHandler(RequestHandler):
def get(self):
self.render("index.html")
class WSHandler(WebSocketHandler):
connections = set()
def open(self):
self.connections.add(self)
for conn in self.connections:
conn.write_message(self.request.remote_ip)
def on_message(self, message):
for conn in self.connections:
conn.write_message(message)
print(message)
def on_close(self):
self.connections.remove(self)
def check_origin(self, origin):
return True
@classmethod
def send_demand_updates(cls, message):
for conn in cls.connections:
conn.write_message(message)
class App(Application):
def __init__(self):
handlers = [
(r"/index", IndexHandler),
(r"/websocket", WSHandler)
]
staticPath = os.path.join(os.path.dirname(__file__), "static")
templatePath = os.path.join(os.path.dirname(__file__), "template")
settings = dict(
debug=True,
static_path = staticPath,
template_path = templatePath
)
Application.__init__(self, handlers, **settings)
def main():
options.parse_command_line()
app = App()
server = HTTPServer(app)
server.listen(options.port)
IOLoop.current().start()
if __name__ == "__main__":
main()
$ vim template/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>websocket</title>
</head>
<body>
<script>
var ws = new WebSocket("ws://127.0.0.1:8000/websocket");
ws.onopen = function(){
ws.send("hello world");
}
ws.onmessage = function(evt){
console.log(evt.data);
}
</script>
</body>
</html>
$ python server.py
运行测试,在浏览器中输入http://127.0.0.1/index
查看控制台输出内容,查看服务器命令行输出内容。