標籤:
公司遊戲底層用的是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. 介面最佳化, 通訊格式應儘可能的減短, 將同一時刻可能發生的多次通訊合并成一次通訊
LuaSocket效能猜測