c++ websocket服务器
h5游戏中网络通信不在使用二进制协议,而是使用websocket(ws) 或者使用基于https的wss进行通信,但是已经自己实现过n次二进制协议通信了,所以也不想使用第三方库的实现。下面就介绍下如何使用c++自己实现一个ws服务器,下一篇文章在介绍wss实现。其实ws的实现只是在二进制协议的基础上增加了一层简单的封装而已,实现并不难。下面介绍ws相关的处理,不会详细介绍epoll的实现部分。
首先ws在连接服务器后第一个包是http中get方法的握手协议包,所以对于第一个包需要特殊处理,握手包的处理代码如下:
bool CWSClientSocket::WSHandshake()
{
if (m_pRecvBuf->GetSize() < PACKHEADSIZE)return true;//包长不够
NetBuffer arrBuf[2];
m_pRecvBuf->GetReadEnable(arrBuf);
uint32_t nContentPos = 0;
uint32_t nContentLen = 0;
int nRet = GetLength(arrBuf[0].buf, arrBuf[0].len, nContentPos, nContentLen);//检查http协议包是否完整
if (nRet == -1)return false;//不支持的协议头解析
if (0 == nRet) return true;//数据不全
if (memcmp(arrBuf[0].buf, "GET ", 4) != 0)return false;//协议头错误
auto pBegin = strstr(arrBuf[0].buf, "Sec-WebSocket-Key: ");
if (nullptr == pBegin)return false;//数据错误
pBegin += 19;
auto pEnd = strstr(pBegin, "\r\n");
if (nullptr == pEnd)return false;//数据错误
SetStatus(enmSocketStatus_WSConnected);
//构建http返回消息 完成握手处理
std::string str("HTTP/1.1 101 Switching Protocols\r\nConnection: upgrade\r\nUpgrad: websocket\r\nSec-Websocket-Accept: ");
uint32_t msg[5];
//sha1加密
CSHA1 sha;
sha.Reset();
sha.Input(pBegin, uint32_t(pEnd - pBegin));
sha.Input("258EAFA5-E914-47DA-95CA-C5AB0DC85B11", 36);
sha.Result(msg);
for (int i = 0; i < 5; ++i)
msg[i] = htonl(msg[i]);
Base64Encode((char*)msg, 20, str);//base 64加密
str.append("\r\n\r\n");
SendWebMsg(str.data(), (uint32_t)str.size());//发送数据
m_pRecvBuf->Remove(nRet);//删除已经处理的数据
return true;
}
握手成功之后就可以进行普通的数据通信了。ws数据分为协议头和数据两部分。下面直接使用二进制数据,也就是说只是将以前的二进制数据通信增加了一个协议头。收包处理代码如下:
bool CWSClientSocket::SpiltMsg()
{
if (!IsWSConencted())//还没有握手成功
{
if (!WSHandshake())
g_pSocketThread->AddKickSession(GetSessionIndex());
return false;
}
static char buff[MaxOneMsg];
char* pBuff = nullptr;
NetBuffer arrBuf[2];
uint32_t nSize = 0;
for(uint32_t nCount = 0; nCount < 50;++nCount)
{
if (m_pRecvBuf->GetSize() < PACKHEADSIZE)return false;//数据不够
m_pRecvBuf->GetReadEnable(arrBuf);
if (char(0x82) != arrBuf[0].buf[0])
{
g_pSocketThread->AddKickSession(GetSessionIndex());
return false;//不是数据包直接关闭socket
}
if (arrBuf[0].len >= 4)
pBuff = arrBuf[0].buf;
else
{
pBuff = buff;
memcpy(pBuff, arrBuf[0].buf, arrBuf[0].len);
memcpy(pBuff + arrBuf[0].len, arrBuf[1].buf, 4 - arrBuf[0].len);
}
nSize = pBuff[1] & 0x7f;
if (127 == nSize || pBuff[1] > 0)
{
g_pSocketThread->AddKickSession(GetSessionIndex());
return false; //不是数据包或者数据包太长直接关闭socket 超过64kws协议头中长度是8字节
}
uint32_t headLen = 2;
if (126 == nSize)
{
nSize = uint8_t(pBuff[2]);
nSize = (nSize << 8) | uint8_t(pBuff[2]);
headLen = 4;
}
uint32_t nDataSize = nSize + headLen + 4;
if (nDataSize > MaxOneMsg || nSize == 0)
{
g_pSocketThread->AddKickSession(GetSessionIndex());
return false;//数据有问题直接关闭服务器
}
if (nDataSize > m_pRecvBuf->GetSize())return false;//数据不完整
if (nDataSize <= arrBuf[0].len)
pBuff = arrBuf[0].buf;
else
{
pBuff = buff;
memcpy(pBuff, arrBuf[0].buf, arrBuf[0].len);
memcpy(pBuff + arrBuf[0].len, arrBuf[1].buf, (size_t)nDataSize - arrBuf[0].len);
}
const char* key = &pBuff[headLen];
#ifdef WIN32
#pragma warning(push)
#pragma warning(disable : 6385)
#endif
for (uint32_t i = headLen + 4, j = 0; i < nDataSize; ++i)//数据解密
{
pBuff[i] = pBuff[i] ^ key[j];
j = 3 == j ? 0 : j + 1;
}
#ifdef WIN32
#pragma warning(pop)
#endif
DispatchMsg(pBuff + headLen, nSize);//消息处理
m_pRecvBuf->Remove(nDataSize);
}
return true;
}
DispatchMsg函数中就可以直接使用以前二进制通信的组包方式获取通信数据了。最后在介绍下发包的处理函数:代码如下:
bool CWSClientSocket::SendMsg(const char* pMsg, uint32_t size)
{
//假设msg前面4个字节是包长 只支持最大包长64k
int len = size - 4;
if (len > 0xffff)return false;
char* msg = (char*)pMsg;
if (len >= 126)
{
msg[0] = char(0x82);
msg[1] = 126;
msg[2] = char(len >> 8);
msg[3] = char(0xff & len);
}
else
{
msg[2] = char(0x82);
msg[3] = char(len);
msg += 2;
size -= 2;
}
SendWebMsg(msg, size);
return true;
}
到这里websocket的相关处理就全部介绍完了,只需要将上面三个函数套在之前的二进制网络通信上面就可以实现ws通信了。
代码的完整实现传送地址如下:https://github.com/mouzey/c-project
更多推荐
所有评论(0)