C++开发websocket功能

本文主要介绍c++开发websocket功能的相关步骤,以及在开发过程中遇到的诸如中文乱码、收发失败等问题。本文参考并借用了websocket_codetool.cpp相关功能代码,下载链接:https://download.csdn.net/download/qq_21268687/12438921?ops_request_misc=&request_id=&biz_id=103&utm_term=websocket_codetool&utm_medium=distribute.pc_search_result.none-task-download-2~download~sobaiduweb~default-0-12438921.pc_v2_rank_dl_default&spm=1018.2226.3001.4451.2,如涉侵权请及时告知。

头文件如下:

#pragma once
#include "spdlog/spdlog.h"
#include "spdlog/sinks/daily_file_sink.h"
#include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/sinks/rotating_file_sink.h"
#include "Iocp/iocpServer.h"
#include "DataThread.h"
#include "AdoHelper.h"
#include "Parameter.h"
#include "LocalsenseThread.h"
#include "../Websocket/websocket_codetool.h"
#include <set>
#define DATA_BUFSIZE 4096
struct PerIOcontext
{
OVERLAPPED m_Overlapped;
SOCKET m_sockAccept;
WSABUF m_wsaBuf;
char buffer[DATA_BUFSIZE];
bool operator <(const PerIOcontext& r)const {
return m_sockAccept < r.m_sockAccept;
}
};
//typedef _paramThread paramValue;
class CWSServer
{
private:
CIocpServer *m_pIocpServer;
public:
CWSServer();
~CWSServer();
public:
HANDLE m_hWebsocketThreads[2];
HANDLE m_hAlarmPopThread;
std::set<PerIOcontext> m_clients;
std::set<PerIOcontext>::iterator it;
int    m_nWSPort;
SOCKET m_server;
Websocket_Codetool m_decodetool;
bool m_bThreadExit;
public:
void Init(LPVOID lpParam);
void Stop();
bool StartWSServer();
static DWORD WINAPI WebsocketAcceptThread(LPVOID lpParameter);
static DWORD WINAPI WebsocketReceiveThread(LPVOID lpParameter);
static DWORD WINAPI AlarmPopThread(LPVOID lpParama);
void Process_messages(PerIOcontext* perIOcontext, DWORD msgLen);
void ConsolePrint(int MessageType, char* perIOcontext, DWORD nLen, SOCKET socket);
void AnalysisText(char* outMessage, DWORD nLen, PerIOcontext* perIOcontext);
std::string string_To_UTF8(const std::string& str);
};

CPP文件如下:

#include "stdafx.h"
#include <iostream>
#include <cstdio>
#include <ATLComTime.h>
#include "WSServer.h"
HANDLE m_completionPort = 0;
CWSServer::CWSServer()
{
m_nWSPort = 50088;
m_bThreadExit = false;
}
CWSServer::~CWSServer()
{
}
void CWSServer::Init(LPVOID lpParam)
{
}
void CWSServer::Stop()
{
//销毁Websocket接收和数据处理线程
CloseHandle(m_hWebsocketThreads[0]);
m_hWebsocketThreads[0] = NULL;
CloseHandle(m_hWebsocketThreads[1]);
m_hWebsocketThreads[1] = NULL;
//consoleWS->flush();
}

bool CWSServer::StartWSServer()
{
m_completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
if (NULL == m_completionPort) {    // 创建IO内核对象失败  
std::cout << "CreateIoCompletionPort failed. Error:" << GetLastError() << std::endl;
//system("pause");
return false;
}
else
{
m_hWebsocketThreads[0] = CreateThread(NULL, 0, WebsocketAcceptThread, this, NULL, NULL);
m_hWebsocketThreads[1] = CreateThread(NULL, 0, WebsocketReceiveThread, this, NULL, NULL);
m_hAlarmPopThread = CreateThread(NULL, 0, AlarmPopThread, this, 0, NULL);
//WaitForMultipleObjects(2, m_hWebsocketThreads, TRUE, INFINITE);
return true;
}
return true;
}

void CWSServer::Process_messages(PerIOcontext* perIOcontext, DWORD msgLen)  //数据处理
{
//printf("%s\n", perIOcontext->buffer);
if (m_decodetool.isWSHandShake(perIOcontext->buffer))
{
std::string handshakeString = m_decodetool.GetHandshakeString(perIOcontext->buffer);
std::cout << handshakeString << std::endl;
send(perIOcontext->m_sockAccept, handshakeString.c_str(), handshakeString.size(), 0);
m_clients.insert(*perIOcontext);
return;
}
//获取数据类型并根据自己的需求实现相关的操作(根据websocket协议头的opcode)
WS_FrameType type = m_decodetool.fetch_websocket_info(perIOcontext->buffer, msgLen);
//定义数据缓冲区
char* outMessage, * responseData;
outMessage = new char[DATA_BUFSIZE];
responseData = new char[DATA_BUFSIZE];
//std::cout << "TYPE: " << type << std::endl;
switch (type)
{
case WS_EMPTY_FRAME:
break;
case WS_ERROR_FRAME:
break;
case WS_TEXT_FRAME:
{
char test[256];
m_decodetool.wsDecodeFrame(perIOcontext->buffer, msgLen, outMessage);
//m_decodetool.wsEncodeFrame(outMessage, strlen(outMessage), responseData, WS_TEXT_FRAME);
memcpy(test, perIOcontext->buffer, strlen(perIOcontext->buffer));
//字符串编码从UTF-8转CP_OEMCP,此处的编码转换主要是为了解决接收中文乱码
int dwNum;
dwNum = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)outMessage, -1, NULL, 0);
wchar_t* pwText;
pwText = new wchar_t[dwNum];
if (!pwText)
{
delete[]pwText;
}
MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)outMessage, -1, pwText, dwNum);
dwNum = WideCharToMultiByte(CP_OEMCP, NULL, pwText, -1, NULL, 0, NULL, FALSE);
char* psText2;
psText2 = new char[dwNum];
if (!psText2)
{
delete[]psText2;
}
WideCharToMultiByte(CP_OEMCP, NULL, pwText, -1, psText2, dwNum, NULL, FALSE);
msgLen = dwNum-1;
memcpy(outMessage, psText2, dwNum);
delete[]pwText;
delete[]psText2;
memcpy(test, outMessage, strlen(outMessage));
//收包日志记录
ConsolePrint(type, outMessage, msgLen, perIOcontext->m_sockAccept);
WriteLog(type, outMessage, msgLen, perIOcontext->m_sockAccept);
this->AnalysisText(outMessage, msgLen,perIOcontext);
break;
}
case WS_BINARY_FRAME:
break;
case WS_PING_FRAME:
break;
case WS_PONG_FRAME:
break;
case WS_OPENING_FRAME:
break;
case WS_CLOSING_FRAME:
{
closesocket(perIOcontext->m_sockAccept);
m_clients.erase(*perIOcontext);
break;
}
case WS_CONNECT_FRAME:
break;
default:
break;
}
if (type >= WS_BINARY_FRAME && type <= WS_OPENING_FRAME )
{
ConsolePrint(type, outMessage, msgLen, perIOcontext->m_sockAccept);
WriteLog(type, outMessage, msgLen, perIOcontext->m_sockAccept);
}
delete[] outMessage;
delete[] responseData;
}

//此函数主要是在服务器往浏览器发包send前调用,解决中文编码问题
std::string CWSServer::string_To_UTF8(const std::string& str)
{
int nwLen = ::MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, NULL, 0);
wchar_t* pwBuf = new wchar_t[nwLen + 1];//一定要加1,不然会出现尾巴 
ZeroMemory(pwBuf, nwLen * 2 + 2);
::MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.length(), pwBuf, nwLen);
int nLen = ::WideCharToMultiByte(CP_UTF8, 0, pwBuf, -1, NULL, NULL, NULL, NULL);
char* pBuf = new char[nLen + 1];
ZeroMemory(pBuf, nLen + 1);
::WideCharToMultiByte(CP_UTF8, 0, pwBuf, nwLen, pBuf, nLen, NULL, NULL);
std::string retStr(pBuf);
delete[]pwBuf;
delete[]pBuf;
pwBuf = NULL;
pBuf = NULL;
return retStr;
}


void CWSServer::AnalysisText(char* outMessage, DWORD nLen, PerIOcontext* perIOcontext)
{
if (outMessage[0] == 0x7B && outMessage[nLen-1] == 0x7D)//json数据
{
std::string value;
Json::Reader reader;
Json::Value root;
if (reader.parse((char*)outMessage, root))  // reader将Json字符串解析到root,root将包含Json里所有子元素  
{
std::string strResponse="";
char responseData[DATA_BUFSIZE];
int nType = 0, nCount = 0;
std::string strSenders;
if (root["type"].isInt())
{
nType = root["type"].asInt();
}
if (nType == 0) {//心跳包
std::string heart = root["heart"].asString();
if (heart == "send") {
for (it = m_clients.begin(); it != m_clients.end(); it++) {
if (it->m_sockAccept == perIOcontext->m_sockAccept) {
strResponse = "{\"type\":0,\"heart\":\"success\"}";
std::string strUTF8=string_To_UTF8(strResponse);
m_decodetool.wsEncodeFrame(strUTF8.c_str(), strlen(strUTF8.c_str()), responseData, WS_TEXT_FRAME);
send(it->m_sockAccept, responseData, strlen(responseData), 0);
break;
}
}
}
}
else if (nType == 2) {//弹框报警推送
}
//回包日志记录
if (strResponse != "") {
ConsolePrint(WS_RECEIVE_FRAME, (char*)strResponse.c_str(), strlen((char*)strResponse.c_str()), perIOcontext->m_sockAccept);
}
}
}
}

void CWSServer::ConsolePrint(int MessageType, char* outMessage, DWORD nLen,SOCKET socket)
{
char recvMessage[DATA_BUFSIZE];
memset(recvMessage,0, DATA_BUFSIZE);
SYSTEMTIME sys;
GetLocalTime(&sys);
char tm_buf[30];
sprintf(tm_buf, "%04d%02d%02d %02d:%02d:%02d.%03d", sys.wYear, sys.wMonth, sys.wDay, sys.wHour, sys.wMinute, sys.wSecond, sys.wMilliseconds);
if (MessageType == WS_TEXT_FRAME)
{
sprintf(recvMessage, "%s: ClientID=%d  接收内容=%s", tm_buf, socket, outMessage);
std::cout << recvMessage << std::endl;
}
else if (MessageType == WS_RECEIVE_FRAME)
{
sprintf(recvMessage, "%s: ClientID=%d  发送内容=%s", tm_buf, socket, outMessage);
std::cout << recvMessage << std::endl;
}
else if (MessageType == WS_CONNECT_FRAME)
{
sprintf(recvMessage, "%s: ClientID=%d %s  链接成功", tm_buf, socket, outMessage);
std::cout << recvMessage << std::endl;
}
else if (MessageType == WS_CLOSING_FRAME)
{
sprintf(recvMessage, "%s: ClientID=%d  链接断开", tm_buf, socket);
std::cout << recvMessage << std::endl;
}
}

DWORD WINAPI CWSServer::WebsocketAcceptThread(LPVOID lpParameter)  //websocket连接线程
{
CWSServer* p = (CWSServer*)lpParameter;
//WSADATA wsaData;
//WSAStartup(MAKEWORD(2, 2), &wsaData);
p->m_server = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, NULL, WSA_FLAG_OVERLAPPED);
if (p->m_server == INVALID_SOCKET)
{
throw CSrvException("错误的websocket tcp socket套接字.", -1);
}
SOCKADDR_IN ServerAddr;
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
ServerAddr.sin_port = htons(p->m_nWSPort);
//此处的bind一定要加全局符号::,因为C++11 thread也有这个bind函数,加上::就代表调用的是WinSock2中的bind函数
::bind(p->m_server, (LPSOCKADDR)&ServerAddr, sizeof(ServerAddr));
listen(p->m_server, 100);
//printf("listenning...\n");
int i = 0;
SOCKADDR_IN ClientAddr;
int addr_length = sizeof(ClientAddr);
//HANDLE completionPort = (HANDLE)lpParameter;
while (TRUE)
{
PerIOcontext* perIOcontext = (PerIOcontext*)GlobalAlloc(GPTR, sizeof(PerIOcontext));
SOCKET acceptSocket;
SOCKADDR_IN acceptAddr;
int len = sizeof(acceptAddr);
acceptSocket = accept(p->m_server, (SOCKADDR*)&acceptAddr, &len);
int msgLen = 0;
int type = WS_CONNECT_FRAME;
char outMessage[DATA_BUFSIZE];
sprintf(outMessage,"%s", inet_ntoa(acceptAddr.sin_addr));
p->ConsolePrint(type, outMessage, msgLen, acceptSocket);
p->WriteLog(type, outMessage, msgLen, acceptSocket);
if (SOCKET_ERROR == perIOcontext->m_sockAccept) {   // 接收客户端失败  
std::cerr << "Accept Socket Error: " << GetLastError() << std::endl;
system("pause");
return -1;
}
perIOcontext->m_wsaBuf.buf = perIOcontext->buffer;
perIOcontext->m_wsaBuf.len = 1024;
perIOcontext->m_sockAccept = acceptSocket;
CreateIoCompletionPort((HANDLE)(perIOcontext->m_sockAccept), m_completionPort, (DWORD)perIOcontext, 0);
DWORD RecvBytes;
DWORD Flags = 0;
ZeroMemory(&(perIOcontext->m_Overlapped), sizeof(OVERLAPPED));
WSARecv(perIOcontext->m_sockAccept, &(perIOcontext->m_wsaBuf), 1, &RecvBytes, &Flags, &(perIOcontext->m_Overlapped), NULL);
}
return FALSE;
}

DWORD WINAPI CWSServer::WebsocketReceiveThread(LPVOID lpParameter) //websocket接收数据线程
{
//HANDLE completionPort = (HANDLE)lpParameter;
CWSServer* p = (CWSServer*)lpParameter;
DWORD BytesTransferred;
PerIOcontext* perIOcontext;
LPOVERLAPPED IpOverlapped = NULL;
while (true)
{
BOOL ret = GetQueuedCompletionStatus(m_completionPort, &BytesTransferred, (PULONG_PTR)&perIOcontext, &IpOverlapped, INFINITE);
//std::cout << "msgLen:" << BytesTransferred << std::endl;
p->Process_messages(perIOcontext, BytesTransferred);
if (BytesTransferred == 0)
{
//printf("获得字节为0,disconnect\n");
}
//printf("客户端:%s\n", perIOcontext->buffer);
memset(perIOcontext->buffer, 0, DATA_BUFSIZE);
DWORD RecvBytes;
DWORD Flags = 0;
//system("pause");
WSARecv(perIOcontext->m_sockAccept, &(perIOcontext->m_wsaBuf), 1, &RecvBytes, &Flags, &(perIOcontext->m_Overlapped), NULL);
}
return FALSE;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值