Files

819 lines
28 KiB
C++
Raw Permalink Normal View History

#include "StdAfx.h"
#include "IOCPServer.h"
#include "2015Remote.h"
#include <iostream>
2025-01-31 22:22:16 +08:00
#include <ws2tcpip.h>
2025-06-08 15:38:41 +08:00
// <20><><EFBFBD><EFBFBD> socket <20><>ȡ<EFBFBD>ͻ<EFBFBD><CDBB><EFBFBD>IP<49><50>ַ.
std::string GetPeerName(SOCKET sock)
{
sockaddr_in ClientAddr = {};
int ulClientAddrLen = sizeof(sockaddr_in);
int s = getpeername(sock, (SOCKADDR*)&ClientAddr, &ulClientAddrLen);
return s != INVALID_SOCKET ? inet_ntoa(ClientAddr.sin_addr) : "";
2025-06-08 15:38:41 +08:00
}
2025-01-31 22:22:16 +08:00
// <20><><EFBFBD><EFBFBD> socket <20><>ȡ<EFBFBD>ͻ<EFBFBD><CDBB><EFBFBD>IP<49><50>ַ.
std::string GetRemoteIP(SOCKET sock)
{
sockaddr_in addr;
int addrLen = sizeof(addr);
if (getpeername(sock, (sockaddr*)&addr, &addrLen) == 0) {
char ipStr[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &addr.sin_addr, ipStr, sizeof(ipStr));
TRACE(">>> <20>Զ<EFBFBD> IP <20><>ַ: %s\n", ipStr);
return ipStr;
}
TRACE(">>> <20><>ȡ<EFBFBD>Զ<EFBFBD> IP ʧ<><CAA7>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: %d\n", WSAGetLastError());
char buf[10];
sprintf_s(buf, "%d", sock);
return buf;
2025-01-31 22:22:16 +08:00
}
IOCPServer::IOCPServer(void)
{
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2,2), &wsaData)!=0) {
return;
}
m_hCompletionPort = NULL;
m_sListenSocket = INVALID_SOCKET;
m_hListenEvent = WSA_INVALID_EVENT;
m_hListenThread = NULL;
m_ulMaxConnections = 10000;
InitializeCriticalSection(&m_cs);
m_ulWorkThreadCount = 0;
m_bTimeToKill = FALSE;
m_ulThreadPoolMin = 0;
m_ulThreadPoolMax = 0;
m_ulCPULowThreadsHold = 0;
m_ulCPUHighThreadsHold = 0;
m_ulCurrentThread = 0;
m_ulBusyThread = 0;
m_ulKeepLiveTime = 0;
m_hKillEvent = NULL;
m_NotifyProc = NULL;
m_OfflineProc = NULL;
}
void IOCPServer::Destroy()
{
m_bTimeToKill = TRUE;
if (m_hKillEvent != NULL) {
SetEvent(m_hKillEvent);
CloseHandle(m_hKillEvent);
m_hKillEvent = NULL;
}
if (m_sListenSocket != INVALID_SOCKET) {
closesocket(m_sListenSocket);
m_sListenSocket = INVALID_SOCKET;
}
if (m_hCompletionPort != INVALID_HANDLE_VALUE) {
CloseHandle(m_hCompletionPort);
m_hCompletionPort = INVALID_HANDLE_VALUE;
}
if (m_hListenEvent != WSA_INVALID_EVENT) {
CloseHandle(m_hListenEvent);
m_hListenEvent = WSA_INVALID_EVENT;
}
}
IOCPServer::~IOCPServer(void)
{
Destroy();
while (m_ulWorkThreadCount || m_hListenThread)
Sleep(10);
while (!m_ContextConnectionList.IsEmpty()) {
CONTEXT_OBJECT *ContextObject = m_ContextConnectionList.GetHead();
RemoveStaleContext(ContextObject);
SAFE_DELETE(ContextObject->olps);
}
while (!m_ContextFreePoolList.IsEmpty()) {
CONTEXT_OBJECT *ContextObject = m_ContextFreePoolList.RemoveHead();
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD><D0B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʣ<EFBFBD>2019.1.14
//SAFE_DELETE(ContextObject->olps);
delete ContextObject;
}
DeleteCriticalSection(&m_cs);
m_ulWorkThreadCount = 0;
m_ulThreadPoolMin = 0;
m_ulThreadPoolMax = 0;
m_ulCPULowThreadsHold = 0;
m_ulCPUHighThreadsHold = 0;
m_ulCurrentThread = 0;
m_ulBusyThread = 0;
m_ulKeepLiveTime = 0;
WSACleanup();
}
// <20><><EFBFBD>ش<EFBFBD><D8B4><EFBFBD><EFBFBD><EFBFBD>0<EFBFBD><30><EFBFBD><EFBFBD><EFBFBD>ɹ<EFBFBD><C9B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϣ.
UINT IOCPServer::StartServer(pfnNotifyProc NotifyProc, pfnOfflineProc OffProc, USHORT uPort)
{
m_nPort = uPort;
m_NotifyProc = NotifyProc;
m_OfflineProc = OffProc;
m_hKillEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
if (m_hKillEvent==NULL) {
return 1;
}
m_sListenSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); //<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>׽<EFBFBD><D7BD><EFBFBD>
if (m_sListenSocket == INVALID_SOCKET) {
return 2;
}
m_hListenEvent = WSACreateEvent();
if (m_hListenEvent == WSA_INVALID_EVENT) {
closesocket(m_sListenSocket);
m_sListenSocket = INVALID_SOCKET;
return 3;
}
int iRet = WSAEventSelect(m_sListenSocket, //<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>׽<EFBFBD><D7BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>¼<EFBFBD><C2BC><EFBFBD><EFBFBD>й<EFBFBD><D0B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>FD_ACCEPT<50><54><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
m_hListenEvent,
FD_ACCEPT);
if (iRet == SOCKET_ERROR) {
int a = GetLastError();
closesocket(m_sListenSocket);
m_sListenSocket = INVALID_SOCKET;
WSACloseEvent(m_hListenEvent);
m_hListenEvent = WSA_INVALID_EVENT;
return a;
}
SOCKADDR_IN ServerAddr;
ServerAddr.sin_port = htons(uPort);
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_addr.s_addr = INADDR_ANY; //<2F><>ʼ<EFBFBD><CABC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>׻<EFBFBD><D7BB>ֺ<EFBFBD><D6BA><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>bind
iRet = bind(m_sListenSocket,
(sockaddr*)&ServerAddr,
sizeof(ServerAddr));
if (iRet == SOCKET_ERROR) {
int a = GetLastError();
closesocket(m_sListenSocket);
m_sListenSocket = INVALID_SOCKET;
WSACloseEvent(m_hListenEvent);
m_hListenEvent = WSA_INVALID_EVENT;
return a;
}
iRet = listen(m_sListenSocket, SOMAXCONN);
if (iRet == SOCKET_ERROR) {
int a = GetLastError();
closesocket(m_sListenSocket);
m_sListenSocket = INVALID_SOCKET;
WSACloseEvent(m_hListenEvent);
m_hListenEvent = WSA_INVALID_EVENT;
return a;
}
m_hListenThread =
(HANDLE)CreateThread(NULL,
0,
ListenThreadProc,
(void*)this, //<2F><>Thread<61>ص<EFBFBD><D8B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>this <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǵ<EFBFBD><C7B5>̻߳ص<CCBB><D8B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>еij<D0B5>Ա
0,
NULL);
if (m_hListenThread==NULL) {
int a = GetLastError();
closesocket(m_sListenSocket);
m_sListenSocket = INVALID_SOCKET;
WSACloseEvent(m_hListenEvent);
m_hListenEvent = WSA_INVALID_EVENT;
return a;
}
//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>߳<EFBFBD> 1 2
InitializeIOCP();
return 0;
}
//1<><31><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɶ˿<C9B6>
//2<><32><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>߳<EFBFBD>
BOOL IOCPServer::InitializeIOCP(VOID)
{
m_hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0 );
if ( m_hCompletionPort == NULL ) {
return FALSE;
}
if (m_hCompletionPort==INVALID_HANDLE_VALUE) {
return FALSE;
}
SYSTEM_INFO SystemInfo;
GetSystemInfo(&SystemInfo); //<2F><><EFBFBD><EFBFBD>PC<50><43><EFBFBD>м<EFBFBD><D0BC><EFBFBD>
m_ulThreadPoolMin = 1;
m_ulThreadPoolMax = SystemInfo.dwNumberOfProcessors * 2;
m_ulCPULowThreadsHold = 10;
m_ulCPUHighThreadsHold = 75;
ULONG ulWorkThreadCount = m_ulThreadPoolMax;
HANDLE hWorkThread = NULL;
for (int i=0; i<ulWorkThreadCount; ++i) {
hWorkThread = (HANDLE)CreateThread(NULL, //<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>߳<EFBFBD>Ŀ<EFBFBD><C4BF><EFBFBD>Ǵ<EFBFBD><C7B4><EFBFBD>Ͷ<EFBFBD>ݵ<EFBFBD><DDB5><EFBFBD><EFBFBD>ɶ˿<C9B6><CBBF>е<EFBFBD><D0B5><EFBFBD><EFBFBD><EFBFBD>
0,
WorkThreadProc,
(void*)this,
0,
NULL);
if (hWorkThread == NULL ) {
CloseHandle(m_hCompletionPort);
return FALSE;
}
AddWorkThread(1);
CloseHandle(hWorkThread);
}
return TRUE;
}
DWORD IOCPServer::WorkThreadProc(LPVOID lParam)
{
Mprintf("======> IOCPServer WorkThreadProc begin \n");
ZSTD_DCtx* m_Dctx = ZSTD_createDCtx(); // <20><>ѹ<EFBFBD><D1B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
IOCPServer* This = (IOCPServer*)(lParam);
HANDLE hCompletionPort = This->m_hCompletionPort;
DWORD dwTrans = 0;
PCONTEXT_OBJECT ContextObject = NULL;
LPOVERLAPPED Overlapped = NULL;
OVERLAPPEDPLUS* OverlappedPlus = NULL;
ULONG ulBusyThread = 0;
BOOL bError = FALSE;
InterlockedIncrement(&This->m_ulCurrentThread);
InterlockedIncrement(&This->m_ulBusyThread);
timeBeginPeriod(1);
while (This->m_bTimeToKill==FALSE) {
InterlockedDecrement(&This->m_ulBusyThread);
// GetQueuedCompletionStatus<75><73>ʱ<EFBFBD>Ƚϳ<C8BD><CFB3><EFBFBD><EFBFBD><EFBFBD><EFBFBD>¿ͻ<C2BF><CDBB>˷<EFBFBD><CBB7><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݵ<EFBFBD><DDB5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>߲<EFBFBD><DFB2><EFBFBD>
BOOL bOk = GetQueuedCompletionStatus(
hCompletionPort,
&dwTrans,
(PULONG_PTR)&ContextObject,
&Overlapped, INFINITE);
DWORD dwIOError = GetLastError();
OverlappedPlus = CONTAINING_RECORD(Overlapped, OVERLAPPEDPLUS, m_ol);
ulBusyThread = InterlockedIncrement(&This->m_ulBusyThread); //1 1
if ( !bOk && dwIOError != WAIT_TIMEOUT ) { //<2F><><EFBFBD>Է<EFBFBD><D4B7><EFBFBD><EFBFBD>׻<EFBFBD><D7BB>Ʒ<EFBFBD><C6B7><EFBFBD><EFBFBD>˹ر<CBB9>
if (ContextObject && This->m_bTimeToKill == FALSE &&dwTrans==0) {
ContextObject->olps = NULL;
Mprintf("!!! RemoveStaleContext: %d \n", WSAGetLastError());
This->RemoveStaleContext(ContextObject);
}
SAFE_DELETE(OverlappedPlus);
continue;
}
if (!bError) {
//<2F><><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD>µ<EFBFBD><C2B5>̵߳<DFB3><CCB5>̵߳<DFB3><CCB5>̳߳<DFB3>
if (ulBusyThread == This->m_ulCurrentThread) {
if (ulBusyThread < This->m_ulThreadPoolMax) {
if (ContextObject != NULL) {
HANDLE hThread = (HANDLE)CreateThread(NULL,
0,
WorkThreadProc,
(void*)This,
0,
NULL);
This->AddWorkThread(hThread ? 1:0);
CloseHandle(hThread);
}
}
}
if (!bOk && dwIOError == WAIT_TIMEOUT) {
if (ContextObject == NULL) {
if (This->m_ulCurrentThread > This->m_ulThreadPoolMin) {
break;
}
bError = TRUE;
}
}
}
if (!bError && !This->m_bTimeToKill) {
if(bOk && OverlappedPlus!=NULL && ContextObject!=NULL) {
try {
This->HandleIO(OverlappedPlus->m_ioType, ContextObject, dwTrans, m_Dctx);
ContextObject = NULL;
} catch (...) {
Mprintf("This->HandleIO catched an error!!!");
}
}
}
SAFE_DELETE(OverlappedPlus);
}
timeEndPeriod(1);
SAFE_DELETE(OverlappedPlus);
InterlockedDecrement(&This->m_ulCurrentThread);
InterlockedDecrement(&This->m_ulBusyThread);
int n= This->AddWorkThread(-1);
if (n == 0) {
Mprintf("======> IOCPServer All WorkThreadProc done\n");
}
ZSTD_freeDCtx(m_Dctx);
Mprintf("======> IOCPServer WorkThreadProc end \n");
return 0;
}
2019-01-10 19:35:03 +08:00
//<2F>ڹ<EFBFBD><DAB9><EFBFBD><EFBFBD>߳<EFBFBD><DFB3>б<EFBFBD><D0B1><EFBFBD><EFBFBD><EFBFBD>
BOOL IOCPServer::HandleIO(IOType PacketFlags,PCONTEXT_OBJECT ContextObject, DWORD dwTrans, ZSTD_DCtx* ctx)
2019-01-10 19:35:03 +08:00
{
BOOL bRet = FALSE;
switch (PacketFlags) {
case IOInitialize:
bRet = OnClientInitializing(ContextObject, dwTrans);
break;
case IORead:
bRet = OnClientReceiving(ContextObject, dwTrans, ctx);
break;
case IOWrite:
bRet = OnClientPostSending(ContextObject, dwTrans);
break;
case IOIdle:
Mprintf("=> HandleIO PacketFlags= IOIdle\n");
break;
default:
break;
}
return bRet;
}
BOOL IOCPServer::OnClientInitializing(PCONTEXT_OBJECT ContextObject, DWORD dwTrans)
{
return TRUE;
}
// May be this function should be a member of `CONTEXT_OBJECT`.
BOOL ParseReceivedData(CONTEXT_OBJECT * ContextObject, DWORD dwTrans, pfnNotifyProc m_NotifyProc, ZSTD_DCtx* m_Dctx)
{
AUTO_TICK(40);
BOOL ret = 1;
try {
if (dwTrans == 0) { //<2F>Է<EFBFBD><D4B7>ر<EFBFBD><D8B1><EFBFBD><EFBFBD>׽<EFBFBD><D7BD><EFBFBD>
return FALSE;
}
//<2F><><EFBFBD><EFBFBD><EFBFBD>յ<EFBFBD><D5B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݿ<EFBFBD><DDBF><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Լ<EFBFBD><D4BC><EFBFBD><EFBFBD>ڴ<EFBFBD><DAB4><EFBFBD>wsabuff 8192
ContextObject->InCompressedBuffer.WriteBuffer((PBYTE)ContextObject->szBuffer,dwTrans);
//<2F><EFBFBD><E9BFB4><EFBFBD>ݰ<EFBFBD><DDB0><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
while (true) {
PR pr = ContextObject->Parse(ContextObject->InCompressedBuffer);
if (pr.IsFailed()) {
ContextObject->InCompressedBuffer.ClearBuffer();
break;
} else if (pr.IsNeedMore()) {
break;
} else if (pr.IsWinOSLogin()) {
ContextObject->InDeCompressedBuffer.ClearBuffer();
ULONG ulCompressedLength = 0;
ULONG ulOriginalLength = 0;
PBYTE CompressedBuffer = ContextObject->ReadBuffer(ulCompressedLength, ulOriginalLength);
ContextObject->InDeCompressedBuffer.WriteBuffer(CompressedBuffer, ulCompressedLength);
if (m_NotifyProc(ContextObject))
ret = CompressedBuffer[0] == TOKEN_LOGIN ? 999 : 1;
SAFE_DELETE_ARRAY(CompressedBuffer);
break;
}
ULONG ulPackTotalLength = 0;
ContextObject->InCompressedBuffer.CopyBuffer(&ulPackTotalLength, sizeof(ULONG), pr.Result);
//ȡ<><C8A1><EFBFBD><EFBFBD><EFBFBD>ݰ<EFBFBD><DDB0><EFBFBD><EFBFBD>ܳ<EFBFBD><DCB3><EFBFBD>5<EFBFBD>ֽڱ<D6BD>ʶ+4<>ֽ<EFBFBD><D6BD><EFBFBD><EFBFBD>ݰ<EFBFBD><DDB0>ܳ<EFBFBD><DCB3><EFBFBD>+4<>ֽ<EFBFBD>ԭʼ<D4AD><CABC><EFBFBD>ݳ<EFBFBD><DDB3><EFBFBD>
int bufLen = ContextObject->InCompressedBuffer.GetBufferLength();
if (ulPackTotalLength && bufLen >= ulPackTotalLength) {
ULONG ulCompressedLength = 0;
ULONG ulOriginalLength = 0;
PBYTE CompressedBuffer = ContextObject->ReadBuffer(ulCompressedLength, ulOriginalLength);
if (ContextObject->CompressMethod == COMPRESS_UNKNOWN) {
delete[] CompressedBuffer;
throw "Unknown method";
} else if (ContextObject->CompressMethod == COMPRESS_NONE) {
ContextObject->InDeCompressedBuffer.ClearBuffer();
ContextObject->Decode(CompressedBuffer, ulOriginalLength);
ContextObject->InDeCompressedBuffer.WriteBuffer(CompressedBuffer, ulOriginalLength);
if (m_NotifyProc(ContextObject))
ret = CompressedBuffer[0] == TOKEN_LOGIN ? 999 : 1;
SAFE_DELETE_ARRAY(CompressedBuffer);
continue;
}
bool usingZstd = ContextObject->CompressMethod == COMPRESS_ZSTD, zlibFailed = false;
PBYTE DeCompressedBuffer = new BYTE[ulOriginalLength]; //<2F><>ѹ<EFBFBD><D1B9><EFBFBD><EFBFBD><EFBFBD>ڴ<EFBFBD>
size_t iRet = usingZstd ?
Muncompress(DeCompressedBuffer, &ulOriginalLength, CompressedBuffer, ulCompressedLength) :
uncompress(DeCompressedBuffer, &ulOriginalLength, CompressedBuffer, ulCompressedLength);
if (usingZstd ? C_SUCCESS(iRet) : (S_OK==iRet)) {
ContextObject->InDeCompressedBuffer.ClearBuffer();
ContextObject->Decode(DeCompressedBuffer, ulOriginalLength);
ContextObject->InDeCompressedBuffer.WriteBuffer(DeCompressedBuffer, ulOriginalLength);
if (m_NotifyProc(ContextObject))
ret = DeCompressedBuffer[0] == TOKEN_LOGIN ? 999 : 1;
} else if (usingZstd) {
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>zlib<69><62>ѹ<EFBFBD><D1B9>
if (Z_OK == uncompress(DeCompressedBuffer, &ulOriginalLength, CompressedBuffer, ulCompressedLength)) {
ContextObject->CompressMethod = COMPRESS_ZLIB;
ContextObject->InDeCompressedBuffer.ClearBuffer();
ContextObject->Decode(DeCompressedBuffer, ulOriginalLength);
ContextObject->InDeCompressedBuffer.WriteBuffer(DeCompressedBuffer, ulOriginalLength);
if (m_NotifyProc(ContextObject))
ret = DeCompressedBuffer[0] == TOKEN_LOGIN ? 999 : 1;
} else {
zlibFailed = true;
ContextObject->CompressMethod = COMPRESS_UNKNOWN;
}
} else {
zlibFailed = true;
}
delete [] CompressedBuffer;
delete [] DeCompressedBuffer;
if (zlibFailed) {
Mprintf("[ERROR] ZLIB uncompress failed \n");
throw "Bad Buffer";
}
} else {
break;
}
}
} catch(...) {
Mprintf("[ERROR] OnClientReceiving catch an error \n");
ContextObject->InCompressedBuffer.ClearBuffer();
ContextObject->InDeCompressedBuffer.ClearBuffer();
}
return ret;
}
BOOL IOCPServer::OnClientReceiving(PCONTEXT_OBJECT ContextObject, DWORD dwTrans, ZSTD_DCtx* ctx)
{
if (FALSE == ParseReceivedData(ContextObject, dwTrans, m_NotifyProc, ctx)) {
RemoveStaleContext(ContextObject);
return FALSE;
}
PostRecv(ContextObject); //Ͷ<><CDB6><EFBFBD>µĽ<C2B5><C4BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݵ<EFBFBD><DDB5><EFBFBD><EFBFBD><EFBFBD>
return TRUE;
}
BOOL WriteContextData(CONTEXT_OBJECT* ContextObject, PBYTE szBuffer, size_t ulOriginalLength, ZSTD_CCtx* m_Cctx)
{
assert(ContextObject);
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>͵<EFBFBD><CDB5><EFBFBD><EFBFBD><EFBFBD>
int cmd = szBuffer[0];
if (ulOriginalLength < 100 && cmd != COMMAND_SCREEN_CONTROL && cmd != CMD_HEARTBEAT_ACK &&
cmd != CMD_DRAW_POINT && cmd != CMD_MOVEWINDOW && cmd != CMD_SET_SIZE) {
char buf[100] = { 0 };
if (ulOriginalLength == 1) {
sprintf_s(buf, "command %d", cmd);
} else {
memcpy(buf, szBuffer, ulOriginalLength);
}
2025-10-26 04:53:16 +08:00
Mprintf("[COMMAND] Send: %s\r\n", buf);
}
try {
do {
if (ulOriginalLength <= 0) return FALSE;
if (ContextObject->CompressMethod == COMPRESS_UNKNOWN) {
Mprintf("[ERROR] UNKNOWN compress method \n");
return FALSE;
} else if (ContextObject->CompressMethod == COMPRESS_NONE) {
Buffer tmp(szBuffer, ulOriginalLength);
szBuffer = tmp.Buf();
ContextObject->WriteBuffer(szBuffer, ulOriginalLength, ulOriginalLength, cmd);
break;
}
bool usingZstd = ContextObject->CompressMethod == COMPRESS_ZSTD;
unsigned long ulCompressedLength = usingZstd ?
ZSTD_compressBound(ulOriginalLength) : (double)ulOriginalLength * 1.001 + 12;
BYTE buf[1024];
LPBYTE CompressedBuffer = ulCompressedLength>1024 ? new BYTE[ulCompressedLength]:buf;
Buffer tmp(szBuffer, ulOriginalLength);
szBuffer = tmp.Buf();
ContextObject->Encode(szBuffer, ulOriginalLength);
if (!m_Cctx) ContextObject->Encode(szBuffer, usingZstd);
size_t iRet = usingZstd ?
Mcompress(CompressedBuffer, &ulCompressedLength, (LPBYTE)szBuffer, ulOriginalLength):
compress(CompressedBuffer, &ulCompressedLength, (LPBYTE)szBuffer, ulOriginalLength);
if (usingZstd ? C_FAILED(iRet) : (S_OK != iRet)) {
Mprintf("[ERROR] compress failed \n");
if (CompressedBuffer != buf) delete [] CompressedBuffer;
return FALSE;
}
ulCompressedLength = usingZstd ? iRet : ulCompressedLength;
ContextObject->WriteBuffer(CompressedBuffer, ulCompressedLength, ulOriginalLength, cmd);
if (CompressedBuffer != buf) delete [] CompressedBuffer;
} while (false);
return TRUE;
} catch (...) {
Mprintf("[ERROR] OnClientPreSending catch an error \n");
return FALSE;
}
}
BOOL IOCPServer::OnClientPreSending(CONTEXT_OBJECT* ContextObject, PBYTE szBuffer, size_t ulOriginalLength)
{
if (WriteContextData(ContextObject, szBuffer, ulOriginalLength)) {
OVERLAPPEDPLUS* OverlappedPlus = new OVERLAPPEDPLUS(IOWrite);
BOOL bOk = PostQueuedCompletionStatus(m_hCompletionPort, 0, (ULONG_PTR)ContextObject, &OverlappedPlus->m_ol);
if ( (!bOk && GetLastError() != ERROR_IO_PENDING) ) { //<2F><><EFBFBD><EFBFBD>Ͷ<EFBFBD><CDB6>ʧ<EFBFBD><CAA7>
int a = GetLastError();
Mprintf("!!! OnClientPreSending Ͷ<><CDB6><EFBFBD><EFBFBD>Ϣʧ<CFA2><CAA7>\n");
RemoveStaleContext(ContextObject);
SAFE_DELETE(OverlappedPlus);
return FALSE;
}
return TRUE;
}
return FALSE;
}
BOOL IOCPServer::OnClientPostSending(CONTEXT_OBJECT* ContextObject,ULONG ulCompletedLength)
{
2025-10-26 18:57:45 +08:00
CAutoCLock L(ContextObject->SendLock);
try {
DWORD ulFlags = MSG_PARTIAL;
ContextObject->OutCompressedBuffer.RemoveCompletedBuffer(ulCompletedLength); //<2F><><EFBFBD><EFBFBD><EFBFBD>ɵ<EFBFBD><C9B5><EFBFBD><EFBFBD>ݴ<EFBFBD><DDB4><EFBFBD><EFBFBD>ݽṹ<DDBD><E1B9B9>ȥ<EFBFBD><C8A5>
if (ContextObject->OutCompressedBuffer.GetBufferLength() == 0) {
ContextObject->OutCompressedBuffer.ClearBuffer();
return true; //<2F>ߵ<EFBFBD><DFB5><EFBFBD><EFBFBD><EFBFBD>˵<EFBFBD><CBB5><EFBFBD><EFBFBD><EFBFBD>ǵ<EFBFBD><C7B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȫ<EFBFBD><C8AB><EFBFBD><EFBFBD>
} else {
OVERLAPPEDPLUS * OverlappedPlus = new OVERLAPPEDPLUS(IOWrite); //<2F><><EFBFBD><EFBFBD>û<EFBFBD><C3BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD>Ǽ<EFBFBD><C7BC><EFBFBD>Ͷ<EFBFBD><CDB6> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
ContextObject->wsaOutBuffer.buf = (char*)ContextObject->OutCompressedBuffer.GetBuffer(0);
ContextObject->wsaOutBuffer.len = ContextObject->OutCompressedBuffer.GetBufferLength();
int iOk = WSASend(ContextObject->sClientSocket, &ContextObject->wsaOutBuffer,1,
NULL, ulFlags,&OverlappedPlus->m_ol, NULL);
if ( iOk == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING ) {
int a = GetLastError();
2025-10-26 18:57:45 +08:00
Mprintf("!!! OnClientPostSending Ͷ<><CDB6><EFBFBD><EFBFBD>Ϣʧ<CFA2><CAA7>: %d\n", a);
RemoveStaleContext(ContextObject);
SAFE_DELETE(OverlappedPlus);
2025-10-26 18:57:45 +08:00
return FALSE;
}
2025-10-26 18:57:45 +08:00
return TRUE;
}
} catch(...) {
Mprintf("[ERROR] OnClientPostSending catch an error \n");
}
return FALSE;
}
DWORD IOCPServer::ListenThreadProc(LPVOID lParam) //<2F><><EFBFBD><EFBFBD><EFBFBD>߳<EFBFBD>
{
IOCPServer* This = (IOCPServer*)(lParam);
WSANETWORKEVENTS NetWorkEvents;
while(!This->m_bTimeToKill) {
if (WaitForSingleObject(This->m_hKillEvent, 100) == WAIT_OBJECT_0)
break;
DWORD dwRet;
dwRet = WSAWaitForMultipleEvents(1,&This->m_hListenEvent,FALSE,100,FALSE);
if (dwRet == WSA_WAIT_TIMEOUT)
continue;
int iRet = WSAEnumNetworkEvents(This->m_sListenSocket,
//<2F><><EFBFBD><EFBFBD><EFBFBD>¼<EFBFBD><C2BC><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD>Ǿͽ<C7BE><CDBD><EFBFBD><EFBFBD>¼<EFBFBD>ת<EFBFBD><D7AA><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>¼<EFBFBD> <20><><EFBFBD><EFBFBD> <20>ж<EFBFBD>
This->m_hListenEvent,
&NetWorkEvents);
if (iRet == SOCKET_ERROR)
break;
if (NetWorkEvents.lNetworkEvents & FD_ACCEPT) {
if (NetWorkEvents.iErrorCode[FD_ACCEPT_BIT] == 0) {
This->OnAccept();
} else {
break;
}
}
}
This->m_hListenThread = NULL;
return 0;
}
void IOCPServer::OnAccept()
{
SOCKADDR_IN ClientAddr = {0};
SOCKET sClientSocket = INVALID_SOCKET;
int iLen = sizeof(SOCKADDR_IN);
sClientSocket = accept(m_sListenSocket,
(sockaddr*)&ClientAddr,
&iLen); //ͨ<><CDA8><EFBFBD><EFBFBD><EFBFBD>ǵļ<C7B5><C4BC><EFBFBD><EFBFBD>׽<EFBFBD><D7BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD><EFBFBD>֮<EFBFBD>ź<EFBFBD>ͨ<EFBFBD>ŵ<EFBFBD><C5B5>׽<EFBFBD><D7BD><EFBFBD>
if (sClientSocket == SOCKET_ERROR) {
return;
}
//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊÿһ<C3BF><D2BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ź<EFBFBD>ά<EFBFBD><CEAC><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD><EFBFBD>֮<EFBFBD><D6AE><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݽṹ<DDBD><E1B9B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD>û<EFBFBD><C3BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>±<EFBFBD><C2B1><EFBFBD><EFBFBD><EFBFBD>
PCONTEXT_OBJECT ContextObject = AllocateContext(sClientSocket); // Context
if (ContextObject == NULL) {
closesocket(sClientSocket);
sClientSocket = INVALID_SOCKET;
return;
}
ContextObject->sClientSocket = sClientSocket;
ContextObject->wsaInBuf.buf = (char*)ContextObject->szBuffer;
ContextObject->wsaInBuf.len = sizeof(ContextObject->szBuffer);
HANDLE Handle = CreateIoCompletionPort((HANDLE)sClientSocket, m_hCompletionPort, (ULONG_PTR)ContextObject, 0);
if (Handle!=m_hCompletionPort) {
delete ContextObject;
ContextObject = NULL;
if (sClientSocket!=INVALID_SOCKET) {
closesocket(sClientSocket);
sClientSocket = INVALID_SOCKET;
}
return;
}
//<2F><><EFBFBD><EFBFBD><EFBFBD>׽<EFBFBD><D7BD>ֵ<EFBFBD>ѡ<EFBFBD> Set KeepAlive <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> SO_KEEPALIVE
//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ӽ<EFBFBD><D3BC><EFBFBD><EFBFBD>Է<EFBFBD><D4B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƿ<EFBFBD><C7B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2Сʱ<D0A1><CAB1><EFBFBD>ڴ<EFBFBD><DAB4>׽ӿڵ<D3BF><DAB5><EFBFBD>һ<EFBFBD><D2BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û
//<2F><><EFBFBD><EFBFBD><EFBFBD>ݽ<EFBFBD><DDBD><EFBFBD><EFBFBD><EFBFBD>TCP<43><50><EFBFBD>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD>Է<EFBFBD> <20><>һ<EFBFBD><D2BB><EFBFBD><EFBFBD><EFBFBD>ִ<EFBFBD><D6B4><EFBFBD>
m_ulKeepLiveTime = 3;
const BOOL bKeepAlive = TRUE;
setsockopt(ContextObject->sClientSocket,SOL_SOCKET,SO_KEEPALIVE,(char*)&bKeepAlive,sizeof(bKeepAlive));
//<2F><><EFBFBD>ó<EFBFBD>ʱ<EFBFBD><CAB1>ϸ<EFBFBD><CFB8>Ϣ
tcp_keepalive KeepAlive;
KeepAlive.onoff = 1; // <20><><EFBFBD>ñ<EFBFBD><C3B1><EFBFBD>
KeepAlive.keepalivetime = m_ulKeepLiveTime; //<2F><><EFBFBD><EFBFBD>3<EFBFBD><33><EFBFBD><EFBFBD>û<EFBFBD><C3BB><EFBFBD><EFBFBD><EFBFBD>ݣ<EFBFBD><DDA3>ͷ<EFBFBD><CDB7><EFBFBD>̽<EFBFBD><CCBD><EFBFBD><EFBFBD>
KeepAlive.keepaliveinterval = 1000 * 10; //<2F><><EFBFBD>Լ<EFBFBD><D4BC><EFBFBD>Ϊ10<31><30> Resend if No-Reply
WSAIoctl(ContextObject->sClientSocket, SIO_KEEPALIVE_VALS,&KeepAlive,sizeof(KeepAlive),
NULL,0,(unsigned long *)&bKeepAlive,0,NULL);
//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD><CAB1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͻ<EFBFBD><CDBB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>߻<EFBFBD><DFBB>ϵ<EFBFBD><CFB5>ȷ<EFBFBD><C8B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ͽ<EFBFBD><CFBF><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD><C3BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>SO_KEEPALIVEѡ<45>
//<2F><><EFBFBD><EFBFBD>һֱ<D2BB><D6B1><EFBFBD>ر<EFBFBD>SOCKET<45><54><EFBFBD><EFBFBD>Ϊ<EFBFBD>ϵĵ<CFB5><C4B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĭ<EFBFBD><C4AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Сʱʱ<CAB1><CAB1>̫<EFBFBD><CCAB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ǿ<EFBFBD><C7BE><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֵ
EnterCriticalSection(&m_cs);
m_ContextConnectionList.AddTail(ContextObject); //<2F><><EFBFBD><EFBFBD><EBB5BD><EFBFBD>ǵ<EFBFBD><C7B5>ڴ<EFBFBD><DAB4>б<EFBFBD><D0B1><EFBFBD>
LeaveCriticalSection(&m_cs);
OVERLAPPEDPLUS *OverlappedPlus = new OVERLAPPEDPLUS(IOInitialize); //ע<><D7A2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ص<EFBFBD>IO<49><4F><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>û<EFBFBD><C3BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
BOOL bOk = PostQueuedCompletionStatus(m_hCompletionPort, 0, (ULONG_PTR)ContextObject, &OverlappedPlus->m_ol); // <20><><EFBFBD><EFBFBD><EFBFBD>߳<EFBFBD>
//<2F><>Ϊ<EFBFBD><CEAA><EFBFBD>ǽ<EFBFBD><C7BD>ܵ<EFBFBD><DCB5><EFBFBD>һ<EFBFBD><D2BB><EFBFBD>û<EFBFBD><C3BB><EFBFBD><EFBFBD>ߵ<EFBFBD><DFB5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ô<EFBFBD><C3B4><EFBFBD>Ǿͽ<C7BE><CDBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>͸<EFBFBD><CDB8><EFBFBD><EFBFBD>ǵ<EFBFBD><C7B5><EFBFBD><EFBFBD>ɶ˿<C9B6> <20><><EFBFBD><EFBFBD><EFBFBD>ǵĹ<C7B5><C4B9><EFBFBD><EFBFBD>̴߳<DFB3><CCB4><EFBFBD><EFBFBD><EFBFBD>
if ( (!bOk && GetLastError() != ERROR_IO_PENDING)) { //<2F><><EFBFBD><EFBFBD>Ͷ<EFBFBD><CDB6>ʧ<EFBFBD><CAA7>
int a = GetLastError();
Mprintf("!!! OnAccept Ͷ<><CDB6><EFBFBD><EFBFBD>Ϣʧ<CFA2><CAA7>\n");
RemoveStaleContext(ContextObject);
SAFE_DELETE(OverlappedPlus);
return;
}
PostRecv(ContextObject);
}
VOID IOCPServer::PostRecv(CONTEXT_OBJECT* ContextObject)
{
//<2F><><EFBFBD><EFBFBD><EFBFBD>ǵĸ<C7B5><C4B8><EFBFBD><EFBFBD>ߵ<EFBFBD><DFB5>û<EFBFBD><C3BB><EFBFBD>Ͷ<EFBFBD><CDB6>һ<EFBFBD><D2BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݵ<EFBFBD><DDB5><EFBFBD><EFBFBD><EFBFBD>
// <20><><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD><C3BB>ĵ<EFBFBD>һ<EFBFBD><D2BB><EFBFBD><EFBFBD><EFBFBD>ݰ<EFBFBD><DDB0><EFBFBD><EFBFBD><EFBFBD>Ҳ<EFBFBD>;<EFBFBD><CDBE>DZ<EFBFBD><C7B1>ض˵ĵ<CBB5>½<EFBFBD><C2BD><EFBFBD>󵽴<EFBFBD><F3B5BDB4><EFBFBD><EFBFBD>ǵĹ<C7B5><C4B9><EFBFBD><EFBFBD>߳̾<DFB3>
// <20><><EFBFBD><EFBFBD>Ӧ,<2C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ProcessIOMessage<67><65><EFBFBD><EFBFBD>
OVERLAPPEDPLUS * OverlappedPlus = new OVERLAPPEDPLUS(IORead);
ContextObject->olps = OverlappedPlus;
DWORD dwReturn;
ULONG ulFlags = MSG_PARTIAL;
int iOk = WSARecv(ContextObject->sClientSocket, &ContextObject->wsaInBuf,
1,&dwReturn, &ulFlags,&OverlappedPlus->m_ol, NULL);
if (iOk == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) {
int a = GetLastError();
Mprintf("!!! PostRecv Ͷ<><CDB6><EFBFBD><EFBFBD>Ϣʧ<CFA2><CAA7>\n");
RemoveStaleContext(ContextObject);
SAFE_DELETE(OverlappedPlus);
}
}
2025-06-08 15:38:41 +08:00
PCONTEXT_OBJECT IOCPServer::AllocateContext(SOCKET s)
{
PCONTEXT_OBJECT ContextObject = NULL;
CLock cs(m_cs);
if (m_ContextConnectionList.GetCount() >= m_ulMaxConnections) {
return NULL;
}
2024-12-31 03:11:26 +08:00
ContextObject = !m_ContextFreePoolList.IsEmpty() ? m_ContextFreePoolList.RemoveHead() : new CONTEXT_OBJECT;
if (ContextObject != NULL) {
ContextObject->InitMember(s, this);
}
2019-01-10 19:35:03 +08:00
return ContextObject;
}
VOID IOCPServer::RemoveStaleContext(CONTEXT_OBJECT* ContextObject)
{
EnterCriticalSection(&m_cs);
auto find = m_ContextConnectionList.Find(ContextObject);
LeaveCriticalSection(&m_cs);
if (find) { //<2F><><EFBFBD>ڴ<EFBFBD><DAB4>в<EFBFBD><D0B2>Ҹ<EFBFBD><D2B8>û<EFBFBD><C3BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݽṹ
m_OfflineProc(ContextObject);
CancelIo((HANDLE)ContextObject->sClientSocket); //ȡ<><C8A1><EFBFBD>ڵ<EFBFBD>ǰ<EFBFBD>׽<EFBFBD><D7BD>ֵ<EFBFBD><D6B5>첽IO -->PostRecv
closesocket(ContextObject->sClientSocket); //<2F>ر<EFBFBD><D8B1>׽<EFBFBD><D7BD><EFBFBD>
ContextObject->sClientSocket = INVALID_SOCKET;
while (!HasOverlappedIoCompleted((LPOVERLAPPED)ContextObject)) { //<2F>жϻ<D0B6><CFBB><EFBFBD>û<EFBFBD><C3BB><EFBFBD>첽IO<49><4F><EFBFBD><EFBFBD><EFBFBD>ڵ<EFBFBD>ǰ<EFBFBD>׽<EFBFBD><D7BD><EFBFBD><EFBFBD><EFBFBD>
Sleep(0);
}
MoveContextToFreePoolList(ContextObject); //<2F><><EFBFBD><EFBFBD><EFBFBD>ڴ<EFBFBD><DAB4><EFBFBD><E1B9B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڴ<EFBFBD><DAB4><EFBFBD>
}
}
VOID IOCPServer::MoveContextToFreePoolList(CONTEXT_OBJECT* ContextObject)
{
CLock cs(m_cs);
POSITION Pos = m_ContextConnectionList.Find(ContextObject);
if (Pos) {
ContextObject->InCompressedBuffer.ClearBuffer();
ContextObject->InDeCompressedBuffer.ClearBuffer();
ContextObject->OutCompressedBuffer.ClearBuffer();
memset(ContextObject->szBuffer,0,8192);
m_ContextFreePoolList.AddTail(ContextObject); //<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڴ<EFBFBD><DAB4><EFBFBD>
m_ContextConnectionList.RemoveAt(Pos); //<2F><><EFBFBD>ڴ<EFBFBD><DAB4><EFBFBD><E1B9B9><EFBFBD>Ƴ<EFBFBD>
}
}
2024-12-31 03:11:26 +08:00
void IOCPServer::UpdateMaxConnection(int maxConn)
{
CLock cs(m_cs);
m_ulMaxConnections = maxConn;
2024-12-31 03:11:26 +08:00
}