使用Node.js和Socket.IO擴(kuò)展Django的實(shí)時(shí)處理功能
今天,我們的目標(biāo)是使用Django,Redis,和Socket.IO建立一個(gè)實(shí)時(shí)的聊天室。雖然幾乎所有的Web應(yīng)用程序都可以建立一個(gè)聊天室的。這篇文章將以較高的水平告訴你如何將基于REST的應(yīng)用程序轉(zhuǎn)換成一個(gè)實(shí)時(shí)的Web應(yīng)用程序的。我會(huì)使用Django創(chuàng)建REST的部分,實(shí)際上自由地使用任何你舒服的語(yǔ)言/框架均可。接下來(lái),讓我們跳進(jìn)代碼,先列舉我們所需要的部分。
組成:
- Django 1.4+
- Redis 2.6.x (版本可選,但是建議使用)
- Redis-py 2.7.x (僅當(dāng)你使用Redis時(shí)需要)
- Node.js v0.8.x
- Socket.IO v0.9.x
- Cookie v0.0.5
- 數(shù)據(jù)庫(kù)、sqlite、其他你覺(jué)得類似數(shù)據(jù)庫(kù)形式的 均可
你的使用的版本可能與我不同,我暫時(shí)未測(cè)試其他版本,全部使用當(dāng)前最新穩(wěn)定版本。如果你無(wú)法通過(guò)下面方法安裝,我已經(jīng)編譯好Ubuntu的軟件包。你可以從評(píng)論中得到其他操作系統(tǒng)版本情況。
#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
執(zhí)行完以上的代碼,django project就配置好了,接下來(lái)要做的是在settings文件中設(shè)置數(shù)據(jù)庫(kù)。先創(chuàng)建一個(gè)空白數(shù)據(jù)庫(kù)。(這是一個(gè)settings file的例子。在我的app中添加了一個(gè)“core”然后配置templates和urls的路徑。你可以隨意更改settings中的配置信息,但是要與你的app相對(duì)應(yīng)。
Model
models很簡(jiǎn)單,我們將要建一個(gè)包含user和text的表。如果你想讓他更復(fù)雜一些,可以添加chatroom等信息。(為了簡(jiǎn)單起見,這里只寫了兩個(gè))
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,接下來(lái)執(zhí)行下面的syncdb代碼(第一行代碼),創(chuàng)建數(shù)據(jù)庫(kù)。然后創(chuàng)建幾個(gè)user來(lái)測(cè)試。(第二行代碼)
python manage.py syncdb python manage.py createsuperuser Node Server With Socket.IO
這一部分將要介紹實(shí)時(shí)信息的發(fā)送和獲取。使用Node.js創(chuàng)建一個(gè)依賴Socket.IO的app server,使用Redis 來(lái)做這項(xiàng)苦差事。在nodejs字典中,創(chuàng)建一個(gè)叫做“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來(lái)存儲(chǔ)Django設(shè)置的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發(fā)送到客戶端 sub.on('message', function(channel, message){ socket.send(message); }); //客戶端通過(guò)socket.io發(fā)送消息 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發(fā)消息 var req = http.get(options, function(res){ res.setEncoding('utf8'); //輸出錯(cuò)誤信息 res.on('data', function(message){ if(message != 'Everything worked :)'){ console.log('Message: ' + message); } }); }); req.write(values); req.end(); }); });
上次我們?cè)O(shè)置了Socket.IO能在本地領(lǐng)域使用cookie的那個(gè)Django設(shè)置,這能讓我們通過(guò)socket.handshake.cookie去訪問(wèn)cookie數(shù)據(jù)。能讓我們?cè)鯓拥玫接脩舻膕ession會(huì)話。
我們?cè)O(shè)置Socket.IO的cookies之后我們才能持有很多事件,第一個(gè)事件是Redis 發(fā)布通道,當(dāng)我們的用戶注意到一個(gè)新的消息已經(jīng)被通知它將發(fā)送消息給所有站點(diǎn)的客戶端。
另一個(gè)事件是當(dāng)客戶端通過(guò)Socket.IO發(fā)送一個(gè)信息,我們使用字符串查詢(queryString)模塊去創(chuàng)建一個(gè)query查詢才能被發(fā)送到我們的Django服務(wù)。我們的Django服務(wù)在本地端口3000將會(huì)運(yùn)行但你能改變了那個(gè)需求。路徑設(shè)置成/node_api那個(gè)URL我們將不久創(chuàng)建在Django旁邊。一旦我們發(fā)送queryString我們等待的Django就會(huì)保存相關(guān)組件并給我們返回"Everything worked(都在工作)"。如果我們沒(méi)有得到返回給我們的輸出錯(cuò)誤就關(guān)閉節(jié)點(diǎn)控制臺(tái)
一個(gè)關(guān)于不使用Redis的節(jié)點(diǎn)
你真的完全沒(méi)必要為這項(xiàng)目使用Redis,我發(fā)現(xiàn)它將是一個(gè)好的學(xué)習(xí)體驗(yàn),如果你想分流Redis你可以創(chuàng)建一個(gè)通道,使用表達(dá)式或一些其它類庫(kù),在這上面的代碼會(huì)從Django里接收一個(gè)消息當(dāng)一個(gè)注釋被保存時(shí),然后你能通過(guò)Socket.IO添加注釋給所有的客戶端
模板
這就是我們所有HTML和javascript被放置的地方,它允許我們顯示注釋和交互我們的Node服務(wù)
<!DOCTYPE html> <html> <head> <title>Realtime Django</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <script src="http://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連接我們的節(jié)點(diǎn)服務(wù)。當(dāng)從服務(wù)器得到了一個(gè)信息我們就在目錄和添加它到我們注釋列表里做了些轉(zhuǎn)義,當(dāng)我們想要發(fā)送一個(gè)信息我們就對(duì)輸入盒子里做了相應(yīng)的13(按下一個(gè)鍵)的按鍵檢查。一旦那被按下后我們就發(fā)出信息給服務(wù)器使其被持有。一旦它被Django保存到我們的數(shù)據(jù)庫(kù)我們就得到一個(gè)"message"事件將其添加到我們的會(huì)話列表里
我們的Django顯示我們?cè)谙乱徊綄⒓虞d一個(gè)"comments"變量,因此我們那樣設(shè)置并遍歷下面所有的循環(huán)。這部分僅僅是當(dāng)頁(yè)面初始加載時(shí)使用了,我們的javascript將添加數(shù)據(jù)給這個(gè)目錄作為一個(gè)新的數(shù)據(jù)來(lái)自我們的Node服務(wù)
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: #通過(guò)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) #創(chuàng)建Comment Comments.objects.create(user=user, text=request.POST.get('comment')) #創(chuàng)建后就把它發(fā)送到聊天室 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))
讓我們看看這里發(fā)生了什么。home是一個(gè)標(biāo)準(zhǔn)的view文件。使用select_related來(lái)獲得每一個(gè)comment的username,而不是在頁(yè)面第一次加載的時(shí)候,就返回一個(gè)comment的query集合。
第二個(gè)就是我們Node app發(fā)送信息的view。我們從POST中獲取sessionid,然后通過(guò)解碼獲得userid。確定user存在后,就可以創(chuàng)建comment了?,F(xiàn)在吧username 和 comment 發(fā)送到 Redis server。最后,把數(shù)據(jù)發(fā)送到這里叫做"chat"的頻道。
URLs
這里比較簡(jiǎn)單,因?yàn)槲覀儗⒁褂肈jango自帶的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創(chuàng)建、加入聊天室。你也可以使用PHP或者Rails開發(fā)。
如果你有什么問(wèn)題,請(qǐng)?jiān)谠u(píng)論處寫下或聯(lián)系我。
- Go語(yǔ)言中利用http發(fā)起Get和Post請(qǐng)求的方法示例
- 利用dep代替go get獲取私有庫(kù)的方法教程
- Django objects.all()、objects.get()與objects.filter()之間的區(qū)別介紹
- Go語(yǔ)言Web編程實(shí)現(xiàn)Get和Post請(qǐng)求發(fā)送與解析的方法詳解
- Go語(yǔ)言服務(wù)器開發(fā)實(shí)現(xiàn)最簡(jiǎn)單HTTP的GET與POST接口
- $_GET[''goods_id'']+0 的使用詳解
- 利用Go語(yǔ)言搭建WebSocket服務(wù)端方法示例
- go的websocket實(shí)現(xiàn)原理與用法詳解
- golang基于websocket實(shí)現(xiàn)的簡(jiǎn)易聊天室程序
- Go語(yǔ)言基于Socket編寫服務(wù)器端與客戶端通信的實(shí)例
- Go get命令使用socket代理的方法
相關(guān)文章
python實(shí)現(xiàn)zencart產(chǎn)品數(shù)據(jù)導(dǎo)入到magento(python導(dǎo)入數(shù)據(jù))
這篇文章主要介紹了python實(shí)現(xiàn)zencart產(chǎn)品數(shù)據(jù)導(dǎo)入到magento(python導(dǎo)入數(shù)據(jù)),需要的朋友可以參考下2014-04-04pycharm配置python環(huán)境的詳細(xì)圖文教程
PyCharm是一款功能強(qiáng)大的Python編輯器,具有跨平臺(tái)性,下面這篇文章主要給大家介紹了關(guān)于pycharm配置python環(huán)境的詳細(xì)圖文教程,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-01-01Python中的time模塊與datetime模塊用法總結(jié)
Python中內(nèi)置的各項(xiàng)時(shí)間日期函數(shù)幾乎都來(lái)自于time和datetime這兩個(gè)模塊,下面整理了Python中的time模塊與datetime模塊用法總結(jié),需要的朋友可以參考下2016-06-06python3?cookbook解壓可迭代對(duì)象賦值給多個(gè)變量的問(wèn)題及解決方案
這篇文章主要介紹了python3?cookbook-解壓可迭代對(duì)象賦值給多個(gè)變量,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-01-01Python使用BeautifulSoup爬取網(wǎng)頁(yè)數(shù)據(jù)的操作步驟
在網(wǎng)絡(luò)時(shí)代,數(shù)據(jù)是最寶貴的資源之一,而爬蟲技術(shù)就是一種獲取數(shù)據(jù)的重要手段,Python 作為一門高效、易學(xué)、易用的編程語(yǔ)言,自然成為了爬蟲技術(shù)的首選語(yǔ)言之一,本文將介紹如何使用 BeautifulSoup 爬取網(wǎng)頁(yè)數(shù)據(jù),并提供詳細(xì)的代碼和注釋,幫助讀者快速上手2023-11-11pandas中read_sql使用參數(shù)進(jìn)行數(shù)據(jù)查詢的實(shí)現(xiàn)
本文主要介紹了pandas中read_sql使用參數(shù)進(jìn)行數(shù)據(jù)查詢的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06