mirror of
https://github.com/Mauler125/r5sdk.git
synced 2025-02-09 19:15:03 +01:00
Tier2: create modular WebSocket system
This code was actually part of the LiveAPI system, but there were many opportunities to make this particular code modular, so it has been decoupled and moved to Tier2. The LiveAPI system will soon use this class instead. The implementation has also been improved by adding dedicated routines for updating socket parameters, disconnecting/reconnecting and destroying sockets. This commit also removes legacy workaround code in UtlVector which was used for before we had early enough access to the game's memalloc singleton. This code was no longer used. This commit also implements the CUtlStringList class, which is now used for the new websocket class to split each socket connection up by a comma delimiter.
This commit is contained in:
parent
1c32339305
commit
e02f8f36fe
@ -90,6 +90,10 @@ ssize_t V_vsnprintfRet(char* pDest, size_t maxLen, const char* pFormat, va_list
|
||||
// Strip white space at the beginning and end of a string
|
||||
ssize_t V_StrTrim(char* pStr);
|
||||
|
||||
class CUtlStringList;
|
||||
void V_SplitString2(const char* pString, const char** pSeparators, ssize_t nSeparators, CUtlStringList& outStrings);
|
||||
void V_SplitString(const char* pString, const char* pSeparator, CUtlStringList& outStrings);
|
||||
|
||||
int V_UTF8ToUnicode(const char* pUTF8, wchar_t* pwchDest, int cubDestSizeInBytes);
|
||||
int V_UnicodeToUTF8(const wchar_t* pUnicode, char* pUTF8, int cubDestSizeInBytes);
|
||||
|
||||
|
@ -61,14 +61,6 @@ public:
|
||||
// Copy the array.
|
||||
CUtlVector<T, A>& operator=(const CUtlVector<T, A>& other);
|
||||
|
||||
// NOTE<R5SDK>:
|
||||
// Do not call after initialization or after adding elements.
|
||||
// This is added so it could be constructed nicely. Since the
|
||||
// game executable in monolithic, we couldn't import the malloc
|
||||
// functions, and thus not construct automatically when using
|
||||
// the game's memalloc singleton.
|
||||
void Init();
|
||||
|
||||
// element access
|
||||
T& operator[](int i);
|
||||
const T& operator[](int i) const;
|
||||
@ -664,15 +656,6 @@ inline CUtlVector<T, A>& CUtlVector<T, A>::operator=(const CUtlVector<T, A>& oth
|
||||
return *this;
|
||||
}
|
||||
|
||||
template< typename T, class A >
|
||||
void CUtlVector<T, A>::Init()
|
||||
{
|
||||
m_Memory.m_pMemory = nullptr;
|
||||
m_Memory.m_nAllocationCount = 0;
|
||||
m_Memory.m_nGrowSize = 0;
|
||||
m_Size = 0;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// element access
|
||||
//-----------------------------------------------------------------------------
|
||||
@ -1417,24 +1400,23 @@ void CUtlVector<T, A>::Validate(CValidator& validator, char* pchName)
|
||||
|
||||
// A vector class for storing pointers, so that the elements pointed to by the pointers are deleted
|
||||
// on exit.
|
||||
template<class T> class CUtlVectorAutoPurge : public CUtlVector< T, CUtlMemory< T, int> >
|
||||
template<class T> class CUtlVectorAutoPurge : public CUtlVector< T >
|
||||
{
|
||||
public:
|
||||
~CUtlVectorAutoPurge(void)
|
||||
{
|
||||
this->PurgeAndDeleteElements();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// easy string list class with dynamically allocated strings. For use with V_SplitString, etc.
|
||||
// Frees the dynamic strings in destructor.
|
||||
class CUtlStringList : public CUtlVectorAutoPurge< char*>
|
||||
class CUtlStringList : public CUtlVectorAutoPurge<char*>
|
||||
{
|
||||
public:
|
||||
void CopyAndAddToTail(char const* pString) // clone the string and add to the end
|
||||
{
|
||||
char* pNewStr = new char[1 + strlen(pString)];
|
||||
char* const pNewStr = new char[strlen(pString) + 1];
|
||||
strcpy(pNewStr, pString);
|
||||
AddToTail(pNewStr);
|
||||
}
|
||||
@ -1446,27 +1428,25 @@ public:
|
||||
|
||||
CUtlStringList() {}
|
||||
|
||||
// !TODO:
|
||||
CUtlStringList(char const* pString, char const* pSeparator)
|
||||
{
|
||||
SplitString(pString, pSeparator);
|
||||
}
|
||||
|
||||
//CUtlStringList(char const* pString, char const* pSeparator)
|
||||
//{
|
||||
// SplitString(pString, pSeparator);
|
||||
//}
|
||||
CUtlStringList(char const* pString, const char** pSeparators, ssize_t nSeparators)
|
||||
{
|
||||
SplitString2(pString, pSeparators, nSeparators);
|
||||
}
|
||||
|
||||
//CUtlStringList(char const* pString, const char** pSeparators, int nSeparators)
|
||||
//{
|
||||
// SplitString2(pString, pSeparators, nSeparators);
|
||||
//}
|
||||
void SplitString(char const* pString, char const* pSeparator)
|
||||
{
|
||||
V_SplitString(pString, pSeparator, *this);
|
||||
}
|
||||
|
||||
//void SplitString(char const* pString, char const* pSeparator)
|
||||
//{
|
||||
// V_SplitString(pString, pSeparator, *this);
|
||||
//}
|
||||
|
||||
//void SplitString2(char const* pString, const char** pSeparators, int nSeparators)
|
||||
//{
|
||||
// V_SplitString2(pString, pSeparators, nSeparators, *this);
|
||||
//}
|
||||
void SplitString2(char const* pString, const char** pSeparators, ssize_t nSeparators)
|
||||
{
|
||||
V_SplitString2(pString, pSeparators, nSeparators, *this);
|
||||
}
|
||||
private:
|
||||
CUtlStringList(const CUtlStringList& other); // copying directly will cause double-release of the same strings; maybe we need to do a deep copy, but unless and until such need arises, this will guard against double-release
|
||||
};
|
||||
|
101
r5dev/public/tier2/websocket.h
Normal file
101
r5dev/public/tier2/websocket.h
Normal file
@ -0,0 +1,101 @@
|
||||
#ifndef TIER2_WEBSOCKETCREATOR_H
|
||||
#define TIER2_WEBSOCKETCREATOR_H
|
||||
|
||||
#define WEBSOCKET_DEFAULT_BUFFER_SIZE 1024
|
||||
|
||||
struct ProtoWebSocketRefT;
|
||||
|
||||
class CWebSocket
|
||||
{
|
||||
public:
|
||||
enum ConnState_e
|
||||
{
|
||||
CS_CREATE = 0,
|
||||
|
||||
CS_CONNECTED,
|
||||
CS_LISTENING,
|
||||
|
||||
CS_DESTROYED,
|
||||
|
||||
CS_RETRY,
|
||||
CS_UNAVAIL
|
||||
};
|
||||
|
||||
struct ConnParams_s
|
||||
{
|
||||
ConnParams_s()
|
||||
{
|
||||
bufSize = WEBSOCKET_DEFAULT_BUFFER_SIZE;
|
||||
retryTime = 0.0f;
|
||||
maxRetries = 0;
|
||||
|
||||
timeOut = -1;
|
||||
keepAlive = -1;
|
||||
laxSSL = 0;
|
||||
}
|
||||
|
||||
int32_t bufSize;
|
||||
float retryTime;
|
||||
int32_t maxRetries;
|
||||
|
||||
int32_t timeOut;
|
||||
int32_t keepAlive;
|
||||
int32_t laxSSL;
|
||||
};
|
||||
|
||||
struct ConnContext_s
|
||||
{
|
||||
ConnContext_s(const char* const addr)
|
||||
{
|
||||
webSocket = nullptr;
|
||||
address = addr;
|
||||
|
||||
state = CS_CREATE;
|
||||
|
||||
tryCount = 0;
|
||||
lastQueryTime = 0;
|
||||
}
|
||||
|
||||
bool Connect(const double queryTime, const ConnParams_s& params);
|
||||
bool Status(const double queryTime);
|
||||
|
||||
void SetParams(const ConnParams_s& params);
|
||||
|
||||
void Disconnect();
|
||||
void Reconnect();
|
||||
void Destroy();
|
||||
|
||||
ProtoWebSocketRefT* webSocket;
|
||||
ConnState_e state;
|
||||
|
||||
int tryCount;
|
||||
double lastQueryTime;
|
||||
|
||||
CUtlString address;
|
||||
};
|
||||
|
||||
CWebSocket();
|
||||
|
||||
bool Init(const char* const addressList, const ConnParams_s& params, const char*& initError);
|
||||
void Shutdown();
|
||||
|
||||
bool SetupFromList(const char* const addressList);
|
||||
void UpdateParams(const ConnParams_s& params);
|
||||
|
||||
void Update();
|
||||
void DeleteUnavailable();
|
||||
|
||||
void DestroyAll();
|
||||
void ReconnectAll();
|
||||
void ClearAll();
|
||||
|
||||
void SendData(const char* const dataBuf, const int32_t dataSize);
|
||||
bool IsInitialized() const;
|
||||
|
||||
private:
|
||||
bool m_initialized;
|
||||
ConnParams_s m_connParams;
|
||||
CUtlVector<ConnContext_s> m_addressList;
|
||||
};
|
||||
|
||||
#endif // TIER2_WEBSOCKETCREATOR_H
|
@ -18,6 +18,20 @@ static int FastToLower(char c)
|
||||
return i;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Allocate a string buffer
|
||||
//-----------------------------------------------------------------------------
|
||||
char* AllocString(const char* pStr, ssize_t nMaxChars)
|
||||
{
|
||||
const ssize_t allocLen = (nMaxChars == -1)
|
||||
? strlen(pStr) + 1
|
||||
: Min((ssize_t)strlen(pStr), nMaxChars) + 1;
|
||||
|
||||
char* const pOut = new char[allocLen];
|
||||
V_strncpy(pOut, pStr, allocLen);
|
||||
|
||||
return pOut;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// A special high-performance case-insensitive compare function
|
||||
@ -404,6 +418,57 @@ ssize_t V_StrTrim(char* pStr)
|
||||
return pDest - pStart;
|
||||
}
|
||||
|
||||
void V_SplitString2(const char* pString, const char** pSeparators, ssize_t nSeparators, CUtlStringList& outStrings)
|
||||
{
|
||||
outStrings.Purge();
|
||||
const char* pCurPos = pString;
|
||||
|
||||
while (true)
|
||||
{
|
||||
ssize_t iFirstSeparator = -1;
|
||||
const char* pFirstSeparator = nullptr;
|
||||
|
||||
for (ssize_t i = 0; i < nSeparators; i++)
|
||||
{
|
||||
const char* const pTest = V_stristr(pCurPos, pSeparators[i]);
|
||||
|
||||
if (pTest && (!pFirstSeparator || pTest < pFirstSeparator))
|
||||
{
|
||||
iFirstSeparator = i;
|
||||
pFirstSeparator = pTest;
|
||||
}
|
||||
}
|
||||
|
||||
if (pFirstSeparator)
|
||||
{
|
||||
// Split on this separator and continue on.
|
||||
const ssize_t separatorLen = strlen(pSeparators[iFirstSeparator]);
|
||||
|
||||
if (pFirstSeparator > pCurPos)
|
||||
{
|
||||
outStrings.AddToTail(AllocString(pCurPos, pFirstSeparator - pCurPos));
|
||||
}
|
||||
|
||||
pCurPos = pFirstSeparator + separatorLen;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Copy the rest of the string
|
||||
if (strlen(pCurPos))
|
||||
{
|
||||
outStrings.AddToTail(AllocString(pCurPos, -1));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void V_SplitString(const char* pString, const char* pSeparator, CUtlStringList& outStrings)
|
||||
{
|
||||
V_SplitString2(pString, &pSeparator, 1, outStrings);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Converts a UTF-8 string into a unicode string
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -9,6 +9,7 @@ add_sources( SOURCE_GROUP "Utility"
|
||||
"meshutils.cpp"
|
||||
"renderutils.cpp"
|
||||
"socketcreator.cpp"
|
||||
"websocket.cpp"
|
||||
)
|
||||
|
||||
file( GLOB TIER2_PUBLIC_HEADERS
|
||||
@ -20,4 +21,12 @@ add_sources( SOURCE_GROUP "Public"
|
||||
|
||||
end_sources()
|
||||
|
||||
target_include_directories( ${PROJECT_NAME} PRIVATE "${ENGINE_SOURCE_DIR}/tier0/" "${ENGINE_SOURCE_DIR}/tier1/" )
|
||||
target_include_directories( ${PROJECT_NAME} PRIVATE
|
||||
"${ENGINE_SOURCE_DIR}/tier0/"
|
||||
"${ENGINE_SOURCE_DIR}/tier1/"
|
||||
)
|
||||
|
||||
target_include_directories( ${PROJECT_NAME} PRIVATE
|
||||
"${THIRDPARTY_SOURCE_DIR}/dirtysdk/include/"
|
||||
"${THIRDPARTY_SOURCE_DIR}/ea/"
|
||||
)
|
||||
|
317
r5dev/tier2/websocket.cpp
Normal file
317
r5dev/tier2/websocket.cpp
Normal file
@ -0,0 +1,317 @@
|
||||
//===========================================================================//
|
||||
//
|
||||
// Purpose: WebSocket implementation
|
||||
//
|
||||
//===========================================================================//
|
||||
#include "tier2/websocket.h"
|
||||
|
||||
#include "DirtySDK/dirtysock.h"
|
||||
#include "DirtySDK/dirtysock/netconn.h"
|
||||
#include "DirtySDK/proto/protossl.h"
|
||||
#include "DirtySDK/proto/protowebsocket.h"
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// constructors/destructors
|
||||
//-----------------------------------------------------------------------------
|
||||
CWebSocket::CWebSocket()
|
||||
{
|
||||
m_initialized = false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: initialization of the socket system
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CWebSocket::Init(const char* const addressList, const ConnParams_s& params, const char*& initError)
|
||||
{
|
||||
if (!NetConnStatus('open', 0, NULL, 0))
|
||||
{
|
||||
initError = "Network connection module not initialized";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SetupFromList(addressList))
|
||||
{
|
||||
initError = (*addressList)
|
||||
? "Failed to parse address list"
|
||||
: "Address list is empty";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
m_connParams = params;
|
||||
m_initialized = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: shutdown of the socket system
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWebSocket::Shutdown()
|
||||
{
|
||||
m_initialized = false;
|
||||
ClearAll();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: setup connection list, returns false if connection list is empty
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CWebSocket::SetupFromList(const char* const addressList)
|
||||
{
|
||||
const CUtlStringList addresses(addressList, ",");
|
||||
|
||||
FOR_EACH_VEC(addresses, i)
|
||||
{
|
||||
const ConnContext_s conn(addresses[i]);
|
||||
m_addressList.AddToTail(conn);
|
||||
}
|
||||
|
||||
return addresses.Count() != 0;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: update parameters for each connection
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWebSocket::UpdateParams(const ConnParams_s& params)
|
||||
{
|
||||
m_connParams = params;
|
||||
|
||||
for (ConnContext_s& conn : m_addressList)
|
||||
{
|
||||
if (conn.webSocket)
|
||||
conn.SetParams(params);
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: socket state machine
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWebSocket::Update()
|
||||
{
|
||||
if (!IsInitialized())
|
||||
return;
|
||||
|
||||
const double queryTime = Plat_FloatTime();
|
||||
|
||||
for (ConnContext_s& conn : m_addressList)
|
||||
{
|
||||
if (conn.webSocket)
|
||||
ProtoWebSocketUpdate(conn.webSocket);
|
||||
|
||||
if (conn.state == CS_CREATE || conn.state == CS_RETRY)
|
||||
{
|
||||
conn.Connect(queryTime, m_connParams);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (conn.state == CS_CONNECTED || conn.state == CS_LISTENING)
|
||||
{
|
||||
conn.Status(queryTime);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (conn.state == CS_DESTROYED)
|
||||
{
|
||||
if (conn.tryCount > m_connParams.maxRetries)
|
||||
{
|
||||
// All retry attempts have been used; mark unavailable for deletion
|
||||
conn.state = CS_UNAVAIL;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Mark as retry, this will recreate the socket and reattempt
|
||||
// the connection
|
||||
conn.state = CS_RETRY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DeleteUnavailable();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: delete all connections marked unavailable
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWebSocket::DeleteUnavailable()
|
||||
{
|
||||
FOR_EACH_VEC_BACK(m_addressList, i)
|
||||
{
|
||||
if (m_addressList[i].state == CS_UNAVAIL)
|
||||
{
|
||||
m_addressList.FastRemove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: destroy all connections
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWebSocket::DestroyAll()
|
||||
{
|
||||
for (ConnContext_s& conn : m_addressList)
|
||||
{
|
||||
conn.Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: reconnect all connections
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWebSocket::ReconnectAll()
|
||||
{
|
||||
for (ConnContext_s& conn : m_addressList)
|
||||
{
|
||||
conn.Reconnect();
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: destroy and purge all connections
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWebSocket::ClearAll()
|
||||
{
|
||||
DestroyAll();
|
||||
m_addressList.Purge();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: send data to all sockets
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWebSocket::SendData(const char* const dataBuf, const int32_t dataSize)
|
||||
{
|
||||
if (!IsInitialized())
|
||||
return;
|
||||
|
||||
for (ConnContext_s& conn : m_addressList)
|
||||
{
|
||||
if (conn.state != CS_LISTENING)
|
||||
continue;
|
||||
|
||||
if (ProtoWebSocketSend(conn.webSocket, dataBuf, dataSize) < 0)
|
||||
{
|
||||
conn.Destroy(); // Reattempt the connection for this socket
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: returns whether the socket system is enabled and able to run
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CWebSocket::IsInitialized() const
|
||||
{
|
||||
return m_initialized;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: connect to a socket
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CWebSocket::ConnContext_s::Connect(const double queryTime, const ConnParams_s& params)
|
||||
{
|
||||
const double retryTimeTotal = lastQueryTime + params.retryTime;
|
||||
const double currTime = Plat_FloatTime();
|
||||
|
||||
if (retryTimeTotal > currTime)
|
||||
return false; // Still within retry period
|
||||
|
||||
tryCount++;
|
||||
webSocket = ProtoWebSocketCreate(params.bufSize);
|
||||
|
||||
if (!webSocket)
|
||||
{
|
||||
state = CS_UNAVAIL;
|
||||
return false;
|
||||
}
|
||||
|
||||
SetParams(params);
|
||||
|
||||
if (ProtoWebSocketConnect(webSocket, address.String()) != NULL)
|
||||
{
|
||||
// Failure
|
||||
Destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
state = CS_CONNECTED;
|
||||
lastQueryTime = queryTime;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: check the connection status and destroy if not connected (-1)
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CWebSocket::ConnContext_s::Status(const double queryTime)
|
||||
{
|
||||
const int32_t status = ProtoWebSocketStatus(webSocket, 'stat', NULL, 0);
|
||||
|
||||
if (status == -1)
|
||||
{
|
||||
Destroy();
|
||||
lastQueryTime = queryTime;
|
||||
|
||||
return false;
|
||||
}
|
||||
else if (!status)
|
||||
{
|
||||
lastQueryTime = queryTime;
|
||||
return false;
|
||||
}
|
||||
|
||||
tryCount = 0;
|
||||
state = CS_LISTENING;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: set parameters for this socket
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWebSocket::ConnContext_s::SetParams(const ConnParams_s& params)
|
||||
{
|
||||
Assert(webSocket);
|
||||
|
||||
if (params.timeOut > 0)
|
||||
{
|
||||
ProtoWebSocketControl(webSocket, 'time', params.timeOut, 0, NULL);
|
||||
}
|
||||
|
||||
if (params.keepAlive > 0)
|
||||
{
|
||||
ProtoWebSocketControl(webSocket, 'keep', params.keepAlive, 0, NULL);
|
||||
}
|
||||
|
||||
ProtoWebSocketControl(webSocket, 'ncrt', params.laxSSL, 0, NULL);
|
||||
ProtoWebSocketUpdate(webSocket);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: disconnect and mark socket as unavailable for removal
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWebSocket::ConnContext_s::Disconnect()
|
||||
{
|
||||
ProtoWebSocketDisconnect(webSocket);
|
||||
ProtoWebSocketUpdate(webSocket);
|
||||
ProtoWebSocketDestroy(webSocket);
|
||||
|
||||
webSocket = nullptr;
|
||||
state = CS_UNAVAIL;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: reconnect without burning retry attempts
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWebSocket::ConnContext_s::Reconnect()
|
||||
{
|
||||
Disconnect();
|
||||
state = CS_CREATE;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: reconnect while burning retry attempts
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWebSocket::ConnContext_s::Destroy()
|
||||
{
|
||||
Disconnect();
|
||||
state = CS_DESTROYED;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user