当前位置:   article > 正文

Django利用Channels+websocket开发聊天室_django websocket channels

django websocket channels

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

前言

一、什么是Websocket?

2.Python-Django ASGI

3,Django开发聊天室或信息推送


前言

数据库系统课程设计要求,要开发一个B2B的售卖平台,本来开发浅薄的我,粗糙又基础的完成了一些基本的功能,想要开发一个单独的一对一聊天的功能(类似于微信这类),查阅了不少资料,依旧没思路,但是却知晓了服务器推送信息和聊天室的开发,记个笔记。


提示:以下是本篇文章正文内容,下面案例可供参考

一、什么是Websocket

1,Websocket的诞生背景:网站为了实现推送技术,用的基本是轮询,轮询是基于浏览器不断对服务器发出HTTP请求,服务器范围最新数据给客户端浏览器,这种模式缺点明显,带宽浪费严重,在这种背景下,HTML5诞生了Websocket协议,能更好的节省服务器资源并实现通讯。

2,WebSocket特点:WebSocket是单个TCP连接上的全双工通信,浏览器和服务器只需要完成一次握手,就可以创建持久性的连接,实现数据的双向传输。

2.Python-Django ASGI

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接口。

3,Django开发聊天室或信息推送

第一步:下载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模块进行修改如下:

  1. import os
  2. from django.core.asgi import get_asgi_application
  3. from channels.routing import ProtocolTypeRouter, URLRouter
  4. from websocket_demo import routing
  5. os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'web_socket.settings')
  6. application = ProtocolTypeRouter({
  7. "http": get_asgi_application(),
  8. "websocket": URLRouter(routing.websocket_urlpatterns),
  9. })

图中代码很容易理解:将服务器的请求分成了两类,http请求则走默认的asgi APP流程,是Websocket请求则进入URL路由,即websocket_demo创建的routing.py文件

第六步,在websocket_demo的APP中views视图下创建chat.py并且配置routing.py的文件:

  1. from django.urls import re_path
  2. from websocket_demo.views import chat
  3. websocket_urlpatterns = [
  4. re_path(r'chat/(?P<group>\w+)/$', chat.ChatConsumer.as_asgi()),
  5. ]

使用正则路径匹配chat/数字/的路径,是则进入chat.py文件中的CharConsumer函数

第七步,在urls.py中配置路由,指向websocket_demo中的chat.py文件

  1. from django.urls import path, include
  2. from websocket_demo.views import chat
  3. urlpatterns = [
  4. path('index/', chat.chat)
  5. ]

第八步,编写chat.py文件的内容如下:

  1. from django.shortcuts import render
  2. from channels.generic.websocket import WebsocketConsumer
  3. from channels.exceptions import StopConsumer
  4. from asgiref.sync import async_to_sync
  5. from datetime import datetime
  6. def chat(request):
  7. group_number = request.GET.get('num')
  8. return render(request, 'web/index.html', context)
  9. class ChatConsumer(WebsocketConsumer):
  10. def websocket_connect(self, message):
  11. self.accept()
  12. group = self.scope['url_route']['kwargs'].get("group")
  13. async_to_sync(self.channel_layer.group_add)(group, self.channel_name)
  14. def websocket_receive(self, message):
  15. group = self.scope['url_route']['kwargs'].get("group")
  16. async_to_sync(self.channel_layer.group_send)(group, {"type": "chat", 'message': message})
  17. def chat(self, event):
  18. text = event['message']['text']
  19. self.send(text)
  20. def websocket_disconnect(self, message):
  21. group = self.scope['url_route']['kwargs'].get("group")
  22. async_to_sync(self.channel_layer.group_discard)(group, self.channel_name)
  23. print('客户端断开连接了')
  24. 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文件:

        

  1. *, *:before, *:after {
  2. box-sizing: border-box;
  3. }
  4. :root {
  5. --white: #fff;
  6. --black: #000;
  7. --bg: #f8f8f8;
  8. --grey: #999;
  9. --dark: #1a1a1a;
  10. --light: #e6e6e6;
  11. --wrapper: 1000px;
  12. --blue: #00b0ff;
  13. }
  14. body {
  15. background-color: var(--bg);
  16. -webkit-font-smoothing: antialiased;
  17. -moz-osx-font-smoothing: grayscale;
  18. text-rendering: optimizeLegibility;
  19. font-family: 'Source Sans Pro', sans-serif;
  20. font-weight: 400;
  21. background-image: url("../img/image.jpg");
  22. background-size: cover;
  23. background-repeat: none;
  24. }
  25. .wrapper {
  26. position: relative;
  27. left: 50%;
  28. width: var(--wrapper);
  29. height: 800px;
  30. -webkit-transform: translate(-50%, 0);
  31. transform: translate(-50%, 0);
  32. }
  33. .container {
  34. position: relative;
  35. top: 50%;
  36. left: 50%;
  37. width: 80%;
  38. height: 75%;
  39. background-color: var(--white);
  40. -webkit-transform: translate(-50%, -50%);
  41. transform: translate(-50%, -50%);
  42. }
  43. .container .left {
  44. float: left;
  45. width: 37.6%;
  46. height: 100%;
  47. border: 1px solid var(--light);
  48. background-color: var(--white);
  49. }
  50. .container .left .top {
  51. position: relative;
  52. width: 100%;
  53. height: 96px;
  54. padding: 29px;
  55. }
  56. .container .left .top:after {
  57. position: absolute;
  58. bottom: 0;
  59. left: 50%;
  60. display: block;
  61. width: 80%;
  62. height: 1px;
  63. content: '';
  64. background-color: var(--light);
  65. -webkit-transform: translate(-50%, 0);
  66. transform: translate(-50%, 0);
  67. }
  68. .container .left input {
  69. float: left;
  70. width: 188px;
  71. height: 42px;
  72. padding: 0 15px;
  73. border: 1px solid var(--light);
  74. background-color: #eceff1;
  75. border-radius: 21px;
  76. font-family: 'Source Sans Pro', sans-serif;
  77. font-weight: 400;
  78. }
  79. .container .left input:focus {
  80. outline: none;
  81. }
  82. .container .left a.search {
  83. display: block;
  84. float: left;
  85. width: 42px;
  86. height: 42px;
  87. margin-left: 10px;
  88. border: 1px solid var(--light);
  89. background-color: var(--blue);
  90. background-image: url("../img//name-type.png");
  91. background-repeat: no-repeat;
  92. background-position: top 12px left 14px;
  93. border-radius: 50%;
  94. }
  95. .container .left .people {
  96. margin-left: -1px;
  97. border-right: 1px solid var(--light);
  98. border-left: 1px solid var(--light);
  99. width: calc(100% + 2px);
  100. }
  101. .container .left .people .person {
  102. position: relative;
  103. width: 100%;
  104. padding: 12px 10% 16px;
  105. cursor: pointer;
  106. background-color: var(--white);
  107. }
  108. .container .left .people .person:after {
  109. position: absolute;
  110. bottom: 0;
  111. left: 50%;
  112. display: block;
  113. width: 80%;
  114. height: 1px;
  115. content: '';
  116. background-color: var(--light);
  117. -webkit-transform: translate(-50%, 0);
  118. transform: translate(-50%, 0);
  119. }
  120. .container .left .people .person img {
  121. float: left;
  122. width: 40px;
  123. height: 40px;
  124. margin-right: 12px;
  125. border-radius: 50%;
  126. }
  127. .container .left .people .person .name {
  128. font-size: 14px;
  129. line-height: 22px;
  130. color: var(--dark);
  131. font-family: 'Source Sans Pro', sans-serif;
  132. font-weight: 600;
  133. }
  134. .container .left .people .person .time {
  135. font-size: 14px;
  136. position: absolute;
  137. top: 16px;
  138. right: 10%;
  139. padding: 0 0 5px 5px;
  140. color: var(--grey);
  141. background-color: var(--white);
  142. }
  143. .container .left .people .person .preview {
  144. font-size: 14px;
  145. display: inline-block;
  146. overflow: hidden !important;
  147. width: 70%;
  148. white-space: nowrap;
  149. text-overflow: ellipsis;
  150. color: var(--grey);
  151. }
  152. .container .left .people .person.active, .container .left .people .person:hover {
  153. margin-top: -1px;
  154. margin-left: -1px;
  155. padding-top: 13px;
  156. border: 0;
  157. background-color: var(--blue);
  158. width: calc(100% + 2px);
  159. padding-left: calc(10% + 1px);
  160. }
  161. .container .left .people .person.active span, .container .left .people .person:hover span {
  162. color: var(--white);
  163. background: transparent;
  164. }
  165. .container .left .people .person.active:after, .container .left .people .person:hover:after {
  166. display: none;
  167. }
  168. .container .right {
  169. position: relative;
  170. float: left;
  171. width: 62.4%;
  172. height: 100%;
  173. }
  174. .container .right .top {
  175. width: 100%;
  176. height: 47px;
  177. padding: 15px 29px;
  178. background-color: #eceff1;
  179. }
  180. .container .right .top span {
  181. font-size: 15px;
  182. color: var(--grey);
  183. }
  184. .container .right .top span .name {
  185. color: var(--dark);
  186. font-family: 'Source Sans Pro', sans-serif;
  187. font-weight: 600;
  188. }
  189. .container .right .chat {
  190. position: relative;
  191. display: none;
  192. overflow: hidden;
  193. padding: 0 35px 92px;
  194. border-width: 1px 1px 1px 0;
  195. border-style: solid;
  196. border-color: var(--light);
  197. height: calc(100% - 48px);
  198. justify-content: flex-end;
  199. flex-direction: column;
  200. }
  201. .container .right .chat.active-chat {
  202. display: block;
  203. display: flex;
  204. }
  205. .container .right .chat.active-chat .bubble {
  206. transition-timing-function: cubic-bezier(0.4, -0.04, 1, 1);
  207. }
  208. .container .right .chat.active-chat .bubble:nth-of-type(1) {
  209. -webkit-animation-duration: 0.15s;
  210. animation-duration: 0.15s;
  211. }
  212. .container .right .chat.active-chat .bubble:nth-of-type(2) {
  213. -webkit-animation-duration: 0.3s;
  214. animation-duration: 0.3s;
  215. }
  216. .container .right .chat.active-chat .bubble:nth-of-type(3) {
  217. -webkit-animation-duration: 0.45s;
  218. animation-duration: 0.45s;
  219. }
  220. .container .right .chat.active-chat .bubble:nth-of-type(4) {
  221. -webkit-animation-duration: 0.6s;
  222. animation-duration: 0.6s;
  223. }
  224. .container .right .chat.active-chat .bubble:nth-of-type(5) {
  225. -webkit-animation-duration: 0.75s;
  226. animation-duration: 0.75s;
  227. }
  228. .container .right .chat.active-chat .bubble:nth-of-type(6) {
  229. -webkit-animation-duration: 0.9s;
  230. animation-duration: 0.9s;
  231. }
  232. .container .right .chat.active-chat .bubble:nth-of-type(7) {
  233. -webkit-animation-duration: 1.05s;
  234. animation-duration: 1.05s;
  235. }
  236. .container .right .chat.active-chat .bubble:nth-of-type(8) {
  237. -webkit-animation-duration: 1.2s;
  238. animation-duration: 1.2s;
  239. }
  240. .container .right .chat.active-chat .bubble:nth-of-type(9) {
  241. -webkit-animation-duration: 1.35s;
  242. animation-duration: 1.35s;
  243. }
  244. .container .right .chat.active-chat .bubble:nth-of-type(10) {
  245. -webkit-animation-duration: 1.5s;
  246. animation-duration: 1.5s;
  247. }
  248. .container .right .write {
  249. position: absolute;
  250. bottom: 29px;
  251. left: 30px;
  252. height: 42px;
  253. padding-left: 8px;
  254. border: 1px solid var(--light);
  255. background-color: #eceff1;
  256. width: calc(100% - 58px);
  257. border-radius: 5px;
  258. }
  259. .container .right .write input {
  260. font-size: 16px;
  261. float: left;
  262. width: 347px;
  263. height: 40px;
  264. padding: 0 10px;
  265. color: var(--dark);
  266. border: 0;
  267. outline: none;
  268. background-color: #eceff1;
  269. font-family: 'Source Sans Pro', sans-serif;
  270. font-weight: 400;
  271. }
  272. .container .right .write .write-link.attach:before {
  273. display: inline-block;
  274. float: left;
  275. width: 20px;
  276. height: 42px;
  277. content: '';
  278. background-image: url("../img/attachment.png");
  279. background-repeat: no-repeat;
  280. background-position: center;
  281. }
  282. .container .right .write .write-link.smiley:before {
  283. display: inline-block;
  284. float: left;
  285. width: 20px;
  286. height: 42px;
  287. content: '';
  288. background-image: url("../img/smiley.png");
  289. background-repeat: no-repeat;
  290. background-position: center;
  291. }
  292. .container .right .write .write-link.send:before {
  293. display: inline-block;
  294. float: left;
  295. width: 20px;
  296. height: 42px;
  297. margin-left: 11px;
  298. content: '';
  299. background-image: url("../img/send.png");
  300. background-repeat: no-repeat;
  301. background-position: center;
  302. }
  303. .container .right .bubble {
  304. font-size: 16px;
  305. position: relative;
  306. display: inline-block;
  307. clear: both;
  308. margin-bottom: 8px;
  309. padding: 13px 14px;
  310. vertical-align: top;
  311. border-radius: 5px;
  312. }
  313. .container .right .bubble:before {
  314. position: absolute;
  315. top: 19px;
  316. display: block;
  317. width: 8px;
  318. height: 6px;
  319. content: '\00a0';
  320. -webkit-transform: rotate(29deg) skew(-35deg);
  321. transform: rotate(29deg) skew(-35deg);
  322. }
  323. .container .right .bubble.you {
  324. float: left;
  325. color: var(--white);
  326. background-color: var(--blue);
  327. align-self: flex-start;
  328. -webkit-animation-name: slideFromLeft;
  329. animation-name: slideFromLeft;
  330. }
  331. .container .right .bubble.you:before {
  332. left: -3px;
  333. background-color: var(--blue);
  334. }
  335. .container .right .bubble.me {
  336. float: right;
  337. color: var(--dark);
  338. background-color: #eceff1;
  339. align-self: flex-end;
  340. -webkit-animation-name: slideFromRight;
  341. animation-name: slideFromRight;
  342. }
  343. .container .right .bubble.me:before {
  344. right: -3px;
  345. background-color: #eceff1;
  346. }
  347. .container .right .conversation-start {
  348. position: relative;
  349. width: 100%;
  350. margin-bottom: 27px;
  351. text-align: center;
  352. }
  353. .container .right .conversation-start span {
  354. font-size: 14px;
  355. display: inline-block;
  356. color: var(--grey);
  357. }
  358. .container .right .conversation-start span:before, .container .right .conversation-start span:after {
  359. position: absolute;
  360. top: 10px;
  361. display: inline-block;
  362. width: 30%;
  363. height: 1px;
  364. content: '';
  365. background-color: var(--light);
  366. }
  367. .container .right .conversation-start span:before {
  368. left: 0;
  369. }
  370. .container .right .conversation-start span:after {
  371. right: 0;
  372. }
  373. @keyframes slideFromLeft {
  374. 0% {
  375. margin-left: -200px;
  376. opacity: 0;
  377. }
  378. 100% {
  379. margin-left: 0;
  380. opacity: 1;
  381. }
  382. }
  383. @-webkit-keyframes slideFromLeft {
  384. 0% {
  385. margin-left: -200px;
  386. opacity: 0;
  387. }
  388. 100% {
  389. margin-left: 0;
  390. opacity: 1;
  391. }
  392. }
  393. @keyframes slideFromRight {
  394. 0% {
  395. margin-right: -200px;
  396. opacity: 0;
  397. }
  398. 100% {
  399. margin-right: 0;
  400. opacity: 1;
  401. }
  402. }
  403. @-webkit-keyframes slideFromRight {
  404. 0% {
  405. margin-right: -200px;
  406. opacity: 0;
  407. }
  408. 100% {
  409. margin-right: 0;
  410. opacity: 1;
  411. }
  412. }

index.js文件:

                

  1. document.querySelector('.chat[data-chat=person2]').classList.add('active-chat');
  2. document.querySelector('.person[data-chat=person2]').classList.add('active');
  3. var friends = {
  4. list: document.querySelector('ul.people'),
  5. all: document.querySelectorAll('.left .person'),
  6. name: '' },
  7. chat = {
  8. container: document.querySelector('.container .right'),
  9. current: null,
  10. person: null,
  11. name: document.querySelector('.container .right .top .name') };
  12. friends.all.forEach(function (f) {
  13. f.addEventListener('mousedown', function () {
  14. f.classList.contains('active') || setAciveChat(f);
  15. });
  16. });
  17. function setAciveChat(f) {
  18. friends.list.querySelector('.active').classList.remove('active');
  19. f.classList.add('active');
  20. chat.current = chat.container.querySelector('.active-chat');
  21. chat.person = f.getAttribute('data-chat');
  22. chat.current.classList.remove('active-chat');
  23. chat.container.querySelector('[data-chat="' + chat.person + '"]').classList.add('active-chat');
  24. friends.name = f.querySelector('.name').innerText;
  25. chat.name.innerHTML = friends.name;
  26. }

index.html文件:

  1. {% load static from static %}
  2. <!DOCTYPE html>
  3. <html lang="en" >
  4. <head>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1">
  7. <title>聊天窗口界面</title>
  8. <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,600" rel="stylesheet">
  9. <link rel="stylesheet" href="{% static 'web/chat/css/reset.min.css' %}">
  10. <link rel="stylesheet" href="{% static 'web/chat/css/style.css' %}">
  11. </head>
  12. <body>
  13. <div class="wrapper">
  14. <div class="container">
  15. <div class="left">
  16. <div class="top">
  17. <input type="text" placeholder="Search" />
  18. <a href="javascript:;" class="search"></a>
  19. </div>
  20. <ul class="people">
  21. <li class="person" data-chat="person2">
  22. <img src="/static/myadmin/dist/img/user2-160x160.jpg" alt="" />
  23. <span class="name">{{admin_name}}</span>
  24. <span class="preview">I was wondering...</span>
  25. </li>
  26. </ul>
  27. </div>
  28. <div class="right">
  29. <div class="top"><span>To: <span class="name">{{admin_name}}</span></span></div>
  30. <div class="chat" data-chat="person2" id="chat-send" >
  31. <div class="conversation-start">
  32. <span>{{time}}</span>
  33. </div>
  34. </div>
  35. <div class="write">
  36. <a href="javascript:;" class="write-link attach"></a>
  37. <input type="text" id="text"/>
  38. <a href="javascript:;" class="write-link smiley"></a>
  39. <a onclick="sendMessage()" class="write-link send"></a>
  40. </div>
  41. </div>
  42. </div>
  43. </div>
  44. <script src="{% static 'web/chat/js/index.js' %}"></script>
  45. <script type="text/javascript">
  46. var socket=new WebSocket("ws://127.0.0.1:8000/chat/{{group_number}}/");
  47. socket.onopen = function(event)
  48. {
  49. var tag=document.getElementById("chat-send");
  50. var d=document.createElement("div");
  51. d.className="bubble you";
  52. d.innerHTML ="连接成功";
  53. tag.appendChild(d);
  54. }
  55. function sendMessage(){
  56. let text=document.getElementById("text");
  57. var tag=document.getElementById("chat-send");
  58. var d=document.createElement("div");
  59. d.className="bubble me";
  60. d.innerHTML =text.value;
  61. tag.appendChild(d);
  62. socket.send(text.value);
  63. text.value=" ";
  64. }
  65. socket.onclose = function(){
  66. var tag=document.getElementById("chat-send");
  67. var d=document.createElement("div");
  68. d.className="bubble you";
  69. d.innerHTML ="服务器主动断开连接";
  70. tag.appendChild(d);
  71. }
  72. socket.onmessage=function(event){
  73. var tag=document.getElementById("chat-send");
  74. var d=document.createElement("div");
  75. d.className="bubble you";
  76. d.innerHTML =event.data;
  77. tag.appendChild(d);
  78. }
  79. </script>
  80. <div style="text-align:center;margin:1px 0; font:normal 14px/24px 'MicroSoft YaHei';">
  81. </div>
  82. </body>
  83. </html>

主要看javascript使用的函数,首先进行Websocket请ws://127.0.0.1:8000/chat/{{group_number}}/,然后分别的通过websocket发送信息,接受信息。

第九步,启动manage.py文件,同时打开两个页面观看效果:

        

可以看到,某个用户发送的信息,将会被服务器发送到同一聊天室的所有客户端,实现了信息推送的功能。 

但是要实现一对一聊天的功能,目前实现是没有思路,有大神看到烦请指点指点。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/327534
推荐阅读
相关标签
  

闽ICP备14008679号