ChannelsallowsyoutouseWebSocketsandothernon-HTTPprotocolsinyourDjangosite.ForexampleyoumightwanttouseWebSocketstoallowapageonyoursitetoimmediatelyreceiveupdatesfromyourDjangoserverwithoutusingHTTPlong-pollingorotherexpensivetechniques.Channels允许您在Django站点中使用Websockets和其他非HTTP协议。例如,您可能希望Websockets允许网站上的页面立即从Django服务器接收更新,而无需使用HTTP长轮询或其他昂贵的技术。
Inthistutorialwewillbuildasimplechatserver,whereyoucanjoinanonlineroom,postmessagestotheroom,andhaveothersinthesameroomseethosemessagesimmediately.在本教程中,我们将建立一个简单的聊天服务器,在那里你可以加入一个在线房间,张贴消息到房间,并让其他人在同一房间看到这些消息立即。
Inthistutorialwewillbuildasimplechatserver.Itwillhavetwopages:在本教程中,我们将构建一个简单的聊天服务器。它将有两个页面:
TheroomviewwilluseaWebSockettocommunicatewiththeDjangoserverandlistenforanymessagesthatareposted.房间视图将使用WebSocket与Django服务器进行通信,并监听任何发送出来的消息。
WeassumethatyouarefamilarwithbasicconceptsforbuildingaDjangosite.IfnotwerecommendyoucompletetheDjangotutorialfirstandthencomebacktothistutorial.我们假设您熟悉构建Django站点的基本概念。如果不是,我们建议您先完成Django教程,然后再回到本教程。
WeassumethatyouhaveDjangoinstalledalready.YoucantellDjangoisinstalledandwhichversionbyrunningthefollowingcommandinashellprompt(indicatedbythe$prefix):我们假设你已经安装了Django。您可以通过在shell提示符下运行以下命令(用$前缀表示)来查看您安装的Django版本:
$python3-mdjango--versionWealsoassumethatyouhaveChannelsinstalledalready.YoucantellChannelsisinstalledbyrunningthefollowingcommand:我们还假设您已经安装了Channels。您可以通过运行以下命令来查看Channels安装与否:
$python3-c'importchannels;print(channels.__version__)'ThistutorialiswrittenforChannels2.0,whichsupportsPython3.5+andDjango1.11+.IftheChannelsversiondoesnotmatch,youcanrefertothetutorialforyourversionofChannelsbyusingtheversionswitcheratthebottomleftcornerofthispage,orupdateChannelstothenewestversion.本教程是为Channels2.0编写的,它支持Python3.5+和Django1.11+。如果Channels版本不匹配,你可以使用本页左下角的版本切换器,或将Channels更新到最新版本,以参考您的Channels版本的教程。
ThistutorialalsousesDockertoinstallandrunRedis.WeuseRedisasthebackingstoreforthechannellayer,whichisanoptionalcomponentoftheChannelslibrarythatweuseinthetutorial.InstallDockerfromitsofficialwebsite-thereareofficialruntimesforMacOSandWindowsthatmakeiteasytouse,andpackagesformanyLinuxdistributionswhereitcanrunnatively.本教程还使用Docker安装和运行Redis。我们使用Redis作为Channels层的后备存储,它是我们在教程中使用的Channels库的可选组件。从其官方网站安装Docker--有用于MacOS和Windows的易于使用的正式运行版,并为许多Linux发行版提供了可本地运行的软件包。
Note提醒WhileyoucanrunthestandardDjangorunserverwithouttheneedforDocker,thechannelsfeatureswe’llbeusinginlaterpartsofthetutorialwillneedRedistorun,andwerecommendDockerastheeasiestwaytodothis.虽然您可以运行标准的Djangorunserver不需要Docker,我们将使用的channels功能在后面的教程将需要Redis运行,我们建议使用Docker这一最简单的方式来做到这一点。
Ifyoudon’talreadyhaveaDjangoproject,youwillneedtocreateone.如果您还没有Django项目,您将需要创建一个。
Fromthecommandline,cdintoadirectorywhereyou’dliketostoreyourcode,thenrunthefollowingcommand:从命令行,将cd放入要存储代码的目录中,然后运行以下命令:
$django-adminstartprojectmysiteThiswillcreateamysitedirectoryinyourcurrentdirectorywiththefollowingcontents:这将在当前目录中创建一个mysite目录,其中有以下内容:
mysite/manage.pymysite/__init__.pysettings.pyurls.pywsgi.pyCreatingtheChatapp创建聊天应用程序Wewillputthecodeforthechatserverinitsownapp.我们会将聊天服务器的代码放在它自己的应用程序中。
Makesureyou’reinthesamedirectoryasmanage.pyandtypethiscommand:请确保您位于与manage.py相同的目录中.然后输入以下命令:
$python3manage.pystartappchatThat’llcreateadirectorychat,whichislaidoutlikethis:这将创建一个chat文件夹,它是像这样的:
chat/__init__.pyadmin.pyapps.pymigrations/__init__.pymodels.pytests.pyviews.pyForthepurposesofthistutorial,wewillonlybeworkingwithchat/views.pyandchat/__init__.py.Soremoveallotherfilesfromthechatdirectory.为了达到本教程的目的,我们将只使用chat/views.py和chat/__init__.py。因此,从chat目录中删除所有其他文件。
Afterremovingunnecessaryfiles,thechatdirectoryshouldlooklike:删除不必要的文件后,chat目录应如下所示:
chat/__init__.pyviews.pyWeneedtotellourprojectthatthechatappisinstalled.Editthemysite/settings.pyfileandadd'chat'totheINSTALLED_APPSsetting.It’lllooklikethis:我们需要告诉我们的项目chatapp已经安装。编辑mysite/settings.py文件并将'chat'添加到INSTALLED_APPS设置中。它看起来像这样:
#mysite/settings.pyINSTALLED_APPS=['chat','django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles',]Addtheindexview添加index视图Wewillnowcreatethefirstview,anindexviewthatletsyoutypethenameofachatroomtojoin.现在,我们将创建第一个视图,这个index视图允许您输入要加入的聊天室的名称。
Createatemplatesdirectoryinyourchatdirectory.Withinthetemplatesdirectoryyouhavejustcreated,createanotherdirectorycalledchat,andwithinthatcreateafilecalledindex.htmltoholdthetemplatefortheindexview.在chat目录中创建templates目录。在刚刚创建的templates目录中,创建另一个名为chat的目录,并在其中创建一个名为index.html的文件。
Yourchatdirectoryshouldnowlooklike:您的chat目录现在应该看起来像:
chat/__init__.pytemplates/chat/index.htmlviews.pyPutthefollowingcodeinchat/templates/chat/index.html:将下面的代码写进chat/templates/chat/index.html文件中:
#chat/views.pyfromdjango.shortcutsimportrenderdefindex(request):returnrender(request,'chat/index.html',{})Tocalltheview,weneedtomapittoaURL-andforthisweneedaURLconf.为了调用这个视图,我们需要把它映射到一个URL--因此我们需要一个URL配置文件。
TocreateaURLconfinthechatdirectory,createafilecalledurls.py.Yourappdirectoryshouldnowlooklike:为了在chat目录下创建一个URL配置文件,我们需要新建一个名为urls.py的文件。你的app目录应该像现在这样子:
chat/__init__.pytemplates/chat/index.htmlurls.pyviews.pyInthechat/urls.pyfileincludethefollowingcode:在chat/urls.py文件中包含以下代码:
#chat/urls.pyfromdjango.conf.urlsimporturlfrom.importviewsurlpatterns=[url(r'^$',views.index,name='index'),]ThenextstepistopointtherootURLconfatthechat.urlsmodule.Inmysite/urls.py,addanimportfordjango.conf.urls.includeandinsertaninclude()intheurlpatternslist,soyouhave:下一步是将根目录下的URLconf文件指向chat.urls模块。在mysite/urls.py中,导入django.conf.urls.include模块,并在urlpatterns列表中插入一个include()函数,因此您需要写入以下代码:
#mysite/urls.pyfromdjango.conf.urlsimportinclude,urlfromdjango.contribimportadminurlpatterns=[url(r'^chat/',include('chat.urls')),url(r'^admin/',admin.site.urls),]Let’sverifythattheindexviewworks.Runthefollowingcommand:让我们验证index视图是否有效。运行以下命令:
$python3manage.pyrunserverYou’llseethefollowingoutputonthecommandline:您将在命令行中看到以下输出:
GototheterminalwhereyourantherunservercommandandpressControl-Ctostoptheserver.转到运行runserver命令的终端,然后按下Control+C以停止服务器。
Sofarwe’vejustcreatedaregularDjangoapp;wehaven’tusedtheChannelslibraryatall.Nowit’stimetointegrateChannels.到目前为止,我们刚刚创建了一个常规的Django应用程序;我们根本就没有使用Channels库。现在是时候集成Channels库了。
Let’sstartbycreatingarootroutingconfigurationforChannels.AChannelsroutingconfigurationissimilartoaDjangoURLconfinthatittellsChannelswhatcodetorunwhenanHTTPrequestisreceivedbytheChannelsserver.让我们从创建Channels的根路由配置文件开始。Channels路由配置类似于DjangoURLconf,它会告诉Channels当收到由Channels服务器发过来的HTTP请求时,应该执行什么代码。
We’llstartwithanemptyroutingconfiguration.Createafilemysite/routing.pyandincludethefollowingcode:我们将从一个空的路由配置文件开始。创建文件mysite/routing.py,并写入以下代码:
#mysite/settings.pyINSTALLED_APPS=['channels','chat','django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles',]You’llalsoneedtopointChannelsattherootroutingconfiguration.Editthemysite/settings.pyfileagainandaddthefollowingtothebottomofit:您同样需要在根路由配置中指向Channels。再次编辑mysite/settings.py文件,并将以下内容添加到底部:
#mysite/settings.py#ChannelsASGI_APPLICATION='mysite.routing.application'WithChannelsnowintheinstalledapps,itwilltakecontroloftherunservercommand,replacingthestandardDjangodevelopmentserverwiththeChannelsdevelopmentserver.现在已安装的应用程序中有Channels,它将控制runserver命令,用Channels开发服务器替换标准的Django开发服务器。
Note提醒
TheChannelsdevelopmentserverwillconflictwithanyotherthird-partyappsthatrequireanoverloadedorreplacementrunservercommand.Anexampleofsuchaconflictiswithwhitenoise.runserver_nostaticfromwhitenoise.Inordertosolvesuchissues,trymovingchannelstothetopofyourINSTALLED_APPSorremovetheoffendingappaltogether.Channels开发服务器将与需要重载或替换runserver命令的任何其他第三方应用程序冲突。whitenoise中的whitenoise.runserver_nostatic是一个冲突的例子。为了解决这些问题,请尝试将Channels移动到您的INSTALLED_APPS的顶部,或者完全删除与其发生冲突的应用程序。
Let’sensurethattheChannelsdevelopmentserverisworkingcorrectly.Runthefollowingcommand:让我们确保Channels开发服务器工作正常。运行以下命令:
Ignorethewarningaboutunapplieddatabasemigrations.Wewon’tbeusingadatabaseinthistutorial.忽略有关未应用数据库迁移的警告。我们将不会在本教程中使用数据库。
ThistutorialbeginswhereTutorial1leftoff.We’llgettheroompageworkingsothatyoucanchatwithyourselfandothersinthesameroom.本教程在教程1的基础上开始。我们会让房间页面工作,这样你可以和你自己或者其他人在同一个房间里聊天。
Wewillnowcreatethesecondview,aroomviewthatletsyouseemessagespostedinaparticularchatroom.现在,我们将创建第二个视图,即一个允许您查看在特定聊天室中发布消息的房间视图。
Createanewfilechat/templates/chat/room.html.Yourappdirectoryshouldnowlooklike:创建新的文件chat/templates/chat/room.html。您的应用程序目录现在应该看起来像:
chat/__init__.pytemplates/chat/index.htmlroom.htmlurls.pyviews.pyCreatetheviewtemplatefortheroomviewinchat/templates/chat/room.html:在chat/templates/chat/room.html中填入一下代码:
#chat/views.pyfromdjango.shortcutsimportrenderfromdjango.utils.safestringimportmark_safeimportjsondefindex(request):returnrender(request,'chat/index.html',{})defroom(request,room_name):returnrender(request,'chat/room.html',{'room_name_json':mark_safe(json.dumps(room_name))})Createtheroutefortheroomviewinchat/urls.py:在chat/urls.py中创建房间视图的路由:
#chat/urls.pyfromdjango.conf.urlsimporturlfrom.importviewsurlpatterns=[url(r'^$',views.index,name='index'),url(r'^(P
Typethemessage“hello”andpressenter.Nothinghappens.Inparticularthemessagedoesnotappearinthechatlog.Why键入消息"hello",然后按enter键。什么也没有发生。尤其是,消息并不会出现在聊天日志中。为什么?
TheroomviewistryingtoopenaWebSockettotheURLws://127.0.0.1:8000/ws/chat/lobby/butwehaven’tcreatedaconsumerthatacceptsWebSocketconnectionsyet.Ifyouopenyourbrowser’sJavaScriptconsole,youshouldseeanerrorthatlookslike:房间视图试图打开一个WebSocket连接到URLws://127.0.0.1:8000/ws/chat/lobby/,但我们还没有创建一个接受WebSocket连接的consumer。如果打开浏览器的JavaScript控制台,您应该会看到如下所示的错误:
WebSocketconnectionto'ws://127.0.0.1:8000/ws/chat/lobby/'failed:Unexpectedresponsecode:500Writeyourfirstconsumer编写您的第一个用户WhenDjangoacceptsanHTTPrequest,itconsultstherootURLconftolookupaviewfunction,andthencallstheviewfunctiontohandletherequest.Similarly,whenChannelsacceptsaWebSocketconnection,itconsultstherootroutingconfigurationtolookupaconsumer,andthencallsvariousfunctionsontheconsumertohandleeventsfromtheconnection.当Django接受HTTP请求时,它会根据根URLconf以查找视图函数,然后调用视图函数来处理请求。同样,当Channels接受WebSocket连接时,它会根据根路由配置以查找对应的consumer,然后调用consumer上的各种函数来处理来自这个连接的事件。
WewillwriteabasicconsumerthatacceptsWebSocketconnectionsonthepath/ws/chat/ROOM_NAME/thattakesanymessageitreceivesontheWebSocketandechositbacktothesameWebSocket.我们将编写一个简单的consumer,它会在路径/ws/chat/ROOM_NAME/接收WebSocket连接,然后把接收任意的消息,回送给同一个WebSocket连接。
Itisgoodpracticetouseacommonpathprefixlike/ws/todistinguishWebSocketconnectionsfromordinaryHTTPconnectionsbecauseitwillmakedeployingChannelstoaproductionenvironmentincertainconfigurationseasier.使用常见的路径前缀(如/ws)来区分WebSocket连接与普通HTTP连接是很好的做法,因为它将使在某些配置中部署Channels更容易。
Inparticularforlargesitesitwillbepossibletoconfigureaproduction-gradeHTTPserverlikenginxtorouterequestsbasedonpathtoeitheraproduction-gradeWSGIserverlikeGunicorn+DjangoforordinaryHTTPrequestsoraproduction-gradeASGIserverlikeDaphne+ChannelsforWebSocketrequests.特别是大型网站,它们很有可能配置像nginx这样的生产级别HTTP服务器,根据路径将请求发送到生产级别的WSGI服务器,例如用于处理普通HTTP请求的Gunicorn+Django,或生产级别的ASGI服务器,例如用于处理WebSocket请求的Daphne+Channels。
NotethatforsmallersitesyoucanuseasimplerdeploymentstrategywhereDaphneservesallrequests-HTTPandWebSocket-ratherthanhavingaseparateWSGIserver.Inthisdeploymentconfigurationnocommonpathprefixlikeis/ws/isnecessary.请注意,对于较小的站点,您可以使用更简单的部署策略,其中Daphne服务器处理所有的请求--HTTP和WebSocket--而不是单独的WSGI服务器。在这种部署配置策略中,不需要使用/ws/这样的通用路径前缀。
Createanewfilechat/consumers.py.Yourappdirectoryshouldnowlooklike:创建新文件chat/consumers.py。您的应用程序目录现在应该看起来像:
chat/__init__.pyconsumers.pytemplates/chat/index.htmlroom.htmlurls.pyviews.pyPutthefollowingcodeinchat/consumers.py:在chat/consumers.py中写入以下代码:
#chat/consumers.pyfromchannels.generic.websocketimportWebsocketConsumerimportjsonclassChatConsumer(WebsocketConsumer):defconnect(self):self.accept()defdisconnect(self,close_code):passdefreceive(self,text_data):text_data_json=json.loads(text_data)message=text_data_json['message']self.send(text_data=json.dumps({'message':message}))ThisisasynchronousWebSocketconsumerthatacceptsallconnections,receivesmessagesfromitsclient,andechosthosemessagesbacktothesameclient.Fornowitdoesnotbroadcastmessagestootherclientsinthesameroom.这是一个同步WebSocketconsumer,它接受所有连接,接收来自其客户端的消息,并将这些消息回送到同一客户端。现在,它不向同一个房间的其他客户端广播消息。
Channelsalsosupportswritingasynchronousconsumersforgreaterperformance.Howeveranyasynchronousconsumermustbecarefultoavoiddirectlyperformingblockingoperations,suchasaccessingaDjangomodel.SeetheConsumersreferenceformoreinformationaboutwritingasynchronousconsumers.Channels还支持编写异步consumers以提高性能。但是,任何异步consumers都必须小心,避免直接执行阻塞操作,例如访问Django的model。有关编写异步consumers的详细信息,请参阅Consumers。
Weneedtocreatearoutingconfigurationforthechatappthathasaroutetotheconsumer.Createanewfilechat/routing.py.Yourappdirectoryshouldnowlooklike:我们需要为chatapp创建一个路由配置,它有一个通往consumer的路由。创建新文件chat/routing.py。您的应用程序目录现在应该看起来像:
chat/__init__.pyconsumers.pyrouting.pytemplates/chat/index.htmlroom.htmlurls.pyviews.pyPutthefollowingcodeinchat/routing.py:在chat/routing.py中输入以下代码:
#chat/routing.pyfromdjango.conf.urlsimporturlfrom.importconsumerswebsocket_urlpatterns=[url(r'^ws/chat/(P
TheAuthMiddlewareStackwillpopulatetheconnection’sscopewithareferencetothecurrentlyauthenticateduser,similartohowDjango’sAuthenticationMiddlewarepopulatestherequestobjectofaviewfunctionwiththecurrentlyauthenticateduser.(Scopeswillbediscussedlaterinthistutorial.)ThentheconnectionwillbegiventotheURLRouter.AuthMiddlewareStack将使用对当前经过身份验证的用户的引用来填充连接的scope,类似于Django的AuthenticationMiddleware用当前经过身份验证的用户填充视图函数的请求对象。(Scopes将在本教程后面讨论。)然后连接将被给到URLRouter。
TheURLRouterwillexaminetheHTTPpathoftheconnectiontorouteittoaparticularconsumer,basedontheprovidedurlpatterns.根据提供的url模式,URLRouter将检查连接的HTTP路径,以将其路由指定到到特定的consumer。
Let’sverifythattheconsumerforthe/ws/chat/ROOM_NAME/pathworks.StarttheChannelsdevelopmentserver:让我们验证consumer的/ws/chat/ROOM_NAME/路径是否工作。启动Channels开发服务器:
Typethemessage“hello”andpressenter.Youshouldnowsee“hello”echoedinthechatlog.输入消息"hello",然后按enter键。您现在应该看到"hello"在聊天日志中显示。
Achannellayerisakindofcommunicationsystem.Itallowsmultipleconsumerinstancestotalkwitheachother,andwithotherpartsofDjango.channellayer是一种通信系统。它允许多个consumer实例互相交谈,以及与Django的其他部分进行通信。
Achannellayerprovidesthefollowingabstractions:channellayer提供以下抽象:
Everyconsumerinstancehasanautomaticallygenerateduniquechannelname,andsocanbecommunicatedwithviaachannellayer.每个consumer实例都有一个自动生成的唯一的channel名称,因此可以通过channellayer进行通信。
InourchatapplicationwewanttohavemultipleinstancesofChatConsumerinthesameroomcommunicatewitheachother.TodothatwewillhaveeachChatConsumeradditschanneltoagroupwhosenameisbasedontheroomname.ThatwillallowChatConsumerstotransmitmessagestoallotherChatConsumersinthesameroom.在我们的聊天应用程序中,我们希望在同一房间中有多个ChatConsumer的实例相互通信。要做到这一点,我们将有每个ChatConsumer添加它的channel到一个group,其名称是基于房间的名称。这将允许ChatConsumers将消息传输到同一个房间中的所有其他ChatConsumers。
WewilluseachannellayerthatusesRedisasitsbackingstore.TostartaRedisserveronport6379,runthefollowingcommand:我们将使用一个channellayer,使用Redis作为其后备存储。要在端口6379上启动Redis服务器,请运行以下命令:
$dockerrun-p6379:6379-dredis:2.8Weneedtoinstallchannels_redissothatChannelsknowshowtointerfacewithRedis.Runthefollowingcommand:我们需要安装channels_redis,以便Channels知道如何调用redis。运行以下命令:
$pip3installchannels_redisBeforewecanuseachannellayer,wemustconfigureit.Editthemysite/settings.pyfileandaddaCHANNEL_LAYERSsettingtothebottom.Itshouldlooklike:在使用channellayer之前,必须对其进行配置。编辑mysite/settings.py文件并将CHANNEL_LAYERS设置添加到底部。它应该看起来像:
#mysite/settings.py#ChannelsASGI_APPLICATION='mysite.routing.application'CHANNEL_LAYERS={'default':{'BACKEND':'channels_redis.core.RedisChannelLayer','CONFIG':{"hosts":[('127.0.0.1',6379)],},},}Note提醒
Itispossibletohavemultiplechannellayersconfigured.Howevermostprojectswilljustuseasingle'default'channellayer.可以配置多个channellayer。然而,大多数项目只使用一个"默认"的channellayer。
Let’smakesurethatthechannellayercancommunicatewithRedis.OpenaDjangoshellandrunthefollowingcommands:让我们确保channellayer可以与Redis通信。打开Djangoshell并运行以下命令:
$python3manage.pyshell>>>importchannels.layers>>>channel_layer=channels.layers.get_channel_layer()>>>fromasgiref.syncimportasync_to_sync>>>async_to_sync(channel_layer.send)('test_channel',{'type':'hello'})>>>async_to_sync(channel_layer.receive)('test_channel'){'type':'hello'}TypeControl-DtoexittheDjangoshell.输入Control+D退出Djangoshell。
Nowthatwehaveachannellayer,let’suseitinChatConsumer.Putthefollowingcodeinchat/consumers.py,replacingtheoldcode:现在我们有了一个channellayer,让我们在ChatConsumer中使用它。将以下代码放在chat/consumers.py中,替换旧代码:
SeveralpartsofthenewChatConsumercodedeservefurtherexplanation:新的ChatConsumer代码中有几个部分需要进一步解释:
Let’sverifythatthenewconsumerforthe/ws/chat/ROOM_NAME/pathworks.TostarttheChannelsdevelopmentserver,runthefollowingcommand:让我们验证新consumer的/ws/chat/ROOM_NAME/路径是否工作。要启动Channels开发服务器,请运行以下命令:
Inthesecondbrowsertab,typethemessage“hello”andpressenter.Youshouldnowsee“hello”echoedinthechatloginboththesecondbrowsertabandinthefirstbrowsertab.在第二个浏览器选项卡中,输入消息"hello",然后按enter键。在第二个浏览器选项卡和第一个浏览器选项卡中,您现在应该看到"hello"在聊天日志中显示。
Younowhaveabasicfully-functionalchatserver!您现在有一个基本的功能齐全的聊天服务器!
ThistutorialbeginswhereTutorial2leftoff.We’llrewritetheconsumercodetobeasynchronousratherthansynchronoustoimproveitsperformance.本教程在教程2的基础上开始。我们将重写consumer代码使其变成是异步的而不是同步的,以提高其性能。
TheChatConsumerthatwehavewritteniscurrentlysynchronous.SynchronousconsumersareconvenientbecausetheycancallregularsynchronousI/OfunctionssuchasthosethataccessDjangomodelswithoutwritingspecialcode.Howeverasynchronousconsumerscanprovideahigherlevelofperformancesincetheydon’tneedcreateadditionalthreadswhenhandlingrequests.我们编写的ChatConsumer当前是同步的。同步的consumers很方便,因为它们可以调用常规的同步I/O函数,例如访问Djangomodels而不用编写特殊的代码。但是,异步的consumers可以提供更高级别的性能,因为它们在处理请求时不需要创建其他线程。
ChatConsumeronlyusesasync-nativelibraries(Channelsandthechannellayer)andinparticularitdoesnotaccesssynchronousDjangomodels.Thereforeitcanberewrittentobeasynchronouswithoutcomplications.ChatConsumer只使用async-native库(Channels和channellayer),特别是它不访问同步的Djangomodels。因此,它可以被改写为异步的而不会变得复杂化。
EvenifChatConsumerdidaccessDjangomodelsorothersynchronouscodeitwouldstillbepossibletorewriteitasasynchronous.Utilitieslikeasgiref.sync.sync_to_asyncandchannels.db.database_sync_to_asynccanbeusedtocallsynchronouscodefromanasynchronousconsumer.Theperformancegainshoweverwouldbelessthanifitonlyusedasync-nativelibraries.即使ChatConsumer访问Djangomodels或其他同步的代码,它仍然可以将其重写为异步的。像asgiref.sync.sync_to_async和channels.db.database_sync_to_async这样的实用工具可以用来从异步consumer那里调用同步的代码。但是,性能增益将小于仅使用async-native库的方式。
Let’srewriteChatConsumertobeasynchronous.Putthefollowingcodeinchat/consumers.py:让我们重写ChatConsumer使其变为异步的。在chat/consumers.py中输入以下代码:
Let’sverifythattheconsumerforthe/ws/chat/ROOM_NAME/pathstillworks.TostarttheChannelsdevelopmentserver,runthefollowingcommand:让我们验证consumer的/ws/chat/ROOM_NAME/路径是否仍然有效。启动Channels开发服务器,运行以下命令:
Nowyourchatserverisfullyasynchronous!现在,您的聊天服务器是完全异步的了!
ThistutorialbeginswhereTutorial3leftoff.We’vebuiltasimplechatserverandnowwe’llcreatesomeautomatedtestsforit.本教程在教程3的基础上开始。我们已经建立了一个简单的聊天服务器,现在我们将为它创建一些自动化测试。
Toensurethatthechatserverkeepsworking,wewillwritesometests.为了确保聊天服务器能够继续工作,我们将编写一些测试。
Wewillwriteasuiteofend-to-endtestsusingSeleniumtocontrolaChromewebbrowser.Thesetestswillensurethat:我们将编写一套端到端的测试,使用Selenium来控制Chromeweb浏览器。这些测试将确保:
InstalltheChromewebbrowser,ifyoudonotalreadyhaveit.如果您尚未拥有Chromeweb浏览器,请安装它。
Installchromedriver.安装chromedriver。
InstallSelenium.Runthefollowingcommand:安装Selenium。运行以下命令:
$pip3installseleniumCreateanewfilechat/tests.py.Yourappdirectoryshouldnowlooklike:创建新的文件chat/tests.py。您的应用程序目录现在应该看起来像:
chat/__init__.pyconsumers.pyrouting.pytemplates/chat/index.htmlroom.htmltests.pyurls.pyviews.pyPutthefollowingcodeinchat/tests.py:在chat/tests.py中输入以下代码:
Torunthetests,runthefollowingcommand:要运行测试,请运行以下命令:
$python3manage.pytestchat.testsYoushouldseeoutputthatlookslike:您应该看到如下所示的输出:
Creatingtestdatabaseforalias'default'...Systemcheckidentifiednoissues(0silenced)...----------------------------------------------------------------------Ran2testsin5.014sOKDestroyingtestdatabaseforalias'default'...Younowhaveatestedchatserver!你现在有一个经过测试的聊天服务器了!
What’snext接下来应该做什么呢?
Congratulations!You’vefullyimplementedachatserver,madeitperformantbywritingitinasynchronousstyle,andwrittenautomatedteststoensureitwon’tbreak.祝贺!您已经完全实现了一个聊天服务器,通过在异步样式中编写它来高性能,并编写了自动测试以确保它不会中断。
Thisistheendofthetutorial.AtthispointyoushouldknowenoughtostartanappofyourownthatusesChannelsandstartfoolingaround.Asyouneedtolearnnewtricks,comebacktorestofthedocumentation.这是教程的结尾。现在,你应该清楚地知道如何启动一个使用了Channels的你自己的应用程序和做其他的操作。当您需要学习新的技巧时,请回到文档的其余部分。