一、背景
在最近的项目中的一个需求是消息实时推送消息以及通知功能,项目使用django写的所以决定采用django-channels来实现websocket进行实时通讯。目前官方已经更新到2.1版本,相对于老的channels 1.x版本有了很大变化,无论是使用方式还是功能,其中最大的变化莫过于2.x版本中带来的asyncio特性,可使用异步处理模式。本文内容将介绍channels2版本使用,由于项目django是1.11,其中也遇到了一些坑,比如在channels在处理一次请求后hang住然后报错,后面修改了下django1.11版本的一点源码得以解决,2.0版本应该不会有问题。
二、channels介绍
channels是以django插件的形式存在,它不仅能处理http请求,还提供对websocket、MQTT等长连接支持。不仅如此,channels在保留了原生django的同步和易用的特性上还带来了异步处理方式(channels2.X版本),并且将django自带的认证系统以及session集成到模块中,扩展性非常强。官方文档:https://channels.readthedocs.io/en/latest/index.html
三、安装以及安装需求
channels2.0最低django版本要求是1.11+,python3.5+。笔者的版本是django1.11,直接安装可能有问题,以下是测试通过的版本。
笔者的相关版本如下:
如果django版本比较高直接采用pip安装:
redis安装可以参考博客:https://www.cnblogs.com/wdliu/p/9360286.html
四、开始使用
一、配置settings.py
笔者采用的redis作为channel layer(关于其介绍请移步至https://channels.readthedocs.io/en/latest/topics/channel_layers.html),它是实现消息推送的核心,在项目的settings.py中:
注册channles app:
配置channels layer:
二、路由配置
在项目settings文件同级目录中新增routing.py
<span style="color: #0000ff;">from
channels.auth <span style="color: #0000ff;">import<span style="color: #000000;"> AuthMiddlewareStack<span style="color: #0000ff;">from channels.routing <span style="color: #0000ff;">import<span style="color: #000000;"> ProtocolTypeRouter,URLRouter
<span style="color: #0000ff;">import<span style="color: #000000;"> deploy.routing
application =<span style="color: #000000;"> ProtocolTypeRouter({
<span style="color: #800000;">'<span style="color: #800000;">websocket<span style="color: #800000;">'<span style="color: #000000;">: AuthMiddlewareStack(
URLRouter(
deploy.routing.websocket_urlpatterns<span style="color: #008000;">#<span style="color: #008000;"> 指明路由文件是devops/routing.py
<span style="color: #000000;"> )
),})
最后在app里配置路由和对应的消费者,笔者这里是devops下的routing.py:
websocket_urlpatterns
=<span style="color: #000000;"> [url(r<span style="color: #800000;">'<span style="color: #800000;">^ws/deploy/(?P
]
项目目录结构如下:
三、编写webscoket消息处理方法(消费者)
deploy/consumers.py:
<span style="color: #0000ff;">from
channels.generic.websocket <span style="color: #0000ff;">import<span style="color: #000000;"> AsyncWebsocketConsumer<span style="color: #0000ff;">import<span style="color: #000000;"> json <span style="color: #0000ff;">class<span style="color: #000000;"> DeployResult(AsyncWebsocketConsumer):
async <span style="color: #0000ff;">def<span style="color: #000000;"> connect(self):
self.service_uid = self.scope[<span style="color: #800000;">"<span style="color: #800000;">url_route<span style="color: #800000;">"][<span style="color: #800000;">"<span style="color: #800000;">kwargs<span style="color: #800000;">"][<span style="color: #800000;">"<span style="color: #800000;">service_uid<span style="color: #800000;">"<span style="color: #000000;">]
self.chat_groupname = <span style="color: #800000;">'<span style="color: #800000;">chat%s<span style="color: #800000;">' %<span style="color: #000000;"> self.service_uid
<span style="color: #008000;">#<span style="color: #008000;"> 收到连接时候处理,
<span style="color: #000000;"> await self.channel_layer.group_add(
self.chat_group_name,self.channel_name
)
await self.accept()
async </span><span style="color: #0000ff;">def</span><span style="color: #000000;"> disconnect(self,close_code):
</span><span style="color: #008000;">#</span><span style="color: #008000;"> 关闭channel时候处理</span>
<span style="color: #000000;"> await self.channel_layer.group_discard(
self.chat_group_name,self.channel_name
)
</span><span style="color: #008000;">#</span><span style="color: #008000;"> 收到消息</span>
async <span style="color: #0000ff;">def</span><span style="color: #000000;"> receive(self,text_data):
text_data_json </span>=<span style="color: #000000;"> json.loads(text_data)
message </span>= text_data_json[<span style="color: #800000;">'</span><span style="color: #800000;">message</span><span style="color: #800000;">'</span><span style="color: #000000;">]
</span><span style="color: #0000ff;">print</span>(<span style="color: #800000;">"</span><span style="color: #800000;">收到消息--》</span><span style="color: #800000;">"</span><span style="color: #000000;">,message)
</span><span style="color: #008000;">#</span><span style="color: #008000;"> 发送消息到组</span>
<span style="color: #000000;"> await self.channel_layer.group_send(
self.chat_group_name,{
<span style="color: #800000;">'<span style="color: #800000;">type<span style="color: #800000;">': <span style="color: #800000;">'<span style="color: #800000;">client.message<span style="color: #800000;">'<span style="color: #000000;">,<span style="color: #800000;">'<span style="color: #800000;">message<span style="color: #800000;">'<span style="color: #000000;">: message
}
)
</span><span style="color: #008000;">#</span><span style="color: #008000;"> 处理客户端发来的消息</span>
async <span style="color: #0000ff;">def</span><span style="color: #000000;"> client_message(self,event):
message </span>= event[<span style="color: #800000;">'</span><span style="color: #800000;">message</span><span style="color: #800000;">'</span><span style="color: #000000;">]
</span><span style="color: #0000ff;">print</span>(<span style="color: #800000;">"</span><span style="color: #800000;">发送消息。。。</span><span style="color: #800000;">"</span><span style="color: #000000;">,message)
</span><span style="color: #008000;">#</span><span style="color: #008000;"> 发送消息到 WebSocket</span>
await self.send(text_data=<span style="color: #000000;">json.dumps({
</span><span style="color: #800000;">'</span><span style="color: #800000;">message</span><span style="color: #800000;">'</span><span style="color: #000000;">: message
}))</span></pre>
以上代码部分说明:
1.self.scope是单个连接传入的详细信息,其中包含了请求的session、以及django认证系统中的用户信息等;
2.async...await 是python3.5之后的新异步特性,基于asyncio模块;
四、发起webscoket请求
利用js发起websocket请求
websocket.onmessage </span>= <span style="color: #0000ff;">function</span><span style="color: #000000;"> (e) {
</span><span style="color: #0000ff;">var</span> data =<span style="color: #000000;"> JSON.parse(e.data);
</span><span style="color: #0000ff;">var</span> message = '\n' + data['message'<span style="color: #000000;">];
document.querySelector(</span>'#deploy-res').innerText += (message + '\n'<span style="color: #000000;">);
};
}</span></pre>
五、发送消息到channel
无论是消息的推送或者消息的接受,都是经过channel layer进行传输,以下是发送消息示例,
def send_channel_msg(channel_name,msg):
"""<span style="color: #000000;">
send msg to channel
:param channel_name:
:param msg:
:return:
"""<span style="color: #000000;">
async_to_sync(channel_layer.group_send)(channel_name,{"type": "deploy.run","text": msg})
六、生产部署
大多数django的应用部署方式都采用的是nginx+uwsgi进行部署,当django集成channels时候,由于uwsgi不能处理websocket请求,所以我们需要asgi服务器来处理websocket请求,官方推荐使用daphne。下一篇文章将介绍nginx+supervisor+daphne+uwsgi进行生产部署。