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对 ...
随机推荐
- 【HDOJ】1243 反恐训练营
LCS. /* 1243 */ #include <cstdio> #include <cstring> #include <cstdlib> #define MA ...
- jqGrid中多选
原文地址;http://www.cnblogs.com/josechuanmin/archive/2013/05/19/3087138.html 在jqGrid中设置multiselect: true ...
- config large memory
C Configuring Large Memory Optimization This appendix provides information for configuring memory op ...
- windows快捷键和命令
以管理员方式打开命令行界面:win+X+A 打开服务界面:services.msc 删掉windows系统记住的WIFI密码 cmd下面运行 显示存储的无线连接netsh wlan show prof ...
- MySQL慢查询(二) - pt-query-digest详解慢查询日志
一.简介 pt-query-digest是用于分析mysql慢查询的一个工具,它可以分析binlog.General log.slowlog,也可以通过SHOWPROCESSLIST或者通过tcpdu ...
- SKPhysicsJointSpring类
继承自 NSObject 符合 NSCoding(SKPhysicsJoint)NSObject(NSObject) 框架 /System/Library/Frameworks/SpriteKit. ...
- 第一章 工欲善其事 必先利其器—Android SDK工具(3)
1.3没有真机一样开发--Android模拟器 有些时候,我们手头上可能并没有符合要求的Android设备.那么这时候我们是不是对调试或者开发就一筹莫展了呢?当然不是.由于我们有Android模拟器. ...
- Because the people who are crazy enough to think they can change the world, are the ones who do.
Here's to the crazy ones. The misfits. The rebels. The troublemakers. The round pegs in the square h ...
- python 下的数据结构与算法---7:查找
一:线性查找(Sequential Search) 线性查找可以说是我们用的最早也会是用的最多的查找方式了.其对应的是线性数据结构,回顾一下线性数据结构,其特点是先后加入的元素是有顺序的,相邻的.而线 ...
- 让sublime支持gbk常用编码
Sublime Text 2是一个非常不错的源代码及文本编辑器,但是不支持GB2312和GBK编码在很多情况下会非常麻烦.不过Sublime Package Control所以供的插件可以让Subli ...