最近使用Concurrence官网中的"ASimple Chat Server"改写成了一个简单的游戏服务器。
在服务器长时间运营后,客户端出现无法连接的问题,查看到运行服务器的虚拟机卡死在那里了。刚开始以为是VirtualBox在抽风,不过连续几天都是这种情况,于是开始怀疑游戏服务器出现了内存泄漏。
使用"ps-aux | grep python"显示出服务器进程CPU和内存占用情况,果然内在占用很高,所以断定出现了内存泄漏。仔细审查代码,并未发现出有什么漏洞,况且这个服务器是套用了Concurrence官网的代码,更不该有问题。
于是写了个自动执行的客户端脚本,通过多个客户端脚本执行大量的转发操作来检查泄漏。当我将转发周期调小至0.001s,并运行3个以上客户端时检测到了内存泄漏。于是减少控制台输出,只显示客户端连接与断开的信息,重新测试,发现有1个连接没能正常关闭。仔细观察输出发现有存在一个提示“Nohandlers could be found for logger "SocketServer"”,从网上查到可以通过导入logging模块来解决。添加后,再次测试,这次显示出了错误异常“setchanged size during iteration”,发生在迭代connected_clients(连接客户端)来转发数据时,能够想到这个异常是因为在给用户转发消息时有新的连接加入或断开致使connected_clients发生了改变,我一直以为set和list是一样的,应该支持在迭代中改变内容。不过通过上网查找到Python中set(集合)是不允许在迭代中改变内容的。所以不应该使用set来保存连接的用户,换成list就可以了。
这次又给我补了一课,所以就便总结一下Python中set、list、dict、tuple数据类型的比较。
类型 | 初始化 | 特性 | 关于迭代过程中改变内容 |
set(集合) | set() | 无序,不可重复 | 不允许改变 |
list(列表) | list(), [] | 有序,可重复 | 允许改变 |
dict(字典) | dict(), {} | 无序,键不可重复 | 不允许,迭代keys()/items()允许 |
tuple(元组) | tuple(), () | 有序,可重复,只读 | 永不可变 |
还有一点,看来官方的"ASimple Chat Server"实例使用set来保存连接用户是有漏洞的,在并发量很大的情况下就会暴露出来。