📄 mmpdev-article.xhtml
字号:
<span class="py-src-comment"># Remote method calls that invoke a database connection and wait until the</span><span class="py-src-comment"># database is done returning its query before turning the query into a string</span><span class="py-src-comment"># and then sending the string as the result of the remote method call.</span><span class="py-src-keyword">class</span><a name="MyClass"><span class="py-src-identifier"> MyClass</span></a>(Referenceable): <span class="py-src-keyword">def</span><a name="remote_doIt"><span class="py-src-identifier"> remote_doIt</span></a>(self): <span class="py-src-keyword">return</span> self.databaseConnector.runQuery( <span class="py-src-string">"select * from foo"</span>).addCallback(str)</pre><div class="py-caption">Deferred Examples - <span class="py-filename">defer-examples.py</span></div></div><p>Now that you've seen a basic introduction, let's look a little deeper intowhat makes that <code>callRemote</code> work.</p><h2>High Level Network Services: Perspective Broker</h2><p>One of the most powerful tools that Twisted provides to the networkapplication developer is its own multi-purpose remote method invocationprotocol, Perspective Broker, affectionately known as PB. PB was designed fromthe ground up with several integration-minded goals.<ul> <li>PB operates over a single transport connection, allowing both client/server (untrusted, widely distributed) and server/server (trusted, locally clustered) communication. This can reduce the total amount of communication code your system needs.</li> <li>PB is highly customizable, and provides hooks at all three layers of decoding: (un)marshalling, (de)serializing, and message routing. This allows PB to remain very generic and compatible, but still be very responsive to the needs of your specific application.</li> <li>PB provides object-to-object, not process-to-process communication. This allows multiple services to be transparently multiplexed over one connection, which in turn allows independent functionality to be integrated into existing servers and clients.</li> <li>PB uses dynamic method lookup and method routing. This allows upgrade transitions from one client/server version to the next to be smooth; your choice of how long to maintain support for legacy clients is not made by fragile technology. </li> <li>PB provides for secure, robust authentication at the protocol level. </li></ul></p><p>Many of these features are interesting and useful, but most critical is theability to integrate independently-developed services over the same networkconnection without time-consuming integration work on disparate code-bases.This is useful both externally -- making use of code available from third-partyvendors and volunteers -- as well as internally. Using PB or a technology likeit will allow you to parallelize your server-side development process, byallowing teams working on abstractly un-related features like <q>chat</q> and<q>inventory</q> to be completely independent of each other. </p><p>Authentication, too, is an important part of this puzzle. Regardless of thetype of service being accessed, the user will need to be authenticated withsome service in order to verify that they have the authority to use thatservice.</p><p>Twisted uses its authentication model, <code>twisted.cred</code>, to unifyservices in Perspective Broker.</p><h3>An Interlude with <code>twisted.cred</code></h3><p>Twisted Cred breaks down authentication into four major abstractions:<code>twisted.cred.service.Service</code>,<code>twisted.cred.perspective.Perspective</code>,<code>twisted.cred.authorizer.Authorizer</code>, and<code>twisted.cred.identity.Identity</code>. The first two are specific toyour application, and the second two are specific to authorization.</p><p>A <code>Service</code> is an over-arching abstraction that represents thewhole service you want to provide. This object performs a role similar to theManager design pattern, for whatever objects need to be <q>globally</q> trackedin a particular subsystem. A <code>Service</code> should contain all the staterelated to one logical service. In the average application, you will end upwriting at least one subclass of <code>Service</code>, and probably two orthree.</p><p>The primary objects that your <code>Service</code> will be managing are<code>Perspective</code> instances. For each <code>Service</code> subclassthere will generally be a corresponding <code>Perspective</code> subclass, andthe <code>Service</code> will only manage <code>Perspective</code> instancesthat it instantiated itself. A <code>Perspective</code> represents the stateof a user with respect to a given Service. It is the contact point between theuser's persona and the functionality that they are working with in the<code>Service</code>. Each <code>Perspective</code> has a name.</p><p>The object which manages authentication is an Authorizer. In the averageTwisted application, you will not need to implement an Authorizer, because youcan use an existing one. You can authenticate out of a file, a database, or atable in memory. Depending on where you want to store your authentication, itmay be necessary to implement your own Authorizer to load and save accountinformation.</p><p>An Authorizer is essentially a persistent collection of Identity instances.Each Identity generally represents one real person who has access to the gamesystem. The only reason you would ever need to implement a new variety ofIdentity is to implement a new style of authorization. Twisted comes withIdentity implementations that can use plain password and challenge-responseauthentication. A secure-shell public-key cryptographic authorizationmechanism is also implemented for use with secure shell clients. An Identitymaintains not only the credentials that the user must present, but informationabout what Perspective instances that user has access to, once validated.</p><p><code>twisted.cred</code> interfaces very tightly with PerspectiveBroker.</p><p>The examples here will be covering the use of built in authenticationmechanisms, and creating new services. Let's take some of these basic buildingblocks and explore an example of their use in a real-world situation.</p><h3>A Simple Example: Fritz and Franz go to the Psychologist</h3><p>In our initial <q>Day in the Life</q> for <u>Metamorphosis Online</u>, twoplayers, Joe and Bob, log in to play their characters, Franz and Fritz. Franzand Fritz need to meet each other for group therapy at the psychologist'soffice. </p><p>In this example, Franz and Fritz first coordinate with each other by talkingabout the appointment on their cell phones, then negotiating the game world'shazards and finally arriving on time to the office.</p><p>While the game simulation is difficult to factor into separate Services, thenotion of globally addressing the player (with all the attendant concerns forfeature-completeness, like logging, auditing, message throttling, obscenityfiltering, and so on) is definitely separable.</p><p>For this example we will be implementing our own simple <q>cell phone</q>based chat system. In a more realistic situation it would probably beadvisable to use the integrated <code>twisted.words</code> chat package andattendant utilities.</p><h4> About the Code Listings</h4><p>The following code examples are each meant to be in one source file. Theselistings, plus a Twisted installation, should make a complete (although small)application.</p><div class="py-listing"><pre class="python"><span class="py-src-comment"># cell phone service</span><span class="py-src-keyword">from</span> twisted.spread.pb <span class="py-src-keyword">import</span> Service, Perspective<span class="py-src-keyword">class</span><a name="Cellphone"><span class="py-src-identifier"> Cellphone</span></a>(Perspective): <span class="py-src-keyword">def</span><a name="attached"><span class="py-src-identifier"> attached</span></a>(self, remoteEar, identity): self.remoteEar = remoteEar self.caller = None self.talkingTo = None <span class="py-src-keyword">return</span> Perspective.attached(self, remoteEar, identity) <span class="py-src-keyword">def</span><a name="detached"><span class="py-src-identifier"> detached</span></a>(self, remoteEar, identity): <span class="py-src-keyword">del</span> self.remoteEar self.caller = None self.talkingTo = None <span class="py-src-keyword">return</span> Perspective.detached(self, remoteEar, identity) <span class="py-src-keyword">def</span><a name="hear"><span class="py-src-identifier"> hear</span></a>(self, text): self.remoteEar.callRemote(<span class="py-src-string">'hear'</span>, text) <span class="py-src-keyword">def</span><a name="perspective_dial"><span class="py-src-identifier"> perspective_dial</span></a>(self, phoneNumber): otherPhone = self.service.getPerspectiveNamed(phoneNumber) otherPhone.ring(self) callerID = True <span class="py-src-keyword">def</span><a name="ring"><span class="py-src-identifier"> ring</span></a>(self, otherPhone): self.caller = otherPhone <span class="py-src-keyword">if</span> self.callerID: displayNumber = otherPhone.perspectiveName <span class="py-src-keyword">else</span>: displayNumber = <span class="py-src-string">"000-555-1212"</span> self.remoteEar.callRemote(<span class="py-src-string">'ring'</span>, displayNumber) <span class="py-src-keyword">def</span><a name="perspective_pickup"><span class="py-src-identifier"> perspective_pickup</span></a>(self): <span class="py-src-keyword">if</span> self.caller: self.caller.phoneConnected(self) self.phoneConnected(self.caller) self.caller = None <span class="py-src-keyword">def</span><a name="phoneConnected"><span class="py-src-identifier"> phoneConnected</span></a>(self, otherPhone): self.talkingTo = otherPhone self.remoteEar.callRemote(<span class="py-src-string">'connected'</span>) <span class="py-src-keyword">def</span><a name="perspective_talk"><span class="py-src-identifier"> perspective_talk</span></a>(self, message): <span class="py-src-keyword">if</span> self.talkingTo: self.talkingTo.hear(message)<span class="py-src-keyword">class</span><a name="PhoneCompany"><span class="py-src-identifier"> PhoneCompany</span></a>(Service): perspectiveClass = Cellphone</pre><div class="py-caption">Cell Phone Service - <span class="py-filename">cellphone.py</span></div></div><p>In this first listing, we have a cell-phone service which is usable on itsown, not specific to any game. Each player is represented to this service as a<code>Cellphone</code> instance, uniquely identified by a phone number, whichserves as the name of this perspective. Note the methods whose names beginwith <code>perspective_</code>; those can be called directly by a networkclient of this service.</p><div class="py-listing"><pre class="python"><span class="py-src-keyword">from</span> twisted.spread.pb <span class="py-src-keyword">import</span> Perspective, Service<span class="py-src-keyword">class</span><a name="Bug"><span class="py-src-identifier"> Bug</span></a>(Perspective): angst = 0 <span class="py-src-keyword">def</span><a name="attached"><span class="py-src-identifier"> attached</span></a>(self, remoteBugWatcher, identity): self.remoteBugWatcher = remoteBugWatcher self.psychologist = None self.angst += 5 <span class="py-src-comment"># It's hard to get up in the morning.</span> <span class="py-src-keyword">return</span> Perspective.attached(self, remoteBugWatcher, identity) <span class="py-src-keyword">def</span><a name="detached"><span class="py-src-identifier"> detached</span></a>(self, remoteBugWatcher, identity): self.remoteBugWatcher = None <span class="py-src-keyword">if</span> self.psychologist: self.psychologist.leaveTherapy(self) self.psychologist = None <span class="py-src-keyword">return</span> Perspective.detached(self, remoteBugWatcher, identity)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -