twisted(3)--再谈twisted
上一章,我们直接写了一个小例子来从整体讲述twisted运行的大致过程,今天我们首先深入一些概念,在逐渐明白这些概念以后,我们会修改昨天写的例子。
先看下面一张图:
这个系列的第一篇文章,我们已经为大家展示了一张twisted的原理图,那张图,因为我们没有捕获任何socket事件,所以只有一个圈。这张图上面的大圈代表捕获socket事件,这个也是twisted最主要的功能,它已经为我们做了。并且提供了2个函数,transport.write写入事件,dataReceived读取事件。下面的小圈子,也就是我们自己的代码,比如我们昨天的验证、单聊、组聊等功能。大家一定要时时刻刻记住这张图,编写twisted代码的时候,脑子里印着这张图,这就跟我们以前写c代码的时候,一定要记住内存模型一样。
回到这个大圈,transport.write和dataReceived其实是经过很多层封装的函数,它们本质上还是操作select模型中的写文件描述符(write_fd)、读文件描述符(read_fd),对应twisted的基础类就是IWriteDescriptor和IReadDescriptor,如果我们比较熟悉select模型,我们都知道,每次新来一个连接,都是建立write_fd、read_fd、error_fd,select不停的轮询这些fd,当其中任何一个满足条件时,触发相应的事件,这些所有的东西,twisted都已经帮我们做好了,而且异步化了。我们接受到事件,只管处理就好了。
再看下面一个图,
仔细看上面这个图,再对比之前的图,twisted在socket这块全部为我们做好。
下面我们再讲一下transport这个对象,这个对象在每个Protocol里面都会产生一个,它代表一个连接,这个连接可以是socket,也可以是unix的pipe,twisted已经为我们封装好,一般不会自己去新建它。通常我们会用它来发送数据(write)、获取连接另一方的信息(getPeer)。
再看一下dataReceived这个函数,就是每次接到数据以后触发事件,上面说了,就是每次循环,select检查这些fd,fd被写入就触发。这时候大家想想,如果循环被阻塞,在这个data里面会有很多数据,按照我们昨天的程序,只会处理第一个数据,其他的可能被丢弃掉了。
我们昨天的例子,把客户端运行代码稍微修改一下,在第10秒的时候,同时发送2个数据(粘包),看看服务器运行情况。
if __name__ == '__main__':
cf = EchoClientFactory()
chat_from = sys.argv[1]
all_phone_numbers = ['', '', '', '']
all_phone_numbers.remove(chat_from)
import random
reactor.callLater(3, cf.p.send_verify, chat_from)
reactor.callLater(10, cf.p.send_single_chat, chat_from, random.choice(all_phone_numbers), '你好,这是单聊')
reactor.callLater(10, cf.p.send_single_chat, chat_from, random.choice(all_phone_numbers), '你好,这是单聊')
# reactor.callLater(11, cf.p.send_group_chat, chat_from, [random.choice(all_phone_numbers), random.choice(all_phone_numbers)], '你好,这是组聊')
# reactor.callLater(12, cf.p.send_broadcast_chat, chat_from, '你好,这是群聊') reactor.connectTCP('127.0.0.1', 8124, cf) reactor.run()
客户端代码已经更改,运行一下,看看服务器结果。
/usr/bin/python2. /home/yudahai/PycharmProjects/blog01/tcpserver/frontTCP.py
-- ::+ [-] Log opened.
-- ::+ [-] ChatFactory starting on
-- ::+ [-] Starting factory <__main__.ChatFactory instance at 0x7f382d908638>
-- ::+ [__main__.ChatFactory] New connection, the info is: IPv4Address(TCP, '127.0.0.1', )
-- ::+ [Chat,,127.0.0.1] 欢迎, !
-- ::+ [Chat,,127.0.0.1] 你好,这是单聊
-- ::+ [Chat,,127.0.0.1] Phone_number: 不在线,不能聊天.
果然,只处理了一个数据,后面一个直接丢弃掉了。
通常来说,我们都会为每个Protocol申请一段内存,每次接受到数据以后,先存放到这段内存中,然后再集中处理,这样,即使循环被blocking住或者客户端粘包,我们也能正确处理。新的代码如下:
# coding:utf-8
from twisted.internet.protocol import Factory, Protocol
from twisted.internet import reactor
import struct
import json
from twisted.python import log
import sys
log.startLogging(sys.stdout) class Chat(Protocol):
def __init__(self, users):
self.users = users
self.phone_number = None
self.state = "VERIFY"
self.version = 0
self.command_func_dict = {
1: self.handle_verify,
2: self.handle_single_chat,
3: self.handle_group_chat,
4: self.handle_broadcast_chat
}
self._data_buffer = bytes() def connectionMade(self):
log.msg("New connection, the info is:", self.transport.getPeer()) def connectionLost(self, reason):
if self.phone_number in self.users:
del self.users[self.phone_number] def dataReceived(self, data):
"""
接受到数据以后的操作
"""
self._data_buffer += data while True:
length, self.version, command_id = struct.unpack('!3I', self._data_buffer[:12]) if length > len(self._data_buffer):
break content = self._data_buffer[12:length] if command_id not in [1, 2, 3, 4]:
return if self.state == "VERIFY" and command_id == 1:
self.handle_verify(content)
else:
self.handle_data(command_id, content) self._data_buffer = self._data_buffer[length:] if len(self._data_buffer) < 12:
break def handle_verify(self, content):
"""
验证函数
"""
content = json.loads(content)
phone_number = content.get('phone_number')
if phone_number in self.users:
log.msg("电话号码<%s>存在老的连接." % phone_number.encode('utf-8'))
self.users[phone_number].connectionLost("")
log.msg("欢迎, %s!" % (phone_number.encode('utf-8'),))
self.phone_number = phone_number
self.users[phone_number] = self
self.state = "DATA" send_content = json.dumps({'code': 1}) self.send_content(send_content, 101, [phone_number]) def handle_data(self, command_id, content):
"""
根据command_id来分配函数
"""
self.command_func_dict[command_id](content) def handle_single_chat(self, content):
"""
单播
"""
content = json.loads(content)
chat_from = content.get('chat_from')
chat_to = content.get('chat_to')
chat_content = content.get('chat_content')
log.msg(chat_content.encode('utf-8'))
send_content = json.dumps(dict(chat_from=chat_from, chat_content=chat_content)) self.send_content(send_content, 102, [chat_to]) def handle_group_chat(self, content):
"""
组播
"""
content = json.loads(content)
chat_from = content.get('chat_from')
chat_to = content.get('chat_to')
chat_content = content.get('chat_content')
send_content = json.dumps(dict(chat_from=chat_from, chat_content=chat_content)) phone_numbers = chat_to
self.send_content(send_content, 103, phone_numbers) def handle_broadcast_chat(self, content):
"""
广播
"""
content = json.loads(content)
chat_from = content.get('chat_from')
chat_content = content.get('chat_content')
send_content = json.dumps(dict(chat_from=chat_from, chat_content=chat_content)) phone_numbers = self.users.keys()
self.send_content(send_content, 104, phone_numbers) def send_content(self, send_content, command_id, phone_numbers):
"""
发送函数
"""
length = 12 + len(send_content)
version = self.version
command_id = command_id
header = [length, version, command_id]
header_pack = struct.pack('!3I', *header)
for phone_number in phone_numbers:
if phone_number in self.users.keys():
self.users[phone_number].transport.write(header_pack + send_content)
else:
log.msg("Phone_number:%s 不在线,不能聊天." % phone_number.encode('utf-8')) class ChatFactory(Factory):
def __init__(self):
self.users = {} def buildProtocol(self, addr):
return Chat(self.users) reactor.listenTCP(8124, ChatFactory())
reactor.run()
我们在构造函数里面,加入了一个字段,这个字段就是self._data_buffer,在每次接受到数据以后,都循环处理这段内存。再看看运行结果,有什么不同。
/usr/bin/python2. /home/yudahai/PycharmProjects/blog01/tcpserver/frontTCP.py
-- ::+ [-] Log opened.
-- ::+ [-] ChatFactory starting on
-- ::+ [-] Starting factory <__main__.ChatFactory instance at 0x7f96860e0680>
-- ::+ [__main__.ChatFactory] New connection, the info is: IPv4Address(TCP, '127.0.0.1', )
-- ::+ [Chat,,127.0.0.1] 欢迎, !
-- ::+ [Chat,,127.0.0.1] 你好,这是单聊
-- ::+ [Chat,,127.0.0.1] Phone_number: 不在线,不能聊天.
-- ::+ [Chat,,127.0.0.1] 你好,这是单聊
-- ::+ [Chat,,127.0.0.1] Phone_number: 不在线,不能聊天.
是不是正确了?接受数据,我们先讲到这,下面我们讲开发tcpserver一定要处理的问题,异常断线
异常断线
异常断线的处理在tcpserver开发过程中必不可少,很多时候,尤其是无线、3G、4G网络,信号不好的时候就断线,由于是网络问题,没有经过tcp结束的4次握手,服务器不可能及时检查到此事件,这时候就有可能出错。通常我们会采取一种心跳包机制,即客户端每隔一段时间就向服务器端发送一个心跳包,服务器端每隔一段时间就检测一下,如果发现客户端连续2次或者多次没有发送心跳包,就认为客户端已经掉线,再采取措施。
好了,说了这么多,先要重新部署一下程序,我把一个客户端发在我的另外一台笔记本上,先连接好,然后拔掉网线,再从服务器端发送一组数据过去,看看会发生什么。
首先,我们把000002放在笔记本上,000001在服务器端,在10秒和20秒的时候,分别发送一个单聊给000002,看看服务器端和000002的情况。
000001的运行代码修改如下:
if __name__ == '__main__':
cf = EchoClientFactory()
chat_from = sys.argv[1]
all_phone_numbers = ['', '', '', '']
all_phone_numbers.remove(chat_from)
import random
reactor.callLater(3, cf.p.send_verify, chat_from)
reactor.callLater(10, cf.p.send_single_chat, chat_from, '', '你好,这是10秒的时候发送')
reactor.callLater(20, cf.p.send_single_chat, chat_from, '', '你好,这是20秒的时候发送') reactor.connectTCP('127.0.0.1', 8124, cf) reactor.run()
10秒和20秒,分别发送数据到服务器端,而000002端,在10秒和20秒的中间,拔掉网线,我们看看发生了什么情况。
首先,服务器端的运行结果如下:
/usr/bin/python2.7 /home/yudahai/PycharmProjects/blog01/tcpserver/frontTCP.py
2016-06-22 11:40:02+0800 [-] Log opened.
2016-06-22 11:40:02+0800 [-] ChatFactory starting on 8124
2016-06-22 11:40:02+0800 [-] Starting factory <__main__.ChatFactory instance at 0x7f9c39f89638>
2016-06-22 11:41:26+0800 [__main__.ChatFactory] New connection, the info is: IPv4Address(TCP, '192.168.5.15', 57150)
2016-06-22 11:41:29+0800 [Chat,0,192.168.5.15] 欢迎, 000002!
2016-06-22 11:41:41+0800 [__main__.ChatFactory] New connection, the info is: IPv4Address(TCP, '127.0.0.1', 49526)
2016-06-22 11:41:44+0800 [Chat,1,127.0.0.1] 欢迎, 000001!
2016-06-22 11:41:51+0800 [Chat,1,127.0.0.1] 你好,这是10秒的时候发送
2016-06-22 11:42:01+0800 [Chat,1,127.0.0.1] 你好,这是20秒的时候发送
它在000002中断了以后,并没有发现000002已经中断,还是照样write下去,其实本质上,它还是把数据发到了write_fd上,然后就是底层的事了。
而000002客户端的结果比较有意思。
-- ::+ [-] Log opened.
-- ::+ [-] Starting factory <__main__.EchoClientFactory instance at 0x7f4e75db7680>
-- ::+ [-] Started to connect
-- ::+ [Uninitialized] Connected.
-- ::+ [Uninitialized] New connection IPv4Address(TCP, '192.168.5.60', )
-- ::+ [EchoClient,client] 验证通过
-- ::+ [EchoClient,client] [单聊][]:你好,这是10秒的时候发送
-- ::+ [EchoClient,client] [单聊][]:你好,这是20秒的时候发送
大家注意到没有,居然还是收到了,但是看时间,时间和原来的是不对的。我后来把网线重新插上去,然后就接受到了。twisted把write_fd的数据重新发送给了客户端,因为客户端没有任何改变,ip和端口都是原来的,网络情况没有改变,所以再次就连接上来。
我们再试一下另外一种情况,也是移动端经常遇到的情况,就是切换网络,比如从4G切换到无线网,看看会发生什么。
yudahai@yu-sony:~/PycharmProjects/flask001$ python frontClient.py 000002
2016-06-22 13:09:34+0800 [-] Log opened.
2016-06-22 13:09:34+0800 [-] Starting factory <__main__.EchoClientFactory instance at 0x7fd8a0408680>
2016-06-22 13:09:34+0800 [-] Started to connect
2016-06-22 13:09:34+0800 [Uninitialized] Connected.
2016-06-22 13:09:34+0800 [Uninitialized] New connection IPv4Address(TCP, '192.168.5.60', 8124)
2016-06-22 13:09:37+0800 [EchoClient,client] 验证通过
2016-06-22 13:09:54+0800 [EchoClient,client] [单聊][000001]:你好,这是10秒的时候发送
客户端再也收不到了,这也是真实情况。通常来说,用户切换网络的时候,都会更改网络信息,这时候移动客户端再也收不到这个信息了,而且服务器端也不会报错(以后要为我们做消息确认机制埋下伏笔。)
既然收不到了,我们就解决这个问题,上面说了,增加心跳包机制,客户端每隔一段时间发送一次心跳包,服务器端收到心跳包以后,记录最近一次接受到的时间。每隔一段时间,服务器整体轮询一次,如果发现某一个客户端很长时间没有接受到心跳包,就判定它为断线,这时候主动切断这个客户端。
心跳包的command_id也要加上,直接为5吧,内容为空。只是心跳包,没有必要写内容了。
新代码如下:
frontTCP.py
# coding:utf-8
from twisted.internet.protocol import Factory, Protocol
from twisted.internet import reactor, task
import struct
import json
from twisted.python import log
import sys
import time
log.startLogging(sys.stdout) class Chat(Protocol):
def __init__(self, users):
self.users = users
self.phone_number = None
self.state = "VERIFY"
self.version = 0
self.last_heartbeat_time = 0
self.command_func_dict = {
1: self.handle_verify,
2: self.handle_single_chat,
3: self.handle_group_chat,
4: self.handle_broadcast_chat,
5: self.handle_heartbeat
}
self._data_buffer = bytes() def connectionMade(self):
log.msg("New connection, the info is:", self.transport.getPeer()) def connectionLost(self, reason):
log.msg("[%s]:断线" % self.phone_number.encode('utf-8'))
if self.phone_number in self.users:
del self.users[self.phone_number] def dataReceived(self, data):
"""
接受到数据以后的操作
"""
self._data_buffer += data while True:
length, self.version, command_id = struct.unpack('!3I', self._data_buffer[:12]) if length > len(self._data_buffer):
break content = self._data_buffer[12:length] if command_id not in [1, 2, 3, 4, 5]:
return if self.state == "VERIFY" and command_id == 1:
self.handle_verify(content)
else:
self.handle_data(command_id, content) self._data_buffer = self._data_buffer[length:] if len(self._data_buffer) < 12:
break def handle_heartbeat(self, content):
"""
处理心跳包
"""
self.last_heartbeat_time = int(time.time()) def handle_verify(self, content):
"""
验证函数
"""
content = json.loads(content)
phone_number = content.get('phone_number')
if phone_number in self.users:
log.msg("电话号码<%s>存在老的连接." % phone_number.encode('utf-8'))
self.users[phone_number].connectionLost("")
log.msg("欢迎, %s!" % (phone_number.encode('utf-8'),))
self.phone_number = phone_number
self.users[phone_number] = self
self.state = "DATA" send_content = json.dumps({'code': 1}) self.send_content(send_content, 101, [phone_number]) def handle_data(self, command_id, content):
"""
根据command_id来分配函数
"""
self.command_func_dict[command_id](content) def handle_single_chat(self, content):
"""
单播
"""
content = json.loads(content)
chat_from = content.get('chat_from')
chat_to = content.get('chat_to')
chat_content = content.get('chat_content')
log.msg(chat_content.encode('utf-8'))
send_content = json.dumps(dict(chat_from=chat_from, chat_content=chat_content)) self.send_content(send_content, 102, [chat_to]) def handle_group_chat(self, content):
"""
组播
"""
content = json.loads(content)
chat_from = content.get('chat_from')
chat_to = content.get('chat_to')
chat_content = content.get('chat_content')
send_content = json.dumps(dict(chat_from=chat_from, chat_content=chat_content)) phone_numbers = chat_to
self.send_content(send_content, 103, phone_numbers) def handle_broadcast_chat(self, content):
"""
广播
"""
content = json.loads(content)
chat_from = content.get('chat_from')
chat_content = content.get('chat_content')
send_content = json.dumps(dict(chat_from=chat_from, chat_content=chat_content)) phone_numbers = self.users.keys()
self.send_content(send_content, 104, phone_numbers) def send_content(self, send_content, command_id, phone_numbers):
"""
发送函数
"""
length = 12 + len(send_content)
version = self.version
command_id = command_id
header = [length, version, command_id]
header_pack = struct.pack('!3I', *header)
for phone_number in phone_numbers:
if phone_number in self.users.keys():
self.users[phone_number].transport.write(header_pack + send_content)
else:
log.msg("Phone_number:%s 不在线,不能聊天." % phone_number.encode('utf-8')) class ChatFactory(Factory):
def __init__(self):
self.users = {} def buildProtocol(self, addr):
return Chat(self.users) def check_users_online(self):
for key, value in self.users.items():
if value.last_heartbeat_time != 0 and int(time.time()) - value.last_heartbeat_time > 4:
log.msg("[%s]没有检测到心跳包,主动切断" % key.encode('utf-8'))
value.transport.abortConnection() cf = ChatFactory() task1 = task.LoopingCall(cf.check_users_online)
task1.start(3, now=False) reactor.listenTCP(8124, cf)
reactor.run()
就像上面所说的,加了一个接受心跳包的检测的函数,handle_heartbeat,每次来一个心跳包,就把它相应的last_heartbeat_time变换一下,这样,整体轮询检测的时候,我只要判断最后一次连接时间和当前连接时间之差,就可以判断它是不是异常断线了。
这里看我异常断线的处理,transport.abortConnection(),从字面意思上,直接丢弃这个连接,它会调用Protocol的connectionLost,而且它不管那个fd里面有没有数据,全部丢弃。这个我们以后用netstat分析连接的时候,会进一步说明这个函数,现在只要记住,它会强行中断这个连接,删除任何缓存在里面的数据即可。
frontClient.py
# coding:utf-8
from twisted.internet import reactor, task
from twisted.internet.protocol import Protocol, ClientFactory
import struct
from twisted.python import log
import sys
import json
log.startLogging(sys.stdout) class EchoClient(Protocol):
def __init__(self):
self.command_func_dict = {
101: self.handle_verify_s,
102: self.handle_single_chat_s,
103: self.handle_group_chat_s,
104: self.handle_broadcast_chat_s
}
self.version = 0
self.state = "VERIFY"
self.phone_number = "" def connectionMade(self):
log.msg("New connection", self.transport.getPeer()) def dataReceived(self, data):
length, self.version, command_id = struct.unpack('!3I', data[:12])
content = data[12:length]
if self.state == "VERIFY" and command_id == 101:
self.handle_verify_s(content)
else:
self.handle_data(command_id, content) def handle_data(self, command_id, pack_data):
self.command_func_dict[command_id](pack_data) def connectionLost(self, reason):
log.msg("connection lost") def handle_verify_s(self, pack_data):
"""
接受验证结果
"""
content = json.loads(pack_data)
code = content.get('code')
if code == 1:
log.msg('验证通过')
self.state = "Data" def handle_single_chat_s(self, pack_data):
"""
接受单聊
"""
content = json.loads(pack_data)
chat_from = content.get('chat_from')
chat_content = content.get('chat_content')
log.msg("[单聊][%s]:%s" % (chat_from.encode('utf-8'), chat_content.encode('utf-8'))) def handle_group_chat_s(self, pack_data):
"""
接受组聊
"""
content = json.loads(pack_data)
chat_from = content.get('chat_from')
chat_content = content.get('chat_content')
log.msg("[组聊][%s]:%s" % (chat_from.encode('utf-8'), chat_content.encode('utf-8'))) def handle_broadcast_chat_s(self, pack_data):
"""
接受广播
"""
content = json.loads(pack_data)
chat_from = content.get('chat_from')
chat_content = content.get('chat_content')
log.msg("[群聊][%s]:%s" % (chat_from.encode('utf-8'), chat_content.encode('utf-8'))) def send_verify(self, phone_number):
"""
发送验证
"""
content = json.dumps(dict(phone_number=phone_number))
self.send_data(content, 1) def send_single_chat(self, chat_from, chat_to, chat_content):
"""
发送单聊内容
"""
content = json.dumps(dict(chat_from=chat_from, chat_to=chat_to, chat_content=chat_content))
self.send_data(content, 2) def send_group_chat(self, chat_from, chat_to, chat_content):
"""
发送组聊内容
"""
content = json.dumps(dict(chat_from=chat_from, chat_to=chat_to, chat_content=chat_content))
self.send_data(content, 3) def send_broadcast_chat(self, chat_from, chat_content):
"""
发送群聊内容
"""
content = json.dumps(dict(chat_from=chat_from, chat_content=chat_content))
self.send_data(content, 4) def send_data(self, send_content, command_id):
"""
发送函数
"""
length = 12 + len(send_content)
version = self.version
command_id = command_id
header = [length, version, command_id]
header_pack = struct.pack('!3I', *header)
self.transport.write(header_pack + send_content) def send_heartbeat(self):
"""
发送心跳包
"""
length = 12
version = self.version
command_id = 5
header = [length, version, command_id]
header_pack = struct.pack('!3I', *header)
self.transport.write(header_pack) class EchoClientFactory(ClientFactory):
def __init__(self):
self.p = EchoClient() def startedConnecting(self, connector):
log.msg("Started to connect") def buildProtocol(self, addr):
log.msg("Connected.")
return self.p def clientConnectionFailed(self, connector, reason):
log.msg("Lost connection. Reason:", reason) def clientConnectionLost(self, connector, reason):
log.msg("Connection failed. Reason:", reason) if __name__ == '__main__':
cf = EchoClientFactory()
chat_from = sys.argv[1]
all_phone_numbers = ['', '', '', '']
all_phone_numbers.remove(chat_from)
import random task_send_heartbeat = task.LoopingCall(cf.p.send_heartbeat)
task_send_heartbeat.start(2, now=False) reactor.callLater(3, cf.p.send_verify, chat_from)
reactor.callLater(10, cf.p.send_single_chat, chat_from, '', '你好,这是10秒的时候发送')
reactor.callLater(20, cf.p.send_single_chat, chat_from, '', '你好,这是20秒的时候发送') reactor.connectTCP('192.168.5.60', 8124, cf) reactor.run()
这边就添加了一个心跳包发送程序,每隔2秒发送一个心跳包。
我在000002的客户端在10秒和20秒之间,拔掉了网线,看看调试效果,
先看服务器端的调试结果。
/usr/bin/python2. /home/yudahai/PycharmProjects/blog01/tcpserver/frontTCP.py
-- ::+ [-] Log opened.
-- ::+ [-] ChatFactory starting on
-- ::+ [-] Starting factory <__main__.ChatFactory instance at 0x7ff3c3615758>
-- ::+ [__main__.ChatFactory] New connection, the info is: IPv4Address(TCP, '192.168.5.15', )
-- ::+ [__main__.ChatFactory] New connection, the info is: IPv4Address(TCP, '192.168.5.60', )
-- ::+ [Chat,,192.168.5.15] 欢迎, !
-- ::+ [Chat,,192.168.5.60] 欢迎, !
-- ::+ [Chat,,192.168.5.60] 你好,这是10秒的时候发送
-- ::+ [-] []没有检测到心跳包,主动切断
-- ::+ [-] []:断线
-- ::+ [Chat,,192.168.5.60] 你好,这是20秒的时候发送
-- ::+ [Chat,,192.168.5.60] Phone_number: 不在线,不能聊天.
看见没有,已经能主动检测到了。
再看一下客户端000002的调试结果
yudahai@yu-sony:~/PycharmProjects/flask001$ python frontClient.py
-- ::+ [-] Log opened.
-- ::+ [-] Starting factory <__main__.EchoClientFactory instance at 0x7f4e3e3d56c8>
-- ::+ [-] Started to connect
-- ::+ [Uninitialized] Connected.
-- ::+ [Uninitialized] New connection IPv4Address(TCP, '192.168.5.60', )
-- ::+ [EchoClient,client] 验证通过
-- ::+ [EchoClient,client] [单聊][]:你好,这是10秒的时候发送
-- ::+ [EchoClient,client] connection lost
-- ::+ [EchoClient,client] Connection failed. Reason: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.error.ConnectionLost'>: Connection to the other side was lost in a non-clean fashion.
]
-- ::+ [EchoClient,client] Stopping factory <__main__.EchoClientFactory instance at 0x7f4e3e3d56c8>
比较有意思,15:16我中断了连接,没有接受到,这时候服务器主动切断网络,再连接上来的时候,它已经接受到消息,自己被中断了,其实客户端应该有个断线重连机制,不过这是客户端的事,主要看你的业务需求。
到这,利用心跳包来检测异常网络情况就完成了,如果你有更好的方案,欢迎大家跟我讨论,毕竟我不是专门做tcpserver的,很多东西可能没有研究到。
下一章,我们研究twisted连接redis,把一些很状态转移到redis中,这样,其他模块就能共享这个状态了,这在物联网中,用到尤其多,比如设备在线断线状态、报警状态等,前端web可以直接拿来使用了;以后我们还会讲rabbitmq在twisted中的应用。
twisted(3)--再谈twisted的更多相关文章
- [转载]再谈百度:KPI、无人机,以及一个必须给父母看的案例
[转载]再谈百度:KPI.无人机,以及一个必须给父母看的案例 发表于 2016-03-15 | 0 Comments | 阅读次数 33 原文: 再谈百度:KPI.无人机,以及一个必须 ...
- Support Vector Machine (3) : 再谈泛化误差(Generalization Error)
目录 Support Vector Machine (1) : 简单SVM原理 Support Vector Machine (2) : Sequential Minimal Optimization ...
- Unity教程之再谈Unity中的优化技术
这是从 Unity教程之再谈Unity中的优化技术 这篇文章里提取出来的一部分,这篇文章让我学到了挺多可能我应该知道却还没知道的知识,写的挺好的 优化几何体 这一步主要是为了针对性能瓶颈中的”顶点 ...
- 浅谈HTTP中Get与Post的区别/HTTP协议与HTML表单(再谈GET与POST的区别)
HTTP协议与HTML表单(再谈GET与POST的区别) GET方式在request-line中传送数据:POST方式在request-line及request-body中均可以传送数据. http: ...
- Another Look at Events(再谈Events)
转载:http://www.qtcn.org/bbs/simple/?t31383.html Another Look at Events(再谈Events) 最近在学习Qt事件处理的时候发现一篇很不 ...
- C++ Primer 学习笔记_32_STL实践与分析(6) --再谈string类型(下)
STL实践与分析 --再谈string类型(下) 四.string类型的查找操作 string类型提供了6种查找函数,每种函数以不同形式的find命名.这些操作所有返回string::size_typ ...
- 再谈JSON -json定义及数据类型
再谈json 近期在项目中使用到了highcharts ,highstock做了一些统计分析.使用jQuery ajax那就不得不使用json, 可是在使用过程中也出现了非常多的疑惑,比方说,什么情况 ...
- C++ Primer 学习笔记_44_STL实践与分析(18)--再谈迭代器【下】
STL实践与分析 --再谈迭代器[下] 三.反向迭代器[续:习题] //P355 习题11.19 int main() { vector<int> iVec; for (vector< ...
- C++ Primer 学习笔记_43_STL实践与分析(17)--再谈迭代器【中】
STL实践与分析 --再谈迭代器[中] 二.iostream迭代[续] 3.ostream_iterator对象和ostream_iterator对象的使用 能够使用ostream_iterator对 ...
随机推荐
- Wormholes 最短路判断有无负权值
Description While exploring his many farms, Farmer John has discovered a number of amazing wormholes ...
- 安装loadrunner11 ,出现如下错误如何解决?
出现的问题是: 安装LoadRunner 11时弹窗提示"Micosoft Visual C++ 2005 SP1 可再发行组件包(X86):'命令行选项语法错误.键入命令 / ? 可获得帮 ...
- [Design Pattern] Factory Pattern 简单案例
Factory Pattern , 即工厂模式,用于创建对象的场景,属于创建类的设计模式 . 下面是一个工厂模式案例. Shape 作为接口, Circle, Square, Rectangle 作为 ...
- J - Sabotage - UVA 10480(最大流)
题目大意:旧政府有一个很庞大的网络系统,可以很方便的指挥他的城市,起义军为了减少伤亡所以决定破坏他们的网络,使他们的首都(1号城市)和最大的城市(2号城市)不能联系,不过破坏不同的网络所花费的代价是不 ...
- div+css 圆角加阴影
.test{ display: inline-block; padding: 5px 10px 6px; text-decoration: none; border-radius: 5px; -moz ...
- 字体在Android View中的输出 drawText
Canvas 作为绘制文本时,使用FontMetrics对象,计算位置的坐标. public static class FontMetrics { public flo ...
- Eclipse自动插件依赖的一种配置解决方式
Eclipse的插件具有以下特点: (1)每一个插件有自己独立的classloader (2)插件资源的交互通过MENIFEST.MF中"Export-Package, Require-Bu ...
- VC2010对Excel的操作
1. 创建新的C++工程 创建基于对话框的MFC程序 2. 添加库.添加Excel类库 在工程名上右键,选择“添加”—“类”(或者点击菜单栏的“项目”->“添加类”),选择“TypeLib中的M ...
- Java编程 的动态性,第 2部分: 引入反射--转载
在“ Java编程的动态性,第1部分,”我为您介绍了Java编程类和类装入.该篇文章介绍了一些Java二进制类格式的相关信息.这个月我将阐述使用Java反射API来在运行时接入和使用一些相同信息的基础 ...
- 单页面应用SPA架构
个人认为单页面应用的优势相当明显: 前后端职责分离,架构清晰:前端进行交互逻辑,后端负责数据处理. 前后端单独开发.单独测试. 良好的交互体验,前端进行的是局部渲染.避免了不必要的跳转和重复渲染. 当 ...