websocket
? 網(wǎng)易聊天室?
? web微信?
? 直播?
假如你工作以后,你的老板讓你來開發(fā)一個(gè)內(nèi)部的微信程序,你需要怎么辦?我們先來分析一下里面的技術(shù)難點(diǎn)
- 消息的實(shí)時(shí)性?
- 實(shí)現(xiàn)群聊
現(xiàn)在有這樣一個(gè)需求,老板給到你了,關(guān)乎你是否能轉(zhuǎn)正?你要怎么做?
我們先說消息的實(shí)時(shí)性,按照我們目前的想法是我需要用http協(xié)議來做,那么http協(xié)議怎么來做那?
是不是要一直去訪問我們的服務(wù)器,問服務(wù)器有沒有人給我發(fā)消息,有沒有人給我發(fā)消息?那么大家認(rèn)為我多長(zhǎng)時(shí)間去訪問一次服務(wù)比較合適那? 1分鐘1次?1分鐘60次?那這樣是不是有點(diǎn)問題那?咱們都知道http發(fā)起一次請(qǐng)求就需要三次握手,四次斷開,那么這樣是不是對(duì)我服務(wù)器資源是嚴(yán)重的浪費(fèi)啊?對(duì)我本地的資源是不是也是嚴(yán)重的浪費(fèi)啊?這種方式咱們是不是一直去服務(wù)器問啊?問有沒有我的信息?有我就顯示?這種方式咱們一般稱為輪詢
http協(xié)議:
? 一次請(qǐng)求 一次相應(yīng) 斷開
? 無狀態(tài)的 - 你曾經(jīng)來過 session or cookie
? 在斷開的情況下如果有數(shù)據(jù)只能等下次再訪問的時(shí)候返回
那么我們先來總結(jié)一下,輪詢優(yōu)缺點(diǎn)
輪詢 02年之前使用的都是這種技術(shù)
? 每分鐘訪問60次服務(wù)器
? 優(yōu)點(diǎn):消息就基本實(shí)時(shí)
缺點(diǎn):雙資源浪費(fèi)
長(zhǎng)輪詢 2000-現(xiàn)在一直在使用
客戶端發(fā)送一個(gè)請(qǐng)求- 服務(wù)器接受請(qǐng)求-不返回- 阻塞等待客戶端-如果有消息了-返回給客戶端
然后客戶端立即請(qǐng)求服務(wù)器
? 優(yōu)點(diǎn):節(jié)省了部分資源,數(shù)據(jù)實(shí)時(shí)性略差
? 缺點(diǎn):斷開連接次數(shù)過多
那有沒有一種方法是:我的服務(wù)器知道我的客戶端在哪?有客戶端的消息的時(shí)候我就把數(shù)據(jù)發(fā)給客戶端
websocket是一種基于tcp的新網(wǎng)絡(luò)協(xié)議,它實(shí)現(xiàn)了瀏覽器和服務(wù)器之間的雙全工通信,允許服務(wù)端直接向客戶端發(fā)送數(shù)據(jù)
websocket 是一個(gè)長(zhǎng)連接
現(xiàn)在咱們的前端已經(jīng)支持websocket協(xié)議了,可以直接使用websocket
簡(jiǎn)單應(yīng)用
<input type="text" id="input"> <button> 提交數(shù)據(jù)</button> var input=document.getElementById('input'); var button=document.querySelector('button'); var message=document.querySelector('div'); //現(xiàn)在html已經(jīng)提供了websocket,我們可以直接使用 var socket= new WebSocket('ws://echo.websocket.org'); socket.onopen=function () { message.innerHTML='連接成功了' //socket.addEventListener('open',function (data) { // message.innerHTML='連接成功了' button.onclick=function () { socket.onmessage=function (data) { message.innerHTML=data.data socket.onclose=function (data) { message.innerHTML=data.data
優(yōu)化前端代碼
button.onclick=function () { socket.onmessage = function (data) { var dv=document.createElement('div');
websocket 事件
事件 |
事件處理函數(shù) |
描述 |
---|
open |
socket.onopen |
連接建立是觸發(fā) |
message |
socket.onmessage |
客戶端收到服務(wù)端數(shù)據(jù)是觸發(fā) |
error |
socket.error |
通信發(fā)生錯(cuò)誤時(shí)觸發(fā) |
close |
socket.close |
連接關(guān)閉時(shí)觸發(fā) |
websocket方法
方法 |
描述 |
---|
socket.send() |
使用連接發(fā)送數(shù)據(jù) |
socket.close() |
關(guān)閉連接 |
websocke treadyState值的狀態(tài)
值 |
描述 |
---|
0 (CONNECTING ) |
正在鏈接中 |
1 (OPEN ) |
已經(jīng)鏈接并且可以通訊 |
2 (CLOSING ) |
連接正在關(guān)閉 |
3 (CLOSED ) |
連接已關(guān)閉或者沒有鏈接成功 |
自建websocket服務(wù)端
準(zhǔn)備前端頁面
<!-- chat/templates/chat/index.html --> <title>Chat Rooms</title> What chat room would you like to enter?<br/> <input id="room-name-input" type="text" size="100"/><br/> <input id="room-name-submit" type="button" value="Enter"/> document.querySelector('#room-name-input').focus(); document.querySelector('#room-name-input').onkeyup = function(e) { if (e.keyCode === 13) { // enter, return document.querySelector('#room-name-submit').click(); document.querySelector('#room-name-submit').onclick = function(e) { var roomName = document.querySelector('#room-name-input').value; window.location.pathname = '/web/' + roomName + '/';
編輯django的views,使其返回?cái)?shù)據(jù)
from django.shortcuts import render return render(request, 'chat/index.html', {})
修改url
from django.conf.urls import url url(r'^$', index, name='index'),
跟settings同級(jí)目錄下創(chuàng)建routing.py 文件
from channels.routing import ProtocolTypeRouter application = ProtocolTypeRouter({ # (http->django views is added by default)
編輯settings文件,將channels添加到installed_apps里面
'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles',
并添加channel的配置信息
ASGI_APPLICATION = 'mysite.routing.application'
準(zhǔn)備聊天室的頁面
<!-- chat/templates/chat/room.html --> <textarea id="chat-log" cols="100" rows="20"></textarea><br/> <input id="chat-message-input" type="text" size="100"/><br/> <input id="chat-message-submit" type="button" value="Send"/> var roomName = {{ room_name_json|safe }}; var chatSocket = new WebSocket( 'ws://' + window.location.host + '/ws/chat/' + roomName + '/'); chatSocket.onmessage = function(e) { var data = JSON.parse(e.data); var message = data['message']; document.querySelector('#chat-log').value += (message + '\n'); chatSocket.onclose = function(e) { console.error('Chat socket closed unexpectedly'); document.querySelector('#chat-message-input').focus(); document.querySelector('#chat-message-input').onkeyup = function(e) { if (e.keyCode === 13) { // enter, return document.querySelector('#chat-message-submit').click(); document.querySelector('#chat-message-submit').onclick = function(e) { var messageInputDom = document.querySelector('#chat-message-input'); var message = messageInputDom.value; chatSocket.send(JSON.stringify({ messageInputDom.value = '';
準(zhǔn)備views文件,使其返回頁面
def room(request, room_name): return render(request, 'chat/room.html', { 'room_name_json':json.dumps(room_name)
修改url
from django.conf.urls import url url(r'^$', views.index, name='index'), url(r'^(?P<room_name>[^/]+)/$', views.room, name='room'),
實(shí)現(xiàn)簡(jiǎn)單的發(fā)送返回
from channels.generic.websocket import WebsocketConsumer class ChatConsumer(WebsocketConsumer): def disconnect(self, close_code): def receive(self, text_data): text_data_json = json.loads(text_data) message = text_data_json['message'] self.send(text_data=json.dumps({
創(chuàng)建ws的路由
from django.conf.urls import url websocket_urlpatterns = [ url(r'^ws/chat/(?P<room_name>[^/]+)/$', consumers.ChatConsumer),
修改application的信息
from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter application = ProtocolTypeRouter({ # (http->django views is added by default) 'websocket': AuthMiddlewareStack( chat.routing.websocket_urlpatterns
執(zhí)行數(shù)據(jù)庫的遷移命令
python manage.py migrate
要實(shí)現(xiàn)群聊功能,還需要準(zhǔn)備redis
docker run -p 6379:6379 -d redis:2.8 pip3 install channels_redis
將redis添加到settings的配置文件中
ASGI_APPLICATION = 'mysite.routing.application' 'BACKEND': 'channels_redis.core.RedisChannelLayer', "hosts": [('127.0.0.1', 6379)],
修改consumer.py文件
from asgiref.sync import async_to_sync from channels.generic.websocket import WebsocketConsumer class ChatConsumer(WebsocketConsumer): self.room_name = self.scope['url_route']['kwargs']['room_name'] self.room_group_name = 'chat_%s' % self.room_name async_to_sync(self.channel_layer.group_add)( def disconnect(self, close_code): async_to_sync(self.channel_layer.group_discard)( # Receive message from WebSocket def receive(self, text_data): text_data_json = json.loads(text_data) message = text_data_json['message'] # Send message to room group async_to_sync(self.channel_layer.group_send)( # Receive message from room group def chat_message(self, event): message = event['message'] # Send message to WebSocket self.send(text_data=json.dumps({
歡迎關(guān)注公眾號(hào)獲取源碼:南城故夢(mèng)
一個(gè)程序媛的后花園
|