WebSocket是什么

WebSocket是什么

WebSocket是什么_第1张图片

WebSocket介绍

问: websocket是什么?

websocket是在HTML5开始提供的一种在单个TCP上进行全双工通讯的协议

问:为什么要引入WebSocket协议呢?

因为HTTP协议是一种无状态、短连接、单向的应用层协议,采用的是请求/响应模式,通信请求只能由客户端主动发起,服务端只能被动的响应,弊端: HTTP协议无法实现服务器主动向客户端发起消息

websocket协议可以创建持久的连接不断开,基于这个连接可以进行收发数据

【适用于服务端向客户端主动推送消息的场景】

  • web聊天室
  • 监控平台的实时图表更新展示

问: HTTP协议能实现服务端和客户端的双向通信吗?

可以, 大多数Web应用程序通过将频繁的异步AJAX请求实现长轮询(在浏览器端定时的给服务器发请求,拿到服务器的最新的数据展示出来,因为是异步,所以用户感知不到),但轮询的效率低,浪费资源(因为每次都需要重新连接,或者保持HTTP连接始终打开)

WebSocket原理

  • http协议

    • 连接
    • 数据传输
    • 断开连接
  • websocket协议(建立在http协议之上)

    • 由客户端发起连接
    # 下面为HTTP请求头中的信息
    GET /chatsocket HTTP/1.1
    Host: localhost
    Connection: Upgrade		# 标识该HTTP请求时一个协议升级请求 
    Upgrade: websocket		# 协议升级为WebSocket协议
    Origin: http://localhost:63342
    Sec-WebSocket-Version: 13	# 客户端支持WebSocket的版本
    Sec-WebSocket-Key: mnwFxiOlctXFN/DeMt1Amg==		# (重要)随机字符串
    Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
    ....
    \r\n
    \r\n
    
    • 握手环节:重要(目的是为了验证服务端是否支持WebSocket协议)

    服务端接收后客户端发过来的连接请求后

    从请求【握手】信息中提取 Sec-WebSocket-Key

    利用magic_stringSec-WebSocket-Key 进行hmac1加密,再进行base64加密得到密文

    将加密结果响应给客户端

    注:magic string为:258EAFA5-E914-47DA-95CA-C5AB0DC85B11

    服务端返回响应数据

    HTTP/1.1 101 Switching Protocols
    Upgrade:websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: base64(hmac1(Sec-WebSocket-Key + magic string))	// 服务端加密后返回给客户端
    
    • 收发数据

    • 断开连接

    上述流程总结

    在浏览器中生成一个随机字符串,进行加密得到密文

    再将上一步生成的随机字符串发送给服务器,服务器拿到随机字符串后也可进行加密

    服务端将生成的密文返回给客户端,客户端将服务端发过来的密文与自己生成的密文进行比较,经过比 较二者值相同就说明服务端支持websocket协议(前提:服务端和客户端加密算法一样)

基于Django channels实现WebSocket通信

一、Django中配置Channels

创建应用

python manage.py startapp app01

django默认不支持websocket,需要安装组件,这里博主安装的Djangochannels的版本都是4.x开头的

pip install channels  # 默认是当前最新的版本
# django2.x 需要匹配安装 channels 2
# django3.x 需要匹配安装 channels 3
# 版本搞错,django 就不能正常启动  ASGI_APPLICATION

注意:

WSGI: (Web Server Gateway Interface)服务器网关接口,一种描述web server如何与web application通信的规范,用于接收用户请求并将请求进行初次封装然后将请求交给web框架(wsgiref:本质就是编写一个socket服务端用于接收用户请求)

ASGI: ASGI是WSGI的扩展异步Python标准,比只能同步的WSGI性能更好,且可支持websocket,在 Django3+ 和 Flask2+ 中得到支持

服务端(Django后台)进行相关配置:
  1. 注册channels
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'channels'					# 注册应用
    'app01.apps.App01Config',	# 注册你自己的app应用
    
]
  1. settings.py中添加asgi_application
# asgi.py文件在我的djangoProjects目录下,这里你需要根据自己的配置来
ASGI_APPLICATION = "djangoProjects.asgi.application" 
  1. settings.py的同级目录下创建 routings.py(给websocket协议提供路由匹配) 这就等价于urls.py(给http协议提供路由匹配)
from django.urls import re_path

from app01 import consumers		# 从你的app应用中导入

websocket_urlpatterns = [
    re_path(r'room/(?P\w+)/$', consumers.ChatConsumer.as_asgi()),
]
  1. 修改asgi.py文件
import os

from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from djangoProject import routings

# 和你自己的项目名称保持一致
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoProject.settings')

# 支持 http 和 websocket
application = ProtocolTypeRouter(
    {
        "http": get_asgi_application(),    # 对http请求:自动找urls.py,根据路由找视图函数views.py
        
        # 对websocket请求: 自动找routings.py(urls.py)、根据路由找视图:consumers.py(views.py)
        "websocket": URLRouter(routings.websocket_urlpatterns),  
    }
)
  1. app01目录下创建cosummer.py,编写处理websocket请求的业务逻辑
from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer


class ChatConsumer(WebsocketConsumer):
    # 有客户端向后端发送websocket连接的请求时自动触发
    def websocket_connect(self, message):
        print("客户端发来连接请求")
        # 服务端允许和客户端创建连接(即上文所介绍的握手,无需关心上文中所介绍的握手细节,这里已经做好封装)
        self.accept()

    # 浏览器基于websocket向后端发送数据,自动触发接收消息
    def websocket_receive(self, message):
        text = message['text']      # 根据text字段提取客户端发过来的消息的文本内容
        print(f"客户端发来了{text} 消息")
        if not text:
            self.send("你发个空消息干嘛")
        else:
            self.send(f"{text} SB")

    # 客户端主动与服务端断开连接时自动触发
    def websocket_disconnect(self, message):
        print("客户端发起断开连接请求")
        raise StopConsumer()

接下来开始实现客户端的配置

二、客户端(浏览器)实现

websocket配置介绍
websocket对象

下面的API用户创建一个websocket对象

var ws_socket = new WebSocket(url)
websocket事件
事件 事件处理程序 描述
open websocket对象.onopen 连接建立时触发
message websocket对象.onmessage 客户端接收服务端数据时触发
error websocket对象.onerror 通信发生错误时触发
close websocket对象.onclose 连接关闭时触发
websocket方法
方法 描述
send() 使用连接发送数据
开始写客户端页面
  • 用户访问地址看到聊天室页面

用户发起http请求,根据urls.py进行路由匹配,找到对应的views.py下面的视图函数,返回对应的index.html页面

让客户端主动向服务端发起websocket连接,服务端接收到连接后进行握手验证

  • 客户端发起websocket请求
  • 收发消息-客户端向服务端发消息

app01/templates/index.html

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket客户端title>
    <style>
        .message {
            height: 300px;
            border: 1px solid #dddddd;
            width: 100%;
        }
    style>
head>
<body>
<div class="message" id="message">div>
<div>
    <input type="text" placeholder="请输入" id="txt">
    <input type="button" value="发送" onclick="sendMessage()">
    <input type="button" value="关闭连接" onclick="closeConn()">
div>
<script>
	
	// 客户端向服务端发起连接请求
    socket = new WebSocket("ws://127.0.0.1:8001/room/123/");

    // 客户端与服务端创建好连接之后触发(服务端执行self.accept())
    socket.onopen = function (event){
        let tag = document.createElement("div");
        tag.innerText = "[连接成功]";
        document.getElementById("message").appendChild(tag);
    }

    // 当 websocket接收到服务端发来的消息时,自动会触发这个函数
    socket.onmessage = function (event){
        let tag = document.createElement("div");
        tag.innerText = event.data;
        document.getElementById("message").appendChild(tag);
    }

    // 服务端主动断开连接时,这个方法被触发
    socket.onclose = function (event){
        let tag = document.createElement("div");
        tag.innerText = "[断开连接]";
        document.getElementById("message").appendChild(tag);
    }

	// 客户端向服务端发送消息
    function sendMessage(){
        let tag = document.getElementById("txt");
        socket.send(tag.value);		
    }

	// 客户端向服务端发送断开连接的请求
    function closeConn(){
        socket.close();     
    }

script>
body>
html>


ASGI部署

配置完毕后,你需要运行你写的django程序,如果你的使用的django和channels版本和博主使用的一样都是最新的则根据下面来,如果djangochannels的版本使用的是2.x3.x请自行在网上找部署说明,这个不难:

pip install daphne

# 安装完毕后使用daphne启动服务进程
# 项目名称和端口请与你自己的保持一致
daphne djangoProject.asgi:application -b 127.0.0.1 -p 8001

部署完毕后打开你的浏览器访问http://127.0.0.1:8001/index/可以看到如下页面,客户端可以持续给服务端发送消息,服务端也能将浏览器发过来的消息加以SB后缀返回
WebSocket是什么_第2张图片
就目前而言,上文基于django实现的websocket请求只能对某个浏览器创建连接,进行收发消息,还未实现群聊的功能

Web聊天室-实现群聊功能

基于channels中提供的channnels layer来实现

基于上面的工程修改相关文件

settings.py中配置

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels.layers.InMemoryChannelLayer",
    }
}

app01/templates/index.html

	// 客户端向服务端发起连接请求
    socket = new WebSocket("ws://127.0.0.1:8001/room/{{ group_num }}/");

app01/views.py

from django.shortcuts import render


def index(request):
    group_num = request.GET.get("num")
    return render(request, 'index.html', {"group_num": group_num})

在这里插入图片描述

WebSocket是什么_第3张图片
WebSocket是什么_第4张图片

consummers.py

from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer
from asgiref.sync import async_to_sync


class ChatConsumer(WebsocketConsumer):
    def websocket_connect(self, message):
        # 接收这个客户端的连接
        self.accept()

        # 获取群号,获取路由匹配中的
        group = self.scope['url_route']['kwargs'].get("group")

        # 将这个客户端的连接对象加入到某个地方内存, group:群号
        async_to_sync(self.channel_layer.group_add)(group, self.channel_name)

    def websocket_receive(self, message):
        group = self.scope['url_route']['kwargs'].get("group")

        # 通知组内的所有客户端,执行 xx_oo 方法,在此方法中自己可以去定义任意的功能。
        async_to_sync(self.channel_layer.group_send)(group, {"type": "xx.oo", 'message': message})

    def xx_oo(self, event):
        text = event['message']['text']
        self.send(text)

    def websocket_disconnect(self, message):
        group = self.scope['url_route']['kwargs'].get("group")

        async_to_sync(self.channel_layer.group_discard)(group, self.channel_name)
        raise StopConsumer()

重新运行django服务

# 项目名称和端口请与你自己的保持一致
daphne djangoProject.asgi:application -b 127.0.0.1 -p 8001

同时开启三个客户端,保持群号差异,分别发送消息,可以看到如下效果,即我们实现了一个非常简易的群聊功能
WebSocket是什么_第5张图片

总结

非常感谢你能看到这里,到此为止,我们对于websocket的介绍以及基于Django Channels实现一个简单的群聊系统,赶紧动起手来,Ctrl+C, Ctrl+V感受一些WebSocket的魅力把~,博主水平有限,不足之处还请大家在评论区中指出

你可能感兴趣的:(websocket,网络,python,django,网络协议)