首页 > 其他分享 >websocket

websocket

时间:2024-05-06 23:45:54浏览次数:27  
标签:websocket self channels message 服务端 客户端

websocket,web版的socket

原web中:

  • http协议,无状态&短链接
    • 客户端主动连接服务器
    • 客户端向服务器发送消息,服务器收到返回数据
    • 客户端收到数据
    • 断开连接
  • https一些 + 对数据进行加密。

我们在开发过程中想要保留一些状态信息,基于cookie来做

现在支持:

  • http协议。一次请求一次响应。

  • websocket协议,创建持久的连接不断开,基于这个连接可以进行收发数据。【服务端向客户端主动推送消息】

    • web聊天室
    • 实时图表格,柱状图,饼图(highcharts)

websocket原理

  • http协议
    • 连接
    • 数据传输
    • 端口连接
  • webscoket协议,是建立在http协议之上的
    • 连接,客户端发起
    • 握手(验证),客户端发送一个消息,后端接收到消息再做一些特殊处理并返回。服务端支持websocket。
    • 收发数据(加密)
    • 断开连接。

django框架

django默认不支持websocket,需要安装组件:

pip install channels
pip install daphne

配置

  • 注册channels, daphne 必须在 INSTALLED_APPS 中的 django.contrib.staticfiles 之前,所以建议这两个app注册到其他之前,daphne又需要在channels之前。

    INSTALLED_APPS = [
        "daphne", # 注册
        "channels",  # 注册
        "django.contrib.admin",
        "django.contrib.auth",
        "django.contrib.contenttypes",
        "django.contrib.sessions",
        "django.contrib.messages",
        "django.contrib.staticfiles",
        
    ]
    
  • 在settings.py中添加asgi_application

    ASGI_APPLICATION = "项目名.asgi.application"  # django3会自动生成application在asgi.py模块中
    
  • 修改asgi.py文件

    import os
    from django.core.asgi import get_asgi_application
    from channels.routing import ProtocolTypeRouter, URLRouter
    
    from . import routing
    
    
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ws_demo.settings")
    
    # application = get_asgi_application()  默认的
    
    application = ProtocolTypeRouter({
        "http": get_asgi_application(),  # 自动走urls.py 找视图 就是之前正常的流程
        "websocket": URLRouter(routing.websocket_urlpatterns), # 创建的routing就相当于之前的urls.py 创建的consumer.py 就相当于之前的views.py
    })
    
  • 在settings.py的同级目录创建routing.py

    from django.urls import re_path, path
    
    from app01 import consumers
    
    websocket_urlpatterns = [
        # re_path(r"ws/(?P<group>\w+)/$", consumers.ChatConsumer.as_asgi()),
        path("ws/", consumers.ChatConsumer.as_asgi())
    ]
    
  • 在app01(就是你的app)目录下创建consumers.py,编写处理websocket的业务逻辑。

    from channels.generic.websocket import WebsocketConsumer
    from channels.exceptions import StopConsumer
    
    
    class ChatConsumer(WebsocketConsumer):
        # 有客户端向后端发送websocket连接的请求时,自动触发。
        def websocket_connect(self, message):
            
            # 服务端允许客户端创立连接
            self.accept()
        
        # 浏览器基于websocket向后端发送数据,自动触发接收消息。
        def websocket_receive(self, message):
            print(message)
            
            self.send("不要回复消息 不要")
            # self.close() 服务端也可以主动断开连接, self.close()
            
        # 客户端主动端口连接时,会自动触发
        def websocket_disconnect(self, message):
            print("连接已断开")
            raise StopConsumer()
    

    在django中需要了解的

    wsgi,之前默认使用的都是wsgi

    image-20240503234131360

    asgi,等于wsgi + 异步 + websocket

    image-20240503233034812

http

urls.py
views.py

websocket

routings.py
consumers.py

聊天室(单个人)

  • 访问地址看到聊天室的页面,HTTP请求。

  • 让客户端主动向服务端发起websocket连接,服务端接受到连接后通过(握手)。

    • 客户端:

      const socket = new WebSocket("ws://127.0.0.1:8888/ws/");
      
      // 必须以ws:开头 ws结尾
      
    • 服务端

      from channels.generic.websocket import WebsocketConsumer
      from channels.exceptions import StopConsumer
      
      
      class ChatConsumer(WebsocketConsumer):
          # 有客户端向后端发送websocket连接的请求时,自动触发。
          def websocket_connect(self, message):
              print('收到请求了')
              # 服务端允许客户端创立连接
              self.accept()
              
      
  • 收发消息

    • 客户端
    socket.onopen = (event)=>{
        socket.send("hello server!")
    };
    
    • 服务端

class ChatConsumer(WebsocketConsumer):
# 上面的代码和之前一样
def websocket_receive(self, message):
# message是一个字典 {'type': 'websocket.receive', 'text': 'hello server!'}
print("收到客户端的消息", message.get('text')) # hello server!
# 服务端给客户端发消息
self.send("不要回复消息 不要")
# self.close() 服务端也可以主动断开连接, self.close()
```

  • 收发消息(服务端主动发给客户端)

    from channels.generic.websocket import WebsocketConsumer
    from channels.exceptions import StopConsumer
    
    
    class ChatConsumer(WebsocketConsumer):
        # 有客户端向后端发送websocket连接的请求时,自动触发。
        def websocket_connect(self, message):
            # 服务端允许客户端创立连接
            self.accept()
            # 连接建立后 服务端主动给客户端发送消息
            self.send('客官您来啦~') 
            # self 代表连接对象
    
  • 断开连接(客户端主动断开)

    • 客户端

      socket.close()
      
    • 服务端

      from channels.generic.websocket import WebsocketConsumer
      from channels.exceptions import StopConsumer
      
      
      class ChatConsumer(WebsocketConsumer):
      	# 其他代码一样
          # 客户端主动端口连接时,会自动触发
          def websocket_disconnect(self, message):
              print("连接已断开")
              # 这个异常表示同意断开
              raise StopConsumer()
      
    • 断开连接(客户端发送关键字,服务端收到后先断开服务端,然后触发客户端的回调函数onclose)

      • 客户端

        socket.send('关闭')
        
      • 服务端

        from channels.generic.websocket import WebsocketConsumer
        from channels.exceptions import StopConsumer
        
        
        class ChatConsumer(WebsocketConsumer):
        	# 其他代码一样
            def websocket_receive(self, message):
                text = message['text']
                if text == '关闭':
                    self.close() # 执行这个会触发客户端的 socket.onclose()回调函数,也会执行服务端自己的断开websocket_disconnect,总结就是只要客户端和服务端谁执行了close,整个连接就都断开了。
        
      • 客户端

        // 服务端主动断开连接的时候,该方法会被触发
        socket.onclose = (event)=>{
            console.log("链接已关闭");
        }
        
      • 补充

        # 如果想要服务端主动断开连接的时候,不触发websocket_disconnect,那么在服务端断开之后,raise StopConsumer()即可
         def websocket_receive(self, message):
                text = message['text']
                if text == '关闭':
                    self.close() # 执行这个会触发客户端的 socket.onclose()回调函数
                    raise StopConsumer()
                self.send(text + "谢谢!")
                
            # 客户端主动端口连接时,会自动触发,也就是这里只触发客户端断开的逻辑,而不是发送关键字让服务端断开的逻辑。
            def websocket_disconnect(self, message):
                print("连接已断开")
                raise StopConsumer()
        

代码整合

# 后端
from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer


class ChatConsumer(WebsocketConsumer):
    def websocket_connect(self, message):
        self.accept()
        
    def websocket_receive(self, message):
        text = message['text']
        if text == '关闭':
            self.close() 
            raise StopConsumer()
        self.send(text + "谢谢!")

    def websocket_disconnect(self, message):
        print("连接已断开")
        raise StopConsumer()
// 前端
const socket = new WebSocket("ws://127.0.0.1:8888/ws/");

// 创建好连接自动触发(服务端self.accept())后自动触发。
socket.onopen = (event)=>{
    this.isShow = true
    // 往服务端发送消息
    socket.send(this.msg)
};

// 当websocket收到服务端的消息时,会自动触发这个函数
// 数据放在event里面,通过event.data能获取到数据的内容
socket.onmessage = (event)=>{
    this.messageList.push({
        id: this.messageList.length + 1,
        data: event.data
    })
};

// 如果有错误
socket.onerror = (error)=>{
    console.log("websocket error:", error);
};

// 服务端主动断开连时,这个方法会触发
socket.onclose = (event)=>{
    swal('服务端已断开链接')
    console.log("websocket connection closed:", event);
}
},

小结:

基于django实现websocket请求,但只能对某个人进行处理。主要就是后端的self

聊天室(群聊一)

修改后端即可

from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer

# 1. 创建一个列表,存储全部连接对象
# 因为每一个self都是一个连接对象,所以把他接入到一个列表中
CONN_LIST= []

class ChatConsumer(WebsocketConsumer):
    def websocket_connect(self, message):
        self.accept()
        
        CONN_LIST.append(self) # 把连接对象加入到列表

    def websocket_receive(self, message):
        text = message['text']
        
        for conn in CONN_LIST: # 循环遍历列表,拿到每一个连接对象,给每一个连接对象发消息
            conn.send(text + "消息已收到,这是自动回复。")
        
    def websocket_disconnect(self, message):
        CONN_LIST.remove(self) # 当连接断开后,把这个连接对象送对象列表中移除
        raise StopConsumer()

前端代码

<template>
  <div class="home">
    <div class="content">
      <p v-show="isShow">[链接建立成功]</p>
      <hr v-show="isShow">
      <ul v-for="(item, index) in messageList" :key="item.id">
        <li>收到来自服务端的消息:{{ index }}-{{ item.data }}</li>
      </ul>
    </div>
    
    <div class="inner">
      <label for="message">请输入:</label>
      <el-input id="message" v-model="msg"></el-input>

      <p>
      <el-row>
        <el-button type="success" @click="sendWS">发送</el-button>
        <el-button type="danger" @click="closeConn">关闭连接</el-button>
        <el-button type="warning" @click="messageList=[]">清空消息</el-button>
      </el-row>
    </p>
    </div>
    
  </div>
</template>

<script>


export default {
  data() {
    return {
      msg: '',
      isShow: false,
      messageList: []
    }
  },
  methods: {
    sendWS(){
      const socket = new WebSocket("ws://127.0.0.1:8888/");

      // 创建好连接自动触发(服务端self.accept())后自动触发。
      socket.onopen = (event)=>{
        this.isShow = true
        // 往服务端发送消息
        socket.send(this.msg)
      };

      // 当websocket收到服务端的消息时,会自动触发这个函数
      // 数据放在event里面,通过event.data能获取到数据的内容
      socket.onmessage = (event)=>{
        this.messageList.push({
          id: this.messageList.length + 1,
          data: event.data
        })

      };

      // 如果有错误
      socket.onerror = (error)=>{
        console.log("websocket error:", error);
      };

      // 服务端主动断开连时,这个方法会触发
      socket.onclose = (event)=>{
        swal('服务端已断开链接')
        console.log("websocket connection closed:", event);
      }
    },

    // 关闭连接函数
    closeConn(){
      swal("关闭连接函数已触发")
      socket.close()
    }
  },
  created(){
    let token = sessionStorage.getItem("token")
    if (!token){
      swal('请先登录').then(()=>{
        this.$router.push("/login")
      })
    }
  }
}
</script>

<style scoped>
  .home .content {
    border: 1px solid #dddddd;
    width: 800px;
    height: 500px;
    margin: 50px auto;
    overflow: auto;
  }
  .home .inner {
    width: 800px;
    margin: 50px auto;
  }
</style>

聊天室(群聊二)

基于channels中提供的channel layers来实现

  • settings中配置

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

    如果想要换成redis的

    pip3 install channels-redis
    
    # 写法1
    CHANNEL_LAYERS = {
        "default": {
            "BACKEND": "channels_redis.core.RedisChannelLayer",
            "CONFIG": {
                "hosts": [('IP', 6379)]
            },
        },
    }
    
    # 写法2
    CHANNEL_LAYERS = {
        'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {"hosts": ["redis://IP:6379/1"],},
        },
    } 
    
    # 写法3
    CHANNEL_LAYERS = {
        "default": {
            "BACKEND": "channels_redis.core.RedisChannelLayer",
            "CONFIG": {
                "hosts": ["redis://:password@IP:6379/0"],
                "symmetric_encryption_keys": [SECRET_KEY],
            },
        },
    }
    
  • consumers中添加特殊代码

    from channels.generic.websocket import WebsocketConsumer
    from channels.exceptions import StopConsumer
    

from asgiref.sync import async_to_sync

class ChatConsumer(WebsocketConsumer):
    # 有客户端向后端发送websocket连接的请求时,自动触发。
    def websocket_connect(self, message):
        # 服务端允许客户端创立连接
        self.accept()

        # 将这个客户端的连接对象加入到某个地方(内容 or redis)
        # xxx 表示一个群名,随便写 slef.channel_name 表示一个随机的别名
        # 需要将异步转成同步,用同步的方式做
        self.group = self.scope['url_route']['kwargs']['room_id']
        async_to_sync(self.channel_layer.group_add)(self.group, self.channel_name)

    # 浏览器基于websocket向后端发送数据,自动触发接收消息。
    def websocket_receive(self, message):
        text = message['text']
        if text == '关闭':
            self.close() # 执行这个会触发客户端的 socket.onclose()回调函数
            raise StopConsumer()
            return
        
        # 通知组内的所有客户端,执行xx_oo 方法,在此方法中可以自己取定义任意的功能,只是在字段的值中需要写成 xx.oo
        async_to_sync(self.channel_layer.group_send)(self.group, {"type":"xx.oo", "message": message})
        
    def xx_oo(self, event):
        text = event["message"]['text']
        # 这里的send表示给组里的每一个人send不是单独发哪一个,就是群聊里面发
        self.send(text + "哒咩")
        
    # 客户端主动端口连接时,会自动触发
    def websocket_disconnect(self, message):
        print("连接已断开")
        # 连接断开,将这个连接从组里移除掉
        async_to_sync(self.channel_layer.group_discard)(self.group, self.channel_name)
        raise StopConsumer()
```
  • routing的路径为

    from django.urls import  path
    
    from app01 import consumers
    
    websocket_urlpatterns = [
        path("chat/<str:room_id>/", consumers.ChatConsumer.as_asgi())
    
  • 前端访问的地址为

    const socket = new WebSocket("ws://127.0.0.1:8888/chat/123/");
    

简单总结

  • websocket是什么?协议。
  • django中实现websocket,channels组件。
    • 单独连接和收发数据
    • 手动创建连接表 & channel layers

标签:websocket,self,channels,message,服务端,客户端
From: https://www.cnblogs.com/ccsvip/p/18176219

相关文章

  • web server apache tomcat11-31-websocket
    前言整理这个官方翻译的系列,原因是网上大部分的tomcat版本比较旧,此版本为v11最新的版本。开源项目从零手写实现tomcatminicat别称【嗅虎】心有猛虎,轻嗅蔷薇。系列文章webserverapachetomcat11-01-官方文档入门介绍webserverapachetomcat11-02-setup启动web......
  • webapi添加添加websocket中间件
    添加位置我按照MSDN的例子添加了一个复述客户端响应的中间件。需要注意的时,中间件采用那种方式添加,添加在哪。哪种方式我选择创建一条管道分支,只要时ws的连接请求,就转到这个分支因此,我们需要使用的是MapWhen()来创建管道分支。添加在哪要注意授权的问题,所以应该添加到授权......
  • fiber使用websocket--多进程版本
    多进程数据是隔离的,也就是说处理不好,不同的用户登录,其实不在一个进程,没办法直接通信解决办法:定义一个全局变量,所有进程公用,要加锁如果是单进程版本其实就跟gin框架那些一样了多线程+channel不存在数据隔离的问题main.gopackagemainimport( "flag" "fmt" "log" "sync"......
  • 如何基于Django中的WebSockets和异步视图来实现实时通信功能
    本文分享自华为云社区《结合Django中的WebSockets和异步视图实现实时通信功能的完整指南》,作者:柠檬味拥抱。在现代Web应用程序中,实时通信已经成为了必不可少的功能之一。无论是在线聊天、实时数据更新还是实时通知,都需要通过实时通信技术来实现。Django作为一个强大的Web框架,提......
  • vuex结合websocket使用
    1、创建一个store文件夹,并在其中创建store.js文件,结合vuex:importVuefrom'vue'importVuexfrom'vuex'importcommonfrom"../common/common.js";Vue.use(Vuex)conststore=newVuex.Store({state:{/***是否需要强制登录*/......
  • websocket 压测
    压测背景:项目上线需要知道有多少个用户和导购能同时在线,并且正常使用场景分析:先进行用户端连接服务器,然后导购端在链接服务器,然后开始拉取视频列表,最后接听视频脚本设计:设置三个线程组线程组一,负责用户端链接socket,并且发送请求视频通话请求线程组二,负责导购端链接so......
  • vue中websocket的使用---详解
    一、什么是webscoketWebSockets 是一种先进的技术,它可以在用户的浏览器和服务器之间打开交互式通信会话。使用此API,可以向服务器发送消息并接收事件驱动的响应,而无需通过轮询服务器的方式以获得响应。 WebSockets这种技术中有一个接口名为WebSocket,它是一个用于连接WebSoc......
  • Springboot集成WebSocket实现智能聊天【Demo】
    背景openai目前越来越流行,其他ai产业也随之而来,偶然翻到openai接口文档,就想着可以调用接口实现智能聊天,接下来就写写我怎么接入websocket的过程,文笔不佳,谅解。接入WebSocket1.webSocket依赖<dependency><groupId>org.springframework.boot</groupId><artifactId......
  • websocket-fmp4播放器wsPlayer
    https://blog.csdn.net/tech2ipo/article/details/124369020 https://github.com/ZLMediaKit/ZLMediaKithttps://github.com/chatop2020/AKStream 一、web视频播放器的现状与问题1、流媒体协议选型:主流流媒体协议归纳总结:协议名称 网络传输协议 延时 编码类型 HTML5支持情......
  • Springboot 添加 WebSocket 服务
    后端WebSocketServer.java文件:packagecom.ruoyi;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.stereotype.Component;importorg.springframework.stereotype.Service;importjavax.websocket.*;importjavax.websocket.ser......