公司游戏底层用的是LuaSocket, 最近发现有大量玩家反馈游戏卡,经过多方面的调查目前没有结论,我们的测试在游戏过程中也会遇到一阵一阵的卡
服务器那边的调查结果是服务器这边不存在延迟
因此性能瓶颈是不是可能出在LuaSocket上?
这几天阅读了LuaSocket的源码,发现里面并没有新起线程,也就是整个socket通讯是在主线程中进行的
大概有3个主要函数
int socket_waitfd(p_socket ps, int sw, p_timeout tm) {
int ret;
struct pollfd pfd;
pfd.fd = *ps;
pfd.events = sw;
pfd.revents = 0;
if (timeout_iszero(tm)) return IO_TIMEOUT; /* optimize timeout == 0 case */
do {
int t = (int)(timeout_getretry(tm)*1e3);
ret = poll(&pfd, 1, t >= 0? t: -1);
} while (ret == -1 && errno == EINTR);
if (ret == -1) return errno;
if (ret == 0) return IO_TIMEOUT;
if (sw == WAITFD_C && (pfd.revents & (POLLIN|POLLERR))) return IO_CLOSED;
return IO_DONE;
}
/*-------------------------------------------------------------------------*\
* Send with timeout
\*-------------------------------------------------------------------------*/
int socket_send(p_socket ps, const char *data, size_t count,
size_t *sent, p_timeout tm)
{
int err;
*sent = 0;
/* avoid making system calls on closed sockets */
if (*ps == SOCKET_INVALID) return IO_CLOSED;
/* loop until we send something or we give up on error */
for ( ;; ) {
long put = (long) send(*ps, data, count, 0);
/* if we sent anything, we are done */
if (put >= 0) {
*sent = put;
return IO_DONE;
}
err = errno;
/* EPIPE means the connection was closed */
if (err == EPIPE) return IO_CLOSED;
/* we call was interrupted, just try again */
if (err == EINTR) continue;
/* if failed fatal reason, report error */
if (err != EAGAIN) return err;
/* wait until we can send something or we timeout */
if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err;
}
/* can't reach here */
return IO_UNKNOWN;
}
/*-------------------------------------------------------------------------*\
* Receive with timeout
\*-------------------------------------------------------------------------*/
int socket_recv(p_socket ps, char *data, size_t count, size_t *got, p_timeout tm) {
int err;
*got = 0;
if (*ps == SOCKET_INVALID) return IO_CLOSED;
for ( ;; ) {
long taken = (long) recv(*ps, data, count, 0);
if (taken > 0) {
*got = taken;
return IO_DONE;
}
err = errno;
if (taken == 0) return IO_CLOSED;
if (err == EINTR) continue;
if (err != EAGAIN) return err;
if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err;
}
return IO_UNKNOWN;
}
那么既然luasocket没有新起一个线程,那么为什么我们游戏界面没有卡呢,原因是调用了这个函数
/*-------------------------------------------------------------------------*\
* Sets timeout values for IO operations
* Lua Input: base, time [, mode]
* time: time out value in seconds
* mode: "b" for block timeout, "t" for total timeout. (default: b)
\*-------------------------------------------------------------------------*/
int timeout_meth_settimeout(lua_State *L, p_timeout tm) {
double t = luaL_optnumber(L, 2, -1);
const char *mode = luaL_optstring(L, 3, "b");
switch (*mode) {
case 'b':
tm->block = t;
break;
case 'r': case 't':
tm->total = t;
break;
default:
luaL_argcheck(L, 0, 3, "invalid timeout mode");
break;
}
lua_pushnumber(L, 1);
return 1;
}
我们将timeout设置为0, 然后socket_waitfd, timeout为0时poll调用立即返回而不阻塞主线程
查了一下相关资料 io模型
发现这应该是IO复用模型吧,看来如果大学好好学习计算机基础课程在这种情况下应该有很大帮助的,请原谅我。。
我的理解这就是轮询机制, 由主线程定期轮询socket那边数据接收完了没有,实现中每次轮询poll都立即返回而达到不阻塞主线程的目的,在数据包特别多或者大的时候可能会导致一些性能上的问题
而我们的游戏接口加了很多交互,可能在某一个时刻发出很多的socket数据包, 猜测可能会导致轮询很长事件才处理完,影响到了socket的处理事件甚至后续客户端socket的发送
当然这些只是我的猜想,还没有证据
解决方案:
1. 不要采用Luasocket , 在c++层 新起一个线程来操作socket , 将数据存储, 主线程定期去读取这些数据, 注意多线程的锁
2. 接口优化, 通讯格式应尽可能的减短, 将同一时刻可能发生的多次通讯合并成一次通讯
