先放一個小 demo~
用 html5 的 websocket 實現(xiàn)的聊天平臺。后端用的是 python bottle 框架。
后期要改成監(jiān)控,可能要聯(lián)合 saltstack 做實時的監(jiān)控。
像上篇博客說的那樣,實時監(jiān)控就那點東西,就是接收數(shù)據(jù)、顯示數(shù)據(jù) 。
像下面這樣:
原文地址:http://rfyiamcool.blog.51cto.com/1030776/1269232
http://wiki.jikexueyuan.com/project/python-actual-combat/images/8.png" alt="pic" />
WebSocket API 是下一代客戶端-服務器的異步通信方法。該通信取代了單個的 TCP 套接字,使用 ws 或 wss 協(xié)議,可用于任意的客戶端和服務器程序。
WebSocket 目前由 W3C 進行標準化。WebSocket 已經(jīng)受到 Firefox 4、Chrome 、Opera 10.70 以及 Safari 5 等瀏覽器的支持。
WebSocket API最偉大之處在于服務器和客戶端可以在給定的時間范圍內(nèi)的任意時刻,相互推送信息。WebSocket 并不限于以 Ajax(或 XHR)方式通信,因為 Ajax 技術需要客戶端發(fā)起請求,而 WebSocket 服務器和客戶端可以彼此相互推送信息;XHR 受到域的限制,而 WebSocket 允許跨域通信。
WebSocket 的優(yōu)點
a)、服務器與客戶端之間交換的標頭信息很小,大概只有 2 字節(jié);
b)、客戶端與服務器都可以主動傳送數(shù)據(jù)給對方;
c)、不用頻率創(chuàng)建 TCP 請求及銷毀請求,減少網(wǎng)絡帶寬資源的占用,同時也節(jié)省服務器資源;
建立連接的握手
當 Web 應用程序調(diào)用 new WebSocket(url)接口時,Browser 就開始了與地址為 url 的 WebServer 建立握手連接的過程。
1. Browser 與 WebSocket 服務器通過 TCP 三次握手建立連接,如果這個建立連接失敗,那么后面的過程就不會執(zhí)行,Web應用程序?qū)⑹盏藉e誤消息通知。
2. 在 TCP 建立連接成功后,Browser/UA 通過 http 協(xié)議傳送 WebSocket 支持的版本號,協(xié)議的字版本號,原始地址,主機地址等等一些列字段給服務器端。
3. WebSocket 服務器收到 Browser/UA 發(fā)送來的握手請求后,如果數(shù)據(jù)包數(shù)據(jù)和格式正確,客戶端和服務器端的協(xié)議版本號匹配等等,就接受本次握手連接,并給出相應的數(shù)據(jù)回復,同樣回復的數(shù)據(jù)包也是采用 http 協(xié)議傳輸。
4. Browser 收到服務器回復的數(shù)據(jù)包后,如果數(shù)據(jù)包內(nèi)容、格式都沒有問題的話,就表示本次連接成功,觸發(fā) onopen 消息,此時 Web 開發(fā)者就可以在此時通過 send 接口想服務器發(fā)送數(shù)據(jù)。否則,握手連接失敗,Web 應用程序會收到 onerror 消息,并且能知道連接失敗的原因。
這個握手很像 HTTP,但是實際上卻不是,它允許服務器以 HTTP 的方式解釋一部分 handshake 的請求,然后切換為 websocket
數(shù)據(jù)傳輸
WebScoket 協(xié)議中,數(shù)據(jù)以幀序列的形式傳輸。
考慮到數(shù)據(jù)安全性,客戶端向服務器傳輸?shù)臄?shù)據(jù)幀必須進行掩碼處理。服務器若接收到未經(jīng)過掩碼處理的數(shù)據(jù)幀,則必須主動關閉連接。
服務器向客戶端傳輸?shù)臄?shù)據(jù)幀一定不能進行掩碼處理。客戶端若接收到經(jīng)過掩碼處理的數(shù)據(jù)幀,則必須主動關閉連接。
針對上情況,發(fā)現(xiàn)錯誤的一方可向?qū)Ψ桨l(fā)送 close 幀(狀態(tài)碼是 1002,表示協(xié)議錯誤),以關閉連接。
http://wiki.jikexueyuan.com/project/python-actual-combat/images/9.jpg" alt="pic" />
ws的連接狀態(tài):
GET /chat HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Host: 66.xiaorui.cc:10000
Origin: http://66.xiaorui.cc
Cookie: somenterCookie
readyState 表示連接有四種狀態(tài):
url 是代表 WebSocket 服務器的網(wǎng)絡地址,協(xié)議通常是”ws”或“wss(加密通信)”,send 方法就是發(fā)送數(shù)據(jù)到服務器端;
close 方法就是關閉連接;
onopen 連接建立,即握手成功觸發(fā)的事件;
onmessage 收到服務器消息時觸發(fā)的事件;
onerror 異常觸發(fā)的事件;
onclose 關閉連接觸發(fā)的事件;
來個例子,咱們用 js 來搞搞
var wsServer = 'ws://localhost:8888/Demo'; //服務器地址
var websocket = new WebSocket(wsServer); //創(chuàng)建 WebSocket 對象
websocket.send("hello");//向服務器發(fā)送消息
alert(websocket.readyState);//查看 websocket 當前狀態(tài)
websocket.onopen = function (evt) {
//已經(jīng)建立連接
};
websocket.onclose = function (evt) {
//已經(jīng)關閉連接
};
websocket.onmessage = function (evt) {
//收到服務器消息,使用 evt.data 提取
};
websocket.onerror = function (evt) {
//產(chǎn)生異常
};
我的后端代碼:
python 的后端實現(xiàn) websocket 的處理,有很多方法的。
比較常見的是 gevent 的 websocket 的方式。
from bottle import get, run, template
from bottle.ext.websocket import GeventWebSocketServer
from bottle.ext.websocket import websocket
import gevent
users = set()
@get('/')
def index():
return template('index')
@get('/websocket', apply=[websocket])
def chat(ws):
users.add(ws)
while True:
msg = ws.receive()
if msg is not None:
for u in users:
print type(u)
u.send(msg)
print u,msg
else: break
users.remove(ws)
run(host='10.10.10.66', port=10000, server=GeventWebSocketServer)
后端的東西比較的簡單,就是把接收到的數(shù)據(jù),原路打回去。。。
我前端的代碼
這個是連接 webscoket,然后接收和發(fā)數(shù)據(jù)的 js
<script>
$(document).ready(function() {
if (!window.WebSocket) {
if (window.MozWebSocket) {
window.WebSocket = window.MozWebSocket;
} else {
$('#messages').append("<li>Your browser doesn't support WebSockets.</li>");
}
}
ws = new WebSocket('ws://10.10.10.66:10000/websocket');
ws.onopen = function(evt) {
$('#messages').append('<li>Connected to chat.</li>');
}
ws.onmessage = function(evt) {
$('#messages').append('<li>' + evt.data + '</li>');
}
$('#send-message').submit(function() {
ws.send($('#name').val() + ": " + $('#message').val());
$('#message').val('').focus();
return false;
});
});
</script>
用來呈現(xiàn)結(jié)果的 div
form id="send-message" class="form-inline">
<input id="name" type="text" value="可以更換名字">
<input id="message" type="text" value="要扯淡的內(nèi)容" />
<button class="btn btn-success" type="submit">Send</button>
</form>
<div id="messages"></div>
這里有個 tornado 后端的代碼,實現(xiàn)的過程和我差不多的~我需要的朋友可以跑一下~
import logging
import os.path
import uuid
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.websocket
def send_message(message):
for handler in ChatSocketHandler.socket_handlers:
try:
handler.write_message(message)
except:
logging.error('Error sending message', exc_info=True)
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render('index.html')
class ChatSocketHandler(tornado.websocket.WebSocketHandler):
socket_handlers = set()
def open(self):
ChatSocketHandler.socket_handlers.add(self)
send_message('A new user has entered the chat room.')
def on_close(self):
ChatSocketHandler.socket_handlers.remove(self)
send_message('A user has left the chat room.')
def on_message(self, message):
send_message(message)
def main():
settings = {
'template_path': os.path.join(os.path.dirname(__file__), 'templates'),
'static_path': os.path.join(os.path.dirname(__file__), 'static')
}
application = tornado.web.Application([
('/', MainHandler),
('/new-msg/', ChatHandler),
('/new-msg/socket', ChatSocketHandler)
], **settings)
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(8000)
tornado.ioloop.IOLoop.instance().start()
if __name__ == '__main__':
main()
我和沈燦的對話~
http://wiki.jikexueyuan.com/project/python-actual-combat/images/10.jpg" alt="pic" />
沈燦和我的對話
http://wiki.jikexueyuan.com/project/python-actual-combat/images/11.jpg" alt="pic" />
本文出自 “峰云,就她了?!?博客,謝絕轉(zhuǎn)載!