長輪詢
- 輪詢(Polling):是指不管服務(wù)器端有沒有更新进陡,客戶端(通常是指瀏覽器)都定時(shí)的發(fā)送請求進(jìn)行查詢滴须,輪詢的結(jié)果可能是服務(wù)器端有新的更新過來,也可能什么也沒有类腮,只是返回個(gè)空的信息做入。不管結(jié)果如何冒晰,客戶端處理完后到下一個(gè)定時(shí)時(shí)間點(diǎn)將繼續(xù)下一輪的輪詢。類似去不斷查詢銀行支付狀態(tài)竟块。
- 推送或叫長連接(Long-Polling):這種方式的服務(wù)其客戶端是不做輪詢的壶运,客戶端在發(fā)起一次請求后立即掛起,一直到服務(wù)器端有更新的時(shí)候浪秘,服務(wù)器才會(huì)主動(dòng)推送信息到客戶端蒋情。 在服務(wù)器端有更新并推送信息過來之前這個(gè)周期內(nèi),客戶端不會(huì)有新的多余的請求發(fā)生耸携,服務(wù)器端對此客戶端也啥都不用干棵癣,只保留最基本的連接信息,一旦服務(wù)器有更新將推送給客戶端夺衍,客戶端將相應(yīng)的做出處理狈谊,處理完后再重新發(fā)起下一輪請求。
優(yōu)點(diǎn):在無消息的情況下不會(huì)頻繁的請求沟沙。
缺點(diǎn):服務(wù)器hold連接開銷大河劝,浪費(fèi)資源,消耗流量矛紫。
實(shí)例:WebQQ赎瞎、Hi網(wǎng)頁版、Facebook IM颊咬。
WebSocket
輪詢與長輪詢都是基于HTTP的务甥,兩者本身存在著缺陷:輪詢需要更快的處理速度;長輪詢則更要求處理并發(fā)的能力;兩者都是“被動(dòng)型服務(wù)器”的體現(xiàn):服務(wù)器不會(huì)主動(dòng)推送信息贪染,而是在客戶端發(fā)送ajax請求后進(jìn)行返回的響應(yīng)缓呛。而理想的模型是"在服務(wù)器端數(shù)據(jù)有了變化后,可以主動(dòng)推送給客戶端",這種"主動(dòng)型"服務(wù)器是解決這類問題的很好的方案杭隙。Web Sockets就是這樣的方案哟绊。
由于長輪詢消耗太多資源,主要原因是客戶端和服務(wù)器并沒有連接在一起痰憎,能夠讓客戶端和服務(wù)器一直保持連接票髓,這就需要用到websocket。
應(yīng)用場景:實(shí)現(xiàn)即時(shí)通訊:如股票交易行情分析铣耘、聊天室洽沟、在線游戲等,替代輪詢和長輪詢蜗细。
優(yōu)點(diǎn)
WebSocket 使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡單裆操,允許服務(wù)端主動(dòng)向客戶端推送數(shù)據(jù)怒详。在 WebSocket API 中,瀏覽器和服務(wù)器只需要完成一次握手踪区,兩者之間就直接可以創(chuàng)建持久性的連接昆烁,并進(jìn)行雙向數(shù)據(jù)傳輸。
介紹:
WebSocket 協(xié)議是基于 TCP 的一種新的 HTML5 網(wǎng)絡(luò)協(xié)議缎岗。它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工(full-duplex)通信——允許服務(wù)器主動(dòng)發(fā)送信息給客戶端静尼。WebSocket通信協(xié)議于2011年被IETF定為標(biāo)準(zhǔn)RFC 6455,并被RFC7936所補(bǔ)充規(guī)范传泊。在WebSocket API中鼠渺,瀏覽器和服務(wù)器只需要做一個(gè)握手的動(dòng)作,然后眷细,瀏覽器和服務(wù)器之間就形成了一條快速通道拦盹。兩者之間就直接可以數(shù)據(jù)互相傳送。
簡單說:客戶端和服務(wù)器一直連接在一起溪椎。
WebSocket客戶端編程
瀏覽器通過 JavaScript 向服務(wù)器發(fā)出建立 WebSocket 連接的請求掌敬,連接建立以后,客戶端和服務(wù)器端就可以通過 TCP 連接直接交換數(shù)據(jù)池磁。當(dāng)你獲取 Web Socket 連接后奔害,你可以通過 send() 方法來向服務(wù)器發(fā)送數(shù)據(jù),并通過 onmessage 事件來接收服務(wù)器返回的數(shù)據(jù)地熄。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title> WebSocket </title>
<style>
*{
margin: 0;
padding: 0;
}
.box{
width: 800px;
margin-left: auto;
margin-right: auto;
margin-top: 25px;
}
#text{
width: 685px;
height: 130px;
border: 1px solid skyblue;
border-radius: 10px;
font-size: 20px;
text-indent: 1em;
resize:none;
outline: none;
}
#text::placeholder{
color: skyblue;
}
.btn{
width: 100px;
margin: -27px 0 0px 8px;
}
#messages{
padding-left: 10px;
font-size: 25px;
}
#messages li{
list-style: none;
color: #000;
line-height: 30px;
font-size: 18px;
}
</style>
</head>
<body>
<div class="box">
<div>
<textarea id="text" placeholder="請輸入您的內(nèi)容"></textarea>
<a href="javascript:WebSocketSend();" class="btn btn-primary">發(fā)送</a>
</div>
<ul id="messages">
</ul>
</div>
<script type="text/javascript">
var mes = document.getElementById('messages');
if('WebSocket' in window){ /*判斷瀏覽器是否支持WebSocket接口*/
/*創(chuàng)建創(chuàng)建 WebSocket 對象华临,協(xié)議本身使用新的ws://URL格式*/
var Socket = new WebSocket("ws://127.0.0.1:8000/websocket")
/*連接建立時(shí)觸發(fā)*/
Socket.onopen = function () {
alert("連接已建立,可以進(jìn)行通信")
};
/*客戶端接收服務(wù)端數(shù)據(jù)時(shí)觸發(fā)*/
Socket.onmessage = function (ev) {
var received_msg = ev.data; /*接受消息*/
var aLi = "<li>" + received_msg + "</li>";
mes.innerHTML += aLi;
/*jq方式*/
// $(mes).append($(aLi));
};
/*連接關(guān)閉時(shí)觸發(fā)*/
Socket.onclose = function () {
mes.innerHTML += "<br>連接已經(jīng)關(guān)閉...";
};
}else{
/*瀏覽器不支持 WebSocket*/
alert("您的瀏覽器不支持 WebSocket!");
}
function WebSocketSend() {
/*form 里的Dom元素(input select checkbox textarea radio)都是value*/
var send_msg = document.getElementById('text').value;
//或者JQ中獲取
// var send_msg = $("#text").val();
/*使用連接發(fā)送消息*/
Socket.send(send_msg);
}
</script>
</body>
</html>
WebSocket服務(wù)端編程
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
from data.user_module import User
from tornado.options import define,options
from tornado.web import authenticated #導(dǎo)入裝飾器
from pycket.session import SessionMixin
import tornado.websocket
import datetime
define('port', default=8080, help='run port', type=int)
#設(shè)置BaseHandler類端考,重寫函數(shù)get_current_user
class BaseHandler(tornado.web.RequestHandler, SessionMixin):
def get_current_user(self): #前面有綠色小圓圈帶個(gè)o雅潭,再加一個(gè)箭頭表示重寫
print('********')
current_user = self.session.get('user') #獲取加密的cookie
print(current_user)
if current_user:
return current_user
return None
#基類
class BaseWebSocketHandler(tornado.websocket.WebSocketHandler,SessionMixin):
def get_current_user(self):
current_user = self.session.get('user')
if current_user:
return current_user
return None
#跳轉(zhuǎn)
class IndexHandler(BaseHandler):
@authenticated
def get(self):
self.render('websocket.html')
#在LoginHandler類中的post函數(shù)里面加上設(shè)置cookie
class LoginHandler(BaseHandler):
def get(self):
nextname = self.get_argument('next','') #將原來的路由賦值給nextname
print(nextname)
self.render('login.html',nextname=nextname) #跳轉(zhuǎn)頁面帶上獲取的參數(shù)
def post(self,*args,**kwargs):
user = self.get_argument('name','')
username = User.by_name(user)
passwd = self.get_argument('password','')
nextname = self.get_argument('next','') #獲取之前頁面的路由
if username and passwd == username[0].password:
self.session.set('user', user) # 設(shè)置加密cookie
self.redirect(nextname) #跳轉(zhuǎn)到之前的路由
else:
self.render('login.html',nextname=nextname)
'''Tornado 定義了 tornado.websocket.WebSocketHandler 類用于處理 WebSocket 鏈接的請求,
應(yīng)用開發(fā)者應(yīng)該繼承該類并實(shí)現(xiàn)其中的open()却特、on_message()扶供、on_close() 函數(shù)
'''
class MessageHandler(BaseWebSocketHandler):
users = set() #避免重復(fù)登錄,利用集合的去重性
def open(self): #當(dāng)一個(gè)新的WebSocket連接建立后被調(diào)用裂明。
MessageHandler.users.add(self) #類屬性椿浓,用類名直接調(diào)用,
print('----------open------')
def on_message(self, message): #當(dāng)客戶端發(fā)送消息message過來時(shí)被調(diào)用闽晦,注意此方法必須被重寫扳碍。
print(self.request.remote_ip)
for u in self.users: #write_message,向客戶端發(fā)送消息messagea,message可以是字符串或字典(字典會(huì)被轉(zhuǎn)為json字符串
u.write_message('%s-%s-說:%s'%(self.current_user,
datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
message
))
def on_close(self): #當(dāng)WebSocket連接關(guān)閉后被調(diào)用仙蛉。
if self in MessageHandler.users:
MessageHandler.users.remove(self) #斷開的時(shí)候移除
print(self)
print('-----------close--------')
application = tornado.web.Application(
handlers = [
(r'/login',LoginHandler),
(r'/websocket',MessageHandler),
(r'/',IndexHandler),
],
# cookie_secret='1q2w3e4r',
#設(shè)置跳轉(zhuǎn)路由笋敞,為了防止在沒有登錄情況下,直接輸入需要登錄才可見的url進(jìn)行訪問荠瘪,做判斷夯巷,如果沒有登錄則跳轉(zhuǎn)到這個(gè)路由下
login_url='/login',
pycket={
'engine': 'redis',
'storage': {
'host': 'localhost',
'port': 6379,
'db_sessions': 5,
'db_notifications': 11,
'max_connections': 2 ** 31,
},
'cookies': {
'expires_days': 30,
'max_age': 100
},
},
static_path='static',
template_path = 'templates', #想要Tornado能夠正確的找到html文件赛惩,需要在 Application 中指定文件的位置
cookie_secret='sgsdf',
debug = True #調(diào)試模式,修改后自動(dòng)重啟服務(wù)趁餐,不需要自動(dòng)重啟坊秸,生產(chǎn)情況下切勿開啟,安全性
)
if __name__ == '__main__':
tornado.options.parse_command_line()
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
運(yùn)行效果:
Tornado 定義了 tornado.websocket.WebSocketHandler 類用于處理 WebSocket 鏈接的請求澎怒,應(yīng)用開發(fā)者應(yīng)該繼承該類并實(shí)現(xiàn)其中的open()、on_message()阶牍、on_close() 函數(shù)喷面,除了這3個(gè) Tornado 框架自動(dòng)調(diào)用的入口函數(shù),WebSocketHandler 還提供了兩個(gè)開發(fā)者主動(dòng)操作 WebSocket的函數(shù):
WebSocketHandler.write_message(message)函數(shù):用于向與本鏈接相對應(yīng)的客戶端寫消息走孽。
WebSocketHandler.close(code=None,reason=None)函數(shù):主動(dòng)關(guān)閉 WebSocket鏈接惧辈。其中的code和reason用于告訴客戶端鏈接被關(guān)閉的原因。參數(shù)code必須是一個(gè)數(shù)值磕瓷,而reason是一個(gè)字符串盒齿。