提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
目录
前言
一、什么是Websocket?
2.Python-Django ASGI
3,Django开发聊天室或信息推送
前言
数据库系统课程设计要求,要开发一个B2B的售卖平台,本来开发浅薄的我,粗糙又基础的完成了一些基本的功能,想要开发一个单独的一对一聊天的功能(类似于微信这类),查阅了不少资料,依旧没思路,但是却知晓了服务器推送信息和聊天室的开发,记个笔记。
提示:以下是本篇文章正文内容,下面案例可供参考
1,Websocket的诞生背景:网站为了实现推送技术,用的基本是轮询,轮询是基于浏览器不断对服务器发出HTTP请求,服务器范围最新数据给客户端浏览器,这种模式缺点明显,带宽浪费严重,在这种背景下,HTML5诞生了Websocket协议,能更好的节省服务器资源并实现通讯。
2,WebSocket特点:WebSocket是单个TCP连接上的全双工通信,浏览器和服务器只需要完成一次握手,就可以创建持久性的连接,实现数据的双向传输。
1,WSGI:Python Web Server Gateway Interface,就是Web服务器网关接口,主要是规范了Web服务器和Web应用之间的交互,WSGI将Django分成了三类,服务器,APP,中间件。服务器就是用来监听某端口,APP用来调用某个函数,而中间件则位于两者中间,相当于一道门,起审核承接等作用。但是WSGI始终是为同步世界编写的,无法编写异步对象。因此,ASGI诞生了。
2,ASGI:Async Sever Gateway Interface,说白了,就是相当于WSGI+异步功能,而要调用WebSocket,Django就需要使用ASGI接口。
第一步:下载Channels模块
pip install channels
第二步:创建Django项目
Django-admin startproject websocket
创建后会看到Django3.0以后自存在asgi.py文件
第三步:创建一个APP应用
python manage.py startapp websocket_demo
第四步:进入settings.py模块,进行配置
首先在INSTALLED_APPS添加channels和websocket_demo
然后添加再在settings中添加ASGI应用
第五步:在创建的APP中创建routing.py文件以及进入asgi.py模块进行修改如下:
import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from websocket_demo import routing
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'web_socket.settings')
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": URLRouter(routing.websocket_urlpatterns),
})
图中代码很容易理解:将服务器的请求分成了两类,http请求则走默认的asgi APP流程,是Websocket请求则进入URL路由,即websocket_demo创建的routing.py文件
第六步,在websocket_demo的APP中views视图下创建chat.py并且配置routing.py的文件:
from django.urls import re_path
from websocket_demo.views import chat
websocket_urlpatterns = [
re_path(r'chat/(?P\w+)/$', chat.ChatConsumer.as_asgi()),
]
使用正则路径匹配chat/数字/的路径,是则进入chat.py文件中的CharConsumer函数
第七步,在urls.py中配置路由,指向websocket_demo中的chat.py文件
from django.urls import path, include
from websocket_demo.views import chat
urlpatterns = [
path('index/', chat.chat)
]
第八步,编写chat.py文件的内容如下:
from django.shortcuts import render
from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer
from asgiref.sync import async_to_sync
from datetime import datetime
def chat(request):
group_number = request.GET.get('num')
return render(request, 'web/index.html', context)
class ChatConsumer(WebsocketConsumer):
def websocket_connect(self, message):
self.accept()
group = self.scope['url_route']['kwargs'].get("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")
async_to_sync(self.channel_layer.group_send)(group, {"type": "chat", 'message': message})
def chat(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)
print('客户端断开连接了')
raise StopConsumer()
首先第一个函数的意思是,进入index页面后会先进入chat函数,获取URL中请求的参数num,渲染到index.html页面,然后index.html页面使用javascript发起websocket请求,发起websocket请求后,ASGI服务器通过"websocket": URLRouter(routing.websocket_urlpatterns)路由访问routing文件,routing文件指向了ChatConsumer(WebsocketConsumer)类。
第二个函数ChatConsumer(WebsocketConsumer)类对websocket请求进行了处理,接收websocket的请求后获取组号即num的值,然后接收websocket发送的信息后调用chat函数将收到的信息同等的发送回去,websocket_disconnet则是客户端断开请求后触发的函数。
前端index.html的文件如下:
reset.min.css文件:
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}table{border-collapse:collapse;border-spacing:0}
style.css文件:
*, *:before, *:after {
box-sizing: border-box;
}
:root {
--white: #fff;
--black: #000;
--bg: #f8f8f8;
--grey: #999;
--dark: #1a1a1a;
--light: #e6e6e6;
--wrapper: 1000px;
--blue: #00b0ff;
}
body {
background-color: var(--bg);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
font-family: 'Source Sans Pro', sans-serif;
font-weight: 400;
background-image: url("../img/image.jpg");
background-size: cover;
background-repeat: none;
}
.wrapper {
position: relative;
left: 50%;
width: var(--wrapper);
height: 800px;
-webkit-transform: translate(-50%, 0);
transform: translate(-50%, 0);
}
.container {
position: relative;
top: 50%;
left: 50%;
width: 80%;
height: 75%;
background-color: var(--white);
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
.container .left {
float: left;
width: 37.6%;
height: 100%;
border: 1px solid var(--light);
background-color: var(--white);
}
.container .left .top {
position: relative;
width: 100%;
height: 96px;
padding: 29px;
}
.container .left .top:after {
position: absolute;
bottom: 0;
left: 50%;
display: block;
width: 80%;
height: 1px;
content: '';
background-color: var(--light);
-webkit-transform: translate(-50%, 0);
transform: translate(-50%, 0);
}
.container .left input {
float: left;
width: 188px;
height: 42px;
padding: 0 15px;
border: 1px solid var(--light);
background-color: #eceff1;
border-radius: 21px;
font-family: 'Source Sans Pro', sans-serif;
font-weight: 400;
}
.container .left input:focus {
outline: none;
}
.container .left a.search {
display: block;
float: left;
width: 42px;
height: 42px;
margin-left: 10px;
border: 1px solid var(--light);
background-color: var(--blue);
background-image: url("../img//name-type.png");
background-repeat: no-repeat;
background-position: top 12px left 14px;
border-radius: 50%;
}
.container .left .people {
margin-left: -1px;
border-right: 1px solid var(--light);
border-left: 1px solid var(--light);
width: calc(100% + 2px);
}
.container .left .people .person {
position: relative;
width: 100%;
padding: 12px 10% 16px;
cursor: pointer;
background-color: var(--white);
}
.container .left .people .person:after {
position: absolute;
bottom: 0;
left: 50%;
display: block;
width: 80%;
height: 1px;
content: '';
background-color: var(--light);
-webkit-transform: translate(-50%, 0);
transform: translate(-50%, 0);
}
.container .left .people .person img {
float: left;
width: 40px;
height: 40px;
margin-right: 12px;
border-radius: 50%;
}
.container .left .people .person .name {
font-size: 14px;
line-height: 22px;
color: var(--dark);
font-family: 'Source Sans Pro', sans-serif;
font-weight: 600;
}
.container .left .people .person .time {
font-size: 14px;
position: absolute;
top: 16px;
right: 10%;
padding: 0 0 5px 5px;
color: var(--grey);
background-color: var(--white);
}
.container .left .people .person .preview {
font-size: 14px;
display: inline-block;
overflow: hidden !important;
width: 70%;
white-space: nowrap;
text-overflow: ellipsis;
color: var(--grey);
}
.container .left .people .person.active, .container .left .people .person:hover {
margin-top: -1px;
margin-left: -1px;
padding-top: 13px;
border: 0;
background-color: var(--blue);
width: calc(100% + 2px);
padding-left: calc(10% + 1px);
}
.container .left .people .person.active span, .container .left .people .person:hover span {
color: var(--white);
background: transparent;
}
.container .left .people .person.active:after, .container .left .people .person:hover:after {
display: none;
}
.container .right {
position: relative;
float: left;
width: 62.4%;
height: 100%;
}
.container .right .top {
width: 100%;
height: 47px;
padding: 15px 29px;
background-color: #eceff1;
}
.container .right .top span {
font-size: 15px;
color: var(--grey);
}
.container .right .top span .name {
color: var(--dark);
font-family: 'Source Sans Pro', sans-serif;
font-weight: 600;
}
.container .right .chat {
position: relative;
display: none;
overflow: hidden;
padding: 0 35px 92px;
border-width: 1px 1px 1px 0;
border-style: solid;
border-color: var(--light);
height: calc(100% - 48px);
justify-content: flex-end;
flex-direction: column;
}
.container .right .chat.active-chat {
display: block;
display: flex;
}
.container .right .chat.active-chat .bubble {
transition-timing-function: cubic-bezier(0.4, -0.04, 1, 1);
}
.container .right .chat.active-chat .bubble:nth-of-type(1) {
-webkit-animation-duration: 0.15s;
animation-duration: 0.15s;
}
.container .right .chat.active-chat .bubble:nth-of-type(2) {
-webkit-animation-duration: 0.3s;
animation-duration: 0.3s;
}
.container .right .chat.active-chat .bubble:nth-of-type(3) {
-webkit-animation-duration: 0.45s;
animation-duration: 0.45s;
}
.container .right .chat.active-chat .bubble:nth-of-type(4) {
-webkit-animation-duration: 0.6s;
animation-duration: 0.6s;
}
.container .right .chat.active-chat .bubble:nth-of-type(5) {
-webkit-animation-duration: 0.75s;
animation-duration: 0.75s;
}
.container .right .chat.active-chat .bubble:nth-of-type(6) {
-webkit-animation-duration: 0.9s;
animation-duration: 0.9s;
}
.container .right .chat.active-chat .bubble:nth-of-type(7) {
-webkit-animation-duration: 1.05s;
animation-duration: 1.05s;
}
.container .right .chat.active-chat .bubble:nth-of-type(8) {
-webkit-animation-duration: 1.2s;
animation-duration: 1.2s;
}
.container .right .chat.active-chat .bubble:nth-of-type(9) {
-webkit-animation-duration: 1.35s;
animation-duration: 1.35s;
}
.container .right .chat.active-chat .bubble:nth-of-type(10) {
-webkit-animation-duration: 1.5s;
animation-duration: 1.5s;
}
.container .right .write {
position: absolute;
bottom: 29px;
left: 30px;
height: 42px;
padding-left: 8px;
border: 1px solid var(--light);
background-color: #eceff1;
width: calc(100% - 58px);
border-radius: 5px;
}
.container .right .write input {
font-size: 16px;
float: left;
width: 347px;
height: 40px;
padding: 0 10px;
color: var(--dark);
border: 0;
outline: none;
background-color: #eceff1;
font-family: 'Source Sans Pro', sans-serif;
font-weight: 400;
}
.container .right .write .write-link.attach:before {
display: inline-block;
float: left;
width: 20px;
height: 42px;
content: '';
background-image: url("../img/attachment.png");
background-repeat: no-repeat;
background-position: center;
}
.container .right .write .write-link.smiley:before {
display: inline-block;
float: left;
width: 20px;
height: 42px;
content: '';
background-image: url("../img/smiley.png");
background-repeat: no-repeat;
background-position: center;
}
.container .right .write .write-link.send:before {
display: inline-block;
float: left;
width: 20px;
height: 42px;
margin-left: 11px;
content: '';
background-image: url("../img/send.png");
background-repeat: no-repeat;
background-position: center;
}
.container .right .bubble {
font-size: 16px;
position: relative;
display: inline-block;
clear: both;
margin-bottom: 8px;
padding: 13px 14px;
vertical-align: top;
border-radius: 5px;
}
.container .right .bubble:before {
position: absolute;
top: 19px;
display: block;
width: 8px;
height: 6px;
content: '\00a0';
-webkit-transform: rotate(29deg) skew(-35deg);
transform: rotate(29deg) skew(-35deg);
}
.container .right .bubble.you {
float: left;
color: var(--white);
background-color: var(--blue);
align-self: flex-start;
-webkit-animation-name: slideFromLeft;
animation-name: slideFromLeft;
}
.container .right .bubble.you:before {
left: -3px;
background-color: var(--blue);
}
.container .right .bubble.me {
float: right;
color: var(--dark);
background-color: #eceff1;
align-self: flex-end;
-webkit-animation-name: slideFromRight;
animation-name: slideFromRight;
}
.container .right .bubble.me:before {
right: -3px;
background-color: #eceff1;
}
.container .right .conversation-start {
position: relative;
width: 100%;
margin-bottom: 27px;
text-align: center;
}
.container .right .conversation-start span {
font-size: 14px;
display: inline-block;
color: var(--grey);
}
.container .right .conversation-start span:before, .container .right .conversation-start span:after {
position: absolute;
top: 10px;
display: inline-block;
width: 30%;
height: 1px;
content: '';
background-color: var(--light);
}
.container .right .conversation-start span:before {
left: 0;
}
.container .right .conversation-start span:after {
right: 0;
}
@keyframes slideFromLeft {
0% {
margin-left: -200px;
opacity: 0;
}
100% {
margin-left: 0;
opacity: 1;
}
}
@-webkit-keyframes slideFromLeft {
0% {
margin-left: -200px;
opacity: 0;
}
100% {
margin-left: 0;
opacity: 1;
}
}
@keyframes slideFromRight {
0% {
margin-right: -200px;
opacity: 0;
}
100% {
margin-right: 0;
opacity: 1;
}
}
@-webkit-keyframes slideFromRight {
0% {
margin-right: -200px;
opacity: 0;
}
100% {
margin-right: 0;
opacity: 1;
}
}
index.js文件:
document.querySelector('.chat[data-chat=person2]').classList.add('active-chat');
document.querySelector('.person[data-chat=person2]').classList.add('active');
var friends = {
list: document.querySelector('ul.people'),
all: document.querySelectorAll('.left .person'),
name: '' },
chat = {
container: document.querySelector('.container .right'),
current: null,
person: null,
name: document.querySelector('.container .right .top .name') };
friends.all.forEach(function (f) {
f.addEventListener('mousedown', function () {
f.classList.contains('active') || setAciveChat(f);
});
});
function setAciveChat(f) {
friends.list.querySelector('.active').classList.remove('active');
f.classList.add('active');
chat.current = chat.container.querySelector('.active-chat');
chat.person = f.getAttribute('data-chat');
chat.current.classList.remove('active-chat');
chat.container.querySelector('[data-chat="' + chat.person + '"]').classList.add('active-chat');
friends.name = f.querySelector('.name').innerText;
chat.name.innerHTML = friends.name;
}
index.html文件:
{% load static from static %}
聊天窗口界面
主要看javascript使用的函数,首先进行Websocket请ws://127.0.0.1:8000/chat/{{group_number}}/,然后分别的通过websocket发送信息,接受信息。
第九步,启动manage.py文件,同时打开两个页面观看效果:
可以看到,某个用户发送的信息,将会被服务器发送到同一聊天室的所有客户端,实现了信息推送的功能。
但是要实现一对一聊天的功能,目前实现是没有思路,有大神看到烦请指点指点。