1、简介

http(无状态、短连接)是不能由服务器主动向客户端发送请求,展示信息的。

我们要想实现一个人发了信息,能够实时展示在其它人的页面,有以下的方法。

  • 轮询
  • 长轮询
  • WebSocket

image-20240320231144045

WebSocket是一种协议,设计用于提供低延时、==全双工==和长期运行的连接。

  • ==轮询==(大量的请求与响应)
  • ==长轮询==(频繁的建立和关闭连接)
  • WebSocket的优势
    • 双向实时通信
      • 允许在单个、长时间的连接上进行双向实时通信。在需要快速实时更新的应用程序中,比http更加高效
    • 降低延迟
      • 链接一旦建立就会保持开放,数据可以在客户端和服务器之间以比http更低的延迟进行传输
    • 更高效的资源利用
      • 可以减少重复请求和响应的开销,因为它的链接只需要建立一次

2、心跳机制

  • 心跳包

    • 一种特殊的数据包

      • 不包含任何实际数据,仅用来维持连接状态
    • 一个空数据帧

      • 定期发送,确保链接仍然有效,避免长时间没有数据传输而被中断
    • ==如果一段时间内没有收到对方的心跳包,就可以认为连接已经断开==

3、限制

  • 不提供加密功能
  • 不支持古老的浏览器
  • 优化很重要
    • 保持长连接需要服务器不断地维护和处理连接状态,需要优化性能

4、Django

==Django默认不支持WebSocket,需要进行配置==

4.1、下载channels库和daphne

1
python -m pip install -U channels["daphne"]

4.2、配置setting.py文件

setting.py进行注册

1
2
3
4
5
6
INSTALLED_APPS = [
'daphne',
···
···
'channels',
]

配置ASGI_APPLICATION

1
2
3
4
5
6
7
8
ASGI_APPLICATION = '项目名.asgi.application'

使用下面这个帮助我们管理多人聊天
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels.layers.InMemoryChannelLayer",
}
}

4.3、验证

启动django服务,若出现以下内容则表示配置成功

image-20240330100634783

4.4、修改asgi.py文件

1
2
3
4
5
6
7
8
from channels.routing import ProtocolTypeRouter, URLRouter

from . import routing

application = ProtocolTypeRouter({
'http': get_asgi_application(),
'websocket': URLRouter(websocket_urlpatterns),
})

4.5、创建ws路由

创建websocket服务端文件consumers.py文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from channels.generic.websocket import AsyncWebsocketConsumer

#异步方法
class MyConsumer(AsyncWebsocketConsumer):
async def connect(self):
await self.accept()

async def disconnect(self, close_code):
pass

async def receive(self, text_data):
await self.send(text_data)


# 同步方式,仅作示例,不使用
class SyncConsumer(WebsocketConsumer):
def connect(self):
self.accept()

def disconnect(self, close_code):
pass
# 从WebSocket中接收消息
def receive(self, text_data=None, bytes_data=None):
pass

4.6、创建routing.py文件

1
2
3
4
5
6
from django.urls import re_path
from . import consumers

websocket_urlpatterns = [
re_path(r'websocket/$', consumers.MyConsumer.as_asgi()),
]

4.7、编写前端页面的websocket

1
2
3
4
5
var socket = new WebSocket("ws:127.0.0.1:8000/websocket/");

socket.onopen = function () {
console.log('连接成功');//成功连接上Websocket
};

5、示例

5.1、前端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>myserver</title>
</head>

<body>
<div class="message" id="message"></div>
<div>
<input type="text" placeholder="请输入" id="txt">
<input type="button" value="发送" onclick="sendMessage()">
</div>
<script>
socket = new WebSocket("ws://127.0.0.1:8000/room/123")

socket.onopen = function () {
socket.send('hello,world!')
}

socket.onmessage = function (event) {
let tag = document.createElement('div')
tag.innerText = event.data
document.getElementById('message').appendChild(tag)
}

function sendMessage() {
let tag = document.getElementById('txt')
socket.send(tag.value)
}


</script>
</body>

</html>

5.2、后端

项目目录

image-20240330101629850

routing.py

1
2
3
4
5
6
7
8
from django.urls import re_path

from myapp import consumers

websocket_urlpatterns = [
re_path(r"room/123", consumers.ChatConsumer.as_asgi()),
]

consumers.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer
from asgiref.sync import async_to_sync
import time
import requests


class ChatConsumer(WebsocketConsumer):
def websocket_connect(self, message):
# print('连接成功')
# 有客户端向后端发送websocket请求时,自动触发
# 服务端运行和客户端创建连接
self.accept()

# self.send('您好,请问你有什么需求?')

# 将客户端的连接存储起来
# async_to_sync(self.channel_layer.group_add)('my-team', self.channel_name)

def websocket_receive(self, msg):
print(msg['text'])
# headers = {
# 'Content-Type': 'application/json'
# }
# params = {
# 'access_token': '24.4c18ca00fd26adae686089aaeae3813e.2592000.1713699576.282335-54834605',
# }
# data = {
# "messages": [
# {"role": "user", "content": msg['text']}
# ]
# }
# # 浏览器像后端发送数据时触发,用户传送的数据为message,用send方法回复
# response = requests.post('https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions_pro',
# headers=headers, json=data, params=params)
# result = response.json()['result']
self.send('hello,bupt')

# 通知组内的所有成员
# async_to_sync(self.channel_layer.group_send)('my-team', {"type": 'sendmessage', 'msg': message})

# def sendmessage(self, event):
# content = event['msg']['text']
# self.send(content)

def websocket_disconnect(self, message):
# 客户端与服务端主动断开连接时触发
# print('断开连接')
# async_to_sync(self.channel_layer.group_discard)('my-team', self.channel_name)
raise StopConsumer()

asgi.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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', 'myserver.settings')

# application = get_asgi_application()

application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": URLRouter(routing.websocket_urlpatterns)
})

6、参考文献

https://blog.csdn.net/qq_46042132/article/details/130994781