由于实际需要把用户数据中的一个属性值限制范围扩大,仔细看了代码只需要修改宏定义的值,然后重新编译替换到线上即可。结果替换了新版本后,使用管理员工具竟然无法登录,但是线上在服务启动后其他玩家又可以正常登录,大概有一百多人左右。替换成旧的版本,用管理员工具居然又可以登录了,这种理论上根本说不通,很是费解,又回头仔细看了代码,还是觉得不可能是这次修改代码引起的,因为只是修改了一个宏定义的值,其他任何地方都没有修改过。

再次替换成新版本,服务器启动后,一百多玩家正常登录,管理员工具还是无法登录,管理员工具和普通玩家的登录在底层没有任何区别,很是诡异。还是觉得不是新版本的问题,过了一会再次替换成旧版本,这次使用管理员工具也不能登录了,服务器启动后还是有一百多玩家登录上线,同时也有其他玩家反馈无法登录,到这里可以确定不是管理员工具的问题,也不是新版本程序的问题。

仔细观察服务器日志,发现有大量的无效的网络连接,建立连接然后主动断开或被服务器心跳超时断开,看起来应该是有大量的端口扫描。同事的小软件有断线自动重连机制,而且重连尝试的时间间隔很短,当服务重新启动后,短时间有大量的连接建立,可能是由于这个原因导致的,但是具体原因还是不清楚。底层是自己封装的一个基于完成端口的网络库,启动时投递了10个AcceptEx,后面每接收一个连接就重新投递一个AcceptEx。按照正常逻辑感觉也不可能是这块的问题,因为每接收一个连接会重新投递一个AcceptEx。实在想不到别的哪里可能出问题了,就尝试把启动时投递AcceptEx的数量放大到100,编译了一个新的版本,替换到线上后,居然真的可以正常登录了,没有出现无法登录的问题。问题是临时解决了,但总觉得问题应该不是在这里。

找出当时封装完成端口的解决方案,里面包含完整的服务器端和客户端测试代码,既然是短时间大量连接导致的问题,就在客户端里开启1000个线程和服务器建立连接来模拟这种情况,果然在建立接近600个连接时,出现了网络层库打印的错误日志,找到对应位置代码,发现是当GetQueuedCompletionStatus函数返回FALSE时,直接把对应的socket给关掉了,打印了下投递的操作类型,发现是投递的AcceptEx执行失败,AcceptEx是在用于listen的socket上投递的事件,这里直接把用于listen的socket关掉了,肯定后续客户端都无法建立连接了。到这里算是真正定位到了问题,由于服务启动后有大量的端口扫描导致投递的AcceptEx事件执行失败,把用于listen的socket关闭了,之后客户端无法连接服务器,无法进行登录。

修改代码,当GetQueuedCompletionStatus函数返回FALSE时,如果是用于listen的socket上的AcceptEx事件,关闭用于建立连接的socket,释放对应的socketkey,释放投递的overlapped结构,重新投递一个AcceptEx事件,问题解决。

重新查阅MSDN关于GetQueuedCompletionStatus函数返回错误情况,针对每种错误都进行处理。

这两天读到一句话,放到这里很合适:没有意外,只有未曾考虑过的情况!!!!!

总结:

1、出现这个问题的原因是当时封装网络层库的时候,有点想当然,没有仔细去了解GetQueuedCompletionStatus函数的各种返回错误情况;

2、没有考虑到投递的AcceptEx事件执行失败的情况;

3、使用任何函数,都需要弄清楚每一种情况,包括正确的和错误的;