使用Node.js和Socket.IO扩展Django的实时处理功能


Posted in Python onApril 20, 2015

 今天,我们的目标是使用Django,Redis,和Socket.IO建立一个实时的聊天室。虽然几乎所有的Web应用程序都可以建立一个聊天室的。这篇文章将以较高的水平告诉你如何将基于REST的应用程序转换成一个实时的Web应用程序的。我会使用Django创建REST的部分,实际上自由地使用任何你舒服的语言/框架均可。接下来,让我们跳进代码,先列举我们所需要的部分。

组成:

  •     Django 1.4+
  •     Redis 2.6.x (版本可选,但是建议使用)
  •     Redis-py 2.7.x (仅当你使用Redis时需要)
  •     Node.js v0.8.x
  •     Socket.IO v0.9.x
  •     Cookie v0.0.5
  •     数据库、sqlite、其他你觉得类似数据库形式的 均可

 

你的使用的版本可能与我不同,我暂时未测试其他版本,全部使用当前最新稳定版本。如果你无法通过下面方法安装,我已经编译好Ubuntu的软件包。你可以从评论中得到其他操作系统版本情况。
 

#https://docs.djangoproject.com/en/dev/topics/install/
sudo apt-get install python-pip
sudo pip install django
 
#http://redis.io/download
sudo apt-get install redis-server
 
#https://github.com/andymccurdy/redis-py
sudo pip install redis  
   
#https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager
sudo apt-get install python-software-properties
sudo add-apt-repository ppa:chris-lea/node.js
sudo apt-get update
sudo apt-get install nodejs
 
#https://github.com/LearnBoost/socket.io
npm install socket.io
 
#https://github.com/shtylman/node-cookie
npm install cookie

让我们从Django Project开始
 

django-admin.py startproject realtime_tutorial && cd realtime_tutorial
python manage.py startapp core
mkdir nodejs

执行完以上的代码,django project就配置好了,接下来要做的是在settings文件中设置数据库。先创建一个空白数据库。(这是一个settings file的例子。在我的app中添加了一个“core”然后配置templates和urls的路径。你可以随意更改settings中的配置信息,但是要与你的app相对应。

Model

models很简单,我们将要建一个包含user和text的表。如果你想让他更复杂一些,可以添加chatroom等信息。(为了简单起见,这里只写了两个)
 

from django.db import models
from django.contrib.auth.models import User
 
class Comments(models.Model):
  user = models.ForeignKey(User)
  text = models.CharField(max_length=255)

这就是我们将要使用的model,接下来执行下面的syncdb代码(第一行代码),创建数据库。然后创建几个user来测试。(第二行代码)

python manage.py syncdb
python manage.py createsuperuser
 
Node Server With Socket.IO

这一部分将要介绍实时信息的发送和获取。使用Node.js创建一个依赖Socket.IO的app server,使用Redis 来做这项苦差事。在nodejs字典中,创建一个叫做“chat.js”的文件,然后把它放在这里:
 

var http = require('http');
var server = http.createServer().listen(4000);
var io = require('socket.io').listen(server);
var cookie_reader = require('cookie');
var querystring = require('querystring');
 
var redis = require('socket.io/node_modules/redis');
var sub = redis.createClient();

 
//订阅chat channel


sub.subscribe('chat');

 
//配置socket.io来存储Django设置的cookie
io.configure(function(){
  io.set('authorization', function(data, accept){
    if(data.headers.cookie){
      data.cookie = cookie_reader.parse(data.headers.cookie);
      return accept(null, true);
    }
    return accept('error', false);
  });
  io.set('log level', 1);
});
 
io.sockets.on('connection', function (socket) {
   
  //把信息从Redis发送到客户端
  sub.on('message', function(channel, message){
    socket.send(message);
  });
   
  //客户端通过socket.io发送消息
  socket.on('send_message', function (message) {
    values = querystring.stringify({
      comment: message,
      sessionid: socket.handshake.cookie['sessionid'],
    });
     
    var options = {
      host: 'localhost',
      port: 3000,
      path: '/node_api',
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Content-Length': values.length
      }
    };
     
    //使用Django server发消息
    var req = http.get(options, function(res){
      res.setEncoding('utf8');
       
      //输出错误信息
      res.on('data', function(message){
        if(message != 'Everything worked :)'){
          console.log('Message: ' + message);
        }
      });
    });
     
    req.write(values);
    req.end();
  });
});
首先,我们导入并创建http server来监听localhost 4000端口。然后订阅Redis的 "chat" chanel。最后,只要我们在Django view中调用就可以了。

上次我们设置了Socket.IO能在本地领域使用cookie的那个Django设置,这能让我们通过socket.handshake.cookie去访问cookie数据。能让我们怎样得到用户的session会话。

我们设置Socket.IO的cookies之后我们才能持有很多事件,第一个事件是Redis 发布通道,当我们的用户注意到一个新的消息已经被通知它将发送消息给所有站点的客户端。

另一个事件是当客户端通过Socket.IO发送一个信息,我们使用字符串查询(queryString)模块去创建一个query查询才能被发送到我们的Django服务。我们的Django服务在本地端口3000将会运行但你能改变了那个需求。路径设置成/node_api那个URL我们将不久创建在Django旁边。一旦我们发送queryString我们等待的Django就会保存相关组件并给我们返回"Everything worked(都在工作)"。如果我们没有得到返回给我们的输出错误就关闭节点控制台

一个关于不使用Redis的节点

你真的完全没必要为这项目使用Redis,我发现它将是一个好的学习体验,如果你想分流Redis你可以创建一个通道,使用表达式或一些其它类库,在这上面的代码会从Django里接收一个消息当一个注释被保存时,然后你能通过Socket.IO添加注释给所有的客户端
模板

这就是我们所有HTML和javascript被放置的地方,它允许我们显示注释和交互我们的Node服务
 

<!DOCTYPE html>
<html>
<head>
 <title>Realtime Django</title>
 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js" type="text/javascript"></script>
 <script src="http://localhost:4000/socket.io/socket.io.js"></script>
 <script>
  $(document).ready(function(){
   var socket = io.connect('localhost', {port: 4000});
    
   socket.on('connect', function(){
    console.log("connect");
   });
    
   var entry_el = $('#comment');
        
   socket.on('message', function(message) {
    //Escape HTML characters
    var data = message.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
     
    //Append message to the bottom of the list
    $('#comments').append('<li>' + data + '</li>');
    window.scrollBy(0, 10000000000);
    entry_el.focus();
   });
           
   entry_el.keypress(function(event){
    //When enter is pressed send input value to node server
    if(event.keyCode != 13) return;
    var msg = entry_el.attr('value');
    if(msg){
      socket.emit('send_message', msg, function(data){
        console.log(data);
      });
     
    //Clear input value 
    entry_el.attr('value', '');
    }
   });
  });
 </script>
</head>
<body>
  <ul id="comments">
    {% for comment in comments %}
      <li>{{comment.user}}: {{comment.text}}</li>
    {% endfor %}
  </ul>
  <input type="text" id="comment" name="comment" />
</body>
</html>

在上面我们用socket.IO在本地端口4000连接我们的节点服务。当从服务器得到了一个信息我们就在目录和添加它到我们注释列表里做了些转义,当我们想要发送一个信息我们就对输入盒子里做了相应的13(按下一个键)的按键检查。一旦那被按下后我们就发出信息给服务器使其被持有。一旦它被Django保存到我们的数据库我们就得到一个"message"事件将其添加到我们的会话列表里

我们的Django显示我们在下一步将加载一个"comments"变量,因此我们那样设置并遍历下面所有的循环。这部分仅仅是当页面初始加载时使用了,我们的javascript将添加数据给这个目录作为一个新的数据来自我们的Node服务

View

打开realtime_tutorial/core/views.py,然后像我一样编辑:
 

from core.models import Comments, User
 
from django.shortcuts import render
from django.http import HttpResponse, HttpResponseServerError
from django.views.decorators.csrf import csrf_exempt
from django.contrib.sessions.models import Session
from django.contrib.auth.decorators import login_required
 
import redis
 
@login_required
def home(request):
  comments = Comments.objects.select_related().all()[0:100]
  return render(request, 'index.html', locals())
 
@csrf_exempt
def node_api(request):
  try:
    #通过sessionid获得 user
    session = Session.objects.get(session_key=request.POST.get('sessionid'))
    user_id = session.get_decoded().get('_auth_user_id')
    user = User.objects.get(id=user_id)
 
    #创建Comment
    Comments.objects.create(user=user, text=request.POST.get('comment'))
     
    #创建后就把它发送到聊天室
    r = redis.StrictRedis(host='localhost', port=6379, db=0)
    r.publish('chat', user.username + ': ' + request.POST.get('comment'))
     
    return HttpResponse("Everything worked :)")
  except Exception, e:
    return HttpResponseServerError(str(e))

让我们看看这里发生了什么。home是一个标准的view文件。使用select_related来获得每一个comment的username,而不是在页面第一次加载的时候,就返回一个comment的query集合。

第二个就是我们Node app发送信息的view。我们从POST中获取sessionid,然后通过解码获得userid。确定user存在后,就可以创建comment了。现在吧username 和 comment 发送到 Redis server。最后,把数据发送到这里叫做"chat"的频道。

URLs

这里比较简单,因为我们将要使用Django自带的views和template。
 

from django.conf.urls import patterns, include, url
 
urlpatterns = patterns('',
  url(r'^$', 'core.views.home', name='home'),
  url(r'^node_api$', 'core.views.node_api', name='node_api'),
  url(r'^login/$', 'django.contrib.auth.views.login', {'template_name': 'admin/login.html'}, name='login'),
  url(r'^logout/$', 'django.contrib.auth.views.logout', {'next_page': '/'}, name='logout'),
)

Start It Up!

打开servers。
 

python manage.py runserver localhost:3000
 
#In a new terminal tab cd into the nodejs directory we created earlier
node chat.js

我把代码放到github。如果你想把它做得更好,就允许user创建、加入聊天室。你也可以使用PHP或者Rails开发。

如果你有什么问题,请在评论处写下或联系我。

Python 相关文章推荐
用python实现简单EXCEL数据统计的实例
Jan 24 Python
python实现图像识别功能
Jan 29 Python
Python使用cx_Oracle模块操作Oracle数据库详解
May 07 Python
python实现判断一个字符串是否是合法IP地址的示例
Jun 04 Python
Python使用Selenium模块实现模拟浏览器抓取淘宝商品美食信息功能示例
Jul 18 Python
Python异常处理操作实例详解
Aug 28 Python
Python判断两个文件是否相同与两个文本进行相同项筛选的方法
Mar 01 Python
75条笑死人的知乎神回复,用60行代码就爬完了
May 06 Python
Python 3.8正式发布,来尝鲜这些新特性吧
Oct 15 Python
Python绘图实现显示中文
Dec 04 Python
Jupyter notebook设置背景主题,字体大小及自动补全代码的操作
Apr 13 Python
matlab中二维插值函数interp2的使用详解
Apr 22 Python
利用Python的Django框架中的ORM建立查询API
Apr 20 #Python
对于Python的框架中一些会话程序的管理
Apr 20 #Python
介绍Python的Django框架中的QuerySets
Apr 20 #Python
使用Python的Django框架实现事务交易管理的教程
Apr 20 #Python
简化Python的Django框架代码的一些示例
Apr 20 #Python
在Python的Django框架上部署ORM库的教程
Apr 20 #Python
在Heroku云平台上部署Python的Django框架的教程
Apr 20 #Python
You might like
星际争霸任务指南——人族
2020/03/04 星际争霸
PHP 反射机制实现动态代理的代码
2008/10/22 PHP
PHP速成大法
2015/01/30 PHP
php中加密解密DES类的简单使用方法示例
2020/03/26 PHP
JS 两个字符串时间的天数差计算
2013/08/25 Javascript
JavaScript怎么判断图片是否加载完成以便获取其尺寸
2014/05/08 Javascript
js调试工具console.log()方法查看js代码的执行情况
2014/08/08 Javascript
浅谈jquery回调函数callback的使用
2015/01/30 Javascript
基于jquery ui的alert,confirm方案(支持换肤)
2015/04/03 Javascript
JS实现点击上移下移LI行数据的方法
2015/08/05 Javascript
JS实现灵巧的下拉导航效果代码
2015/08/25 Javascript
javascript对象的相关操作小结
2016/05/16 Javascript
JS代码实现根据时间变换页面背景效果
2016/06/16 Javascript
JavaScript轻松创建级联函数的方法示例
2017/02/10 Javascript
微信小程序page的生命周期和音频播放及监听实例详解
2017/04/07 Javascript
Layui数据表格之获取表格中所有的数据方法
2018/08/20 Javascript
从零开始在NPM上发布一个Vue组件的方法步骤
2018/12/20 Javascript
详解puppeteer使用代理
2018/12/27 Javascript
vue下使用nginx刷新页面404的问题解决
2019/08/02 Javascript
Vue-CLI 项目在pycharm中配置方法
2019/08/30 Javascript
微信小程序 wxParse插件显示视频问题
2019/09/27 Javascript
在vue中把含有html标签转为html渲染页面的实例
2019/10/28 Javascript
如何利用JavaScript编写更好的条件语句详解
2020/08/10 Javascript
python代码实现ID3决策树算法
2017/12/20 Python
Python实现按中文排序的方法示例
2018/04/25 Python
python 扩展print打印文件路径和当前时间信息的实例代码
2019/10/11 Python
python图的深度优先和广度优先算法实例分析
2019/10/26 Python
pycharm激活方法到2099年(激活流程)
2020/09/22 Python
selenium框架中driver.close()和driver.quit()关闭浏览器
2020/12/08 Python
Css3+Js制作漂亮时钟(附源码)
2013/04/24 HTML / CSS
浅析border-radius如何兼容IE
2016/04/19 HTML / CSS
VICHY薇姿英国官网:全球专业敏感肌护肤领先品牌
2017/07/04 全球购物
大学生毕业求职自荐书范文
2014/02/04 职场文书
关于工作经历的证明书
2014/10/11 职场文书
2015年母亲节活动总结
2015/02/10 职场文书
浅谈python中的多态
2021/06/15 Python