1.总览

这个文档教你怎样使用Twisted来执行网络协议。

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

我们自己建立的的网络处理类会使用基类(twisted.internet.protocol.Protocol)。大多数的网络协议继承这个基类或者它的一个子类。一个网络类的实例按照自己的需求来写,在断开连接后消失。也就是说,持久性的配置不会保存在Protocol里边。

持久性的配置被保存在Factory这个类里边,它通常继承(twisted.internet.protocol.Factory.)。buildProtocol是个Factory类的方法,用来创建一个Protocol协议(为每一个新的connection连接都创建)。

在多个端口或者多个网络地址上提供相同的服务是很常见的。因此Factory不会监听connection(就是socket里边的listen),事实上Factory也不会知道关于网络细节的任何事情,可以在endpoints文档里边查阅其中的缘由,或者 IReactorTCP.listenTCP, IReactorTCP.listenTCP来看一些更底层的实现。

这个文档会解释建立一个网络连接的每一步。

2.Protocols(协议)

如上所述,这个类是大多数你会写到的代码的存放处。Twisted协议是异步方式处理数据的。protocol处理从网络来的每一个事件,而事件会在protovol中的方法被调用的时候发生。

给一些简单的例子:

1.Twisted学习 Python 第1张

这个例子不能运行,只是为了说明,它主要做的是收到发送者的什么数据就返回给发送者什么数据,但是注意,它不是对所有请求的相应。再写一个简单的例子:

1.Twisted学习 Python 第2张

这个例子和上边第一个例子的不同之处在于,它会回复一个自定义的消息‘good luck to you!’,而且回复之后就自动关闭连接了。

第一的总览说了一大堆,其实里边的所谓方法很简单,connectionMade就是一个方法,我也说过Prorocol这个被继承的基类,我们写的大多数代码都会放在这里边。

connectionMade这个方法通常是用来设置连接的相关配置的,也会写一些问候语(good luck!)总而言之,connectionMade这个方法是写对任何和你接通的连接,在开始连接后的的一些配置,再写一个例子:

1.Twisted学习 Python 第3张

这个例子有四个方法,好吧,其实是三个,第一个不多说了吧,就是你每一个新的连接进来就会配置一些东西,第二个是断开连接后做的一些事,第三个是收到信息后做什么。

一般来说,你打开什么东西总要关闭吧(socket里边也有close)所以connectionMade和connectionLost一个代表开始,一个代表结束,就是个规范。这里初始化了一个工厂,主要是用来计数的,接下来给你讲讲它为什么叫工厂吧。

3.loseConnection()和ABORTConnection()

上边的例子里loseConnection()在transport之后被立刻调用。实际上,loseConnection只有当所有数据都由Twisted写到操作系统时才会被调用来关闭连接。所以不用担心有数据丢包之类的。如果一个生产者被和transport一起使用,loseConnection只会在生产者没有注册时被关闭一次!

在一些特殊情况下,一直等到所有数据都被写出可能不符合我们的要求。由于网络故障或者其他conn错误,写入transport的数据可能不会到达,这样的话,loseConnection就一直不会调用关闭。这种情况下,可以调用abortconnection,它会立刻马上关闭连接,而不是傻傻地像loseConnection一样在网络故障中等待数据送达后关闭(想起了尾生抱柱:)

总而言之,我们最好有两手准备,如果你预感到会有网络堵塞之类的,而且不希望连接一辈子不关闭,就用abortconnection,它会立刻关闭连接。

4.使用协议Protocol

这一章,你会学习怎么运行一个服务器。写个例子吧:

1.Twisted学习 Python 第4张先回顾一下以前的吧,我已开始说了:持久性的配置被保存在Factory这个类里边,它通常继承(twisted.internet.protocol.Factory.)。buildProtocol是个Factory类的方法,用来创建一个Protocol协议(为每一个新的connection连接都创建)。

在这个例子里,我创建了一个Factory,我定义这个工厂类来创建一个方法,所有用了buildProtocol

首先创立了一个TCPserver,然后监听8000端口。

listen()方法会告诉reactor去执行所有连接到本地8000端口的连接,但是一定要有run()方法才行哦,先listen在run起来,之后就可以等待连接了,想停止的话,可以ctrl+c或者reactor.stop()。

5.Helper Protocols

许多协议都是献礼在相同的低级抽象基础上的。

几个例子,许多流行的网络协议是line-based(基于文本行),大概就是以换行符为标志的文本数据,而不是直接包含原始数据。然而买许多协议是混合使用的,有基础文本行的,也有基于原始数据的。不如HTTP/1.1和Freenet。

对这些例子,有一个LineReceiver协议。这个协议分发到两个不同的方法处理函数LineReceived和rawDataReceived。默认是调用lineReceivve。但是,而过你用了SetRawMode方法,这个协议会调用rawDataReceive,之后传给lineReceived。它还提供了一个方法,sendLine,会吧数据写入transport用类的分隔符,也就是默认的\r\n。

给个例子:

1.Twisted学习 Python 第5张

配合文字应该是很好理解的,起码我理解了:)

6.State Machines

许多twisted协议执行需要写一个状态机来记录它所处的状态。给你一些帮助:
  不要写一个庞大的状态机。最好写一次处理一个抽象问题的状态机。

  不要把你的应用程序代码和协议混合在一起写。

7.Factories

对简单实例化特定协议的类实例工厂,有一种更简单的实现工厂的方法。默认的buildProtocol方法会调用protocol的工厂属性来创建一个Protocol实例,然后设置factory属性指向工厂本身。我们可以直接调用这个属性,这样就可以让每一个Protocol都能够存取,然后就能修改它,也就不需要用buildProtocol方法了。给你个例子来使用这些特性:

1.Twisted学习 Python 第6张1.Twisted学习 Python 第7张

如此简单!

工厂开启和关闭

一个工厂有两个方法来执行特定的应用程序的开启和关闭,因为一个工厂是持续存在的,执行__init__和__del__就显得不合时宜,因为它们都太早或者太晚了。

给你个例子,它允许协议来写特定的日志文件:
1.Twisted学习 Python 第8张
实际上上边的例子要运行起来是这个样子的:

 1 from twisted.internet.protocol import Protocol,Factory,connectionDone
 2 from twisted.protocols.basic import LineReceiver
 3 from twisted.internet.endpoints import TCP4ServerEndpoint
 4 from twisted.internet import reactor
 5 
 6 class LoggingProtocol(LineReceiver):
 7     def connectionMade(self):           #第二步
 8         self.transport.write(b'from server')
 9         print('新的conn')
10 
11     #貌似定义了datareceived就不会使用linereceived
12     # def dataReceived(self, data):
13     #     print(data)
14 
15     def lineReceived(self, line):   #接受消息直到一个换行符为止
16         print('收到数据',line.decode('utf-8'))   #第三步
17         if line.decode('utf-8')=='quit':
18             self.transport.loseConnection()
19         self.factory.fp.write(line.decode('utf-8'))
20 
21     def connectionLost(self, reason=connectionDone):
22         print('连接关闭')
23         self.factory.fp.close()
24         self.transport.loseConnection()
25 
26 class LogfileFactory(Factory):
27     protocol = LoggingProtocol
28     def __init__(self,filename):
29         self.filename=filename
30 
31     def startFactory(self):             #第一步
32         print('startfactory')
33         self.fp=open(self.filename,'a')
34 
35     def stopFactory(self):
36         print('stopfactory')
37         self.fp.close()
38 
39 endpoint=TCP4ServerEndpoint(reactor,9999)
40 endpoint.listen(LogfileFactory('log.txt'))
41 reactor.run()

8.把它们写到一起

作为一个最终版本,这里会写一个简单的聊天服务器允许用户们选择用户名和它们要聊天的对象。它展示了如何在工厂中使用共享状态,为每一个单独的类一个状态机,而且在不同的协议中能够交流。

 1 from twisted.internet.protocol import Factory,Protocol,connectionDone
 2 from twisted.internet.endpoints import TCP4ServerEndpoint
 3 from twisted.protocols.basic import LineReceiver
 4 from twisted.internet import reactor
 5 
 6 class Chat(LineReceiver):
 7     def __init__(self,user):
 8         self.user=user
 9         self.name=None
10         self.state='GETNAME'
11 
12     def connectionMade(self):
13         self.sendLine(b'what is your name?')
14 
15     def connectionLost(self, reason=connectionDone):
16         if self.name in self.user:
17             del self.user[self.name]
18 
19     def lineReceived(self, line):
20         print('开始接受数据',line)
21         if self.state=='GETNAME':
22             self.handle_GETNAME(line)
23         else:
24             self.handle_CHAT(line)
25 
26     def handle_GETNAME(self,name):
27         if name in self.user:
28             self.sendLine(b'Name token,please choose another')
29             return
30         self.sendLine(b'welcome %s'%name)
31         self.name=name
32         self.user[name]=self
33         self.state='CHAT'
34 
35     def handle_CHAT(self,msg):
36         msg='<%s> %s'%(self.name,msg)
37         for name,protocol in self.user.items():
38             if protocol!=self:
39                 protocol.sendLine(msg)
40 
41 class ChatFactory(Factory):
42     def __init__(self):
43         self.user={}
44 
45     def buildProtocol(self, addr):
46         return Chat(self.user)
47 
48 endpoint=TCP4ServerEndpoint(reactor,9999)
49 endpoint.listen(ChatFactory())
50 reactor.run()

 

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄