公司游戲底層用的是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. 接口優化, 通訊格式應盡可能的減短, 將同一時刻可能發生的多次通訊合並成一次通訊
