最近由于工程需要,需要在本地实现网页与本地程序实时通信,但网页又不能直接通过socket与本地程序通信,只能支持相关的web协议,经过考虑我选择了http与websocket协议,这样的话就要实现本地服务器,网上有很多开源库websocketpp之类的开源库,但是我觉得很麻烦,不够轻量化,配置也是麻烦的很。我选择了自己实现一个,我的开发环境为win10 VS2017。
首先编写套接字通信程序如下:
(1)main.cpp
#include <iostream>
#include <thread>
#include <WinSock2.h>
#include <Ws2tcpip.h>
#pragma comment(lib,"ws2_32.lib")
#include "webServer.h"
void main() {
WSADATA wsd;
WSAStartup(MAKEWORD(2, 2), &wsd); //初始化套接字
SOCKET socketServer = socket(AF_INET,SOCK_STREAM,0); //创建监听套接字
sockaddr_in serverAddr; //设置监听端口ip、端口、协议等
serverAddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr.S_un.S_addr); //将点分十进制转为二进制整数
serverAddr.sin_port = htons(80);
int bindRe = bind(socketServer, (sockaddr*)&serverAddr, sizeof(serverAddr)); //绑定监听套接字与要监听的ip端口
listen(socketServer, 10); //开始监听套接字,设置等待接受队列为10
sockaddr_in clientAddr; //用于存储客户端信息
int len = sizeof(clientAddr);
while (true)
{
SOCKET communicationServer = accept(socketServer, (sockaddr*)&clientAddr, &len); //监听到新的连接请求信息后创建通信套接字
//char clientIp[INET_ADDRSTRLEN]; //ipv4
//inet_ntop(AF_INET, &clientAddr.sin_addr, clientIp, sizeof(clientIp)); //将二进制整数转为点分十进制
std::thread httpThread(webServer, communicationServer);
httpThread.detach();
}
closesocket(socketServer);
WSACleanup();
system("pause");
}
(2)webServer.h
/*
* 用于完成http协议与websocket协议
*/
#pragma once
#include <WinSock2.h>
#include <Ws2tcpip.h>
#include <iostream>
#include <string>
#include <thread>
#include <map>
#include <iterator>
#include "stringUnit.h"
#include <set>
#include "WsSession.h"
#pragma comment(lib,"ws2_32.lib")
extern std::multimap<std::string, WsSession> wsSessionMap; //用于存储ws服务的session,multimap可重复存储,多线程共享数据
extern std::mutex wsSessionMapMutex; //线程锁
/*
* 定义http协议结构
*/
typedef struct {
std::string Method; //请求方法get、post等等
std::string URL; //请求路径
std::string version; //协议版本
std::string Host; //主机
std::string Connection; //链接后续状态,是否升级协议
std::string Upgrade; //需要升级为什么协议
std::string cookie; //cookie
std::map<std::string, std::string> param; //get请求的参数
std::string SecWebSocketKey; //用于建立websocket协议
std::string content; //主体内容
}requestHttp;
/*
* websocket协议,不支持拓展
*/
typedef struct {
uint8_t msgType; //数据帧状态:0b0数据帧结束,0b1数据帧继续
uint8_t frameType; //控制码:0x0继续、0x1文本、0x2二进制,0x8关闭,0x9ping,0xApong
uint8_t Mask; //是否掩码
uint8_t PayloadLen; //数据长度
char Maskingkey[4]; //掩码,若Mask为1则该字段存在,若为0则该字段缺失
char* Payload; //数据载荷
}wsProtocol;
/*
* 设置web服务初始化参数
*/
typedef struct {
std::string ip = "127.0.0.1"; //绑定ip
int port = 80; //监听端口
int waitingQueun = 10; //等待队列
std::string (*httpResponseFun)(requestHttp) = NULL; //http响应函数指针,返回响应内容,@param requestHttp为请求参数
std::string (*wsResponseFun)(wsProtocol) = NULL; //websocket响应服务,返回响应内容,@param wsProtocol为请求参数
}Web;
/*
* web协议服务
* @param communicationServer 通信套接字
* @param web 设置Web服务参数
*/
void webServer(SOCKET communicationServer, Web* web);
/*
* http协议
* @param communicationServer 通信套接字
* @param reqHttp 请求内容
* @param web 设置Web服务参数
*/
int httpServer(SOCKET communicationServer, requestHttp reqHttp, Web* web);
/*
* websocket协议
* @param communicationServer 通信套接字
* @param reqHttp 请求内容
* @param web 设置Web服务参数
*/
int webscoketServer(SOCKET communicationServer, requestHttp reqHttp, Web* web);
/*
* http协议解析
* @param requestStr 请求字符串
*/
requestHttp httpAnalysis(std::string requestStr);
/*
* websocket 协议帧解析,接收解析
* @param frame 数据帧
*/
wsProtocol websocketAnalysis(char* frame);
/*
* 发送数据
* @param sendStr 要发送的数据
*/
char* websocketDataSend(char* sendStr);
/*
* 构建pong帧
*/
char* pongSend();
/*
* 初始化web服务线程
* @param web web服务基础配置参数
*/
void webServerThreadInit(Web &&web);
/*
* 开启web服务进程
* @param web web服务基础配置参数
* @return 将websocket的会话存在全局变量wsSessionMap中
*/
void webServerStart(Web &web);
(3)webServer.cpp
#include "webServer.h"
void webServer(SOCKET communicationServer)
{
std::cout << "开启一个web服务线程!"<< "线程id!" << std::this_thread::get_id() << std::endl;
char recvbuf[1024*64]; //64kb容量
while (true)
{
int clientSocketStatus = recv(communicationServer, recvbuf, sizeof(recvbuf), 0);
if (clientSocketStatus == 0) { //客户端关闭了通信套接字
std::cout << "客户端关闭了通信套接字!" << std::endl;
closesocket(communicationServer); //关闭服务器端套接字
break;
}
else if (clientSocketStatus > 0) //clientSocketStatus此时为接收到的字节数
{
recvbuf[clientSocketStatus] = '\0';
requestHttp reqHttp = httpAnalysis(recvbuf);
if (reqHttp.version.compare("HTTP/1.1") == 0 && reqHttp.Connection.find("Upgrade") == std::string::npos) //协议版本为HTTP/1.1且未要求协议升级,普通http请求
{
if (httpServer(communicationServer, reqHttp) == 0) {
closesocket(communicationServer);
break;
};
}
else if (reqHttp.version.compare("HTTP/1.1") == 0 && reqHttp.Connection.find("Upgrade") != std::string::npos && reqHttp.Upgrade.find("websocket") != std::string::npos) {
if (webscoketServer(communicationServer, reqHttp) == 0) {
closesocket(communicationServer);
break;
}
}
else{
// do not 暂不支持其他协议
}
}
else
{
std::cout << "接收数据失败!,接收状态:" << clientSocketStatus << std::endl;
closesocket(communicationServer); //关闭服务器端套接字
break;
}
}
std::cout << "服务器进程结束!" << std::endl;
}
int httpServer(SOCKET communicationServer, requestHttp reqHttp)
{
std::cout << "http服务!" << std::endl;
std::string response;
response.append("HTTP/1.1 200 OK\r\n");
//response.append("Content-Type: application/json;charset=UTF-8\r\n");
response.append("Content-Type: text/html;charset=ANSI\r\n"); //c++标准库中的string默认是本地码,即ANSI编码格式
response.append("Server: wxj233\r\n");
response.append("Connection: close\r\n");
response.append("\r\n");
response.append("我是内容哈哈哈哈哈!");
int sendStatus = send(communicationServer, response.c_str(), response.length(), 0);
return 0;
}
int webscoketServer(SOCKET communicationServer, requestHttp reqHttp)
{
std::cout << "websocket服务!" << std::endl;
std::string SecWebSocketAccept = BASE64code(SHA1code(reqHttp.SecWebSocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
std::string response;
response.append("HTTP/1.1 101 Switching Protocols\r\n");
response.append("Upgrade: websocket\r\n");
response.append("Connection: Upgrade\r\n");
response.append("Sec-WebSocket-Accept: " +SecWebSocketAccept + "\r\n");
int sendStatus = send(communicationServer, response.c_str(), response.length(), 0);
char recvbuf[1024 * 64]; //64kb容量
while (true)
{
int clientSocketStatus = recv(communicationServer, recvbuf, sizeof(recvbuf), 0); //阻塞模式无请求不会继续执行
if (clientSocketStatus == 0) { //客户端关闭了通信套接字
return 0;
}
else if(clientSocketStatus > 0) {
recvbuf[clientSocketStatus] = '\0';
wsProtocol wsFrame = websocketAnalysis(recvbuf);
if (wsFrame.frameType == 0x1) {
std::cout << "收到数据:" << wsFrame.Payload <<"数据长度:"<< (int)wsFrame.PayloadLen << std::endl;
std::string sendstr = "aaa";
char* sendTr = websocketDataSend((char*)sendstr.c_str());
int sendStatus = send(communicationServer, sendTr, 2 + sendstr.size(), 0);
delete[] sendTr;
}
else if(wsFrame.frameType == 0x9){
std::cout << "收到ping" << std::endl;
char* pong = pongSend();
int sendStatus = send(communicationServer, pong, 2, 0);
}
else if (wsFrame.frameType == 0x8) {
std::cout << "关闭套接字" << std::endl;
return 0;
}
}
else
{
return 0;
}
}
}
/* http协议解析 */
requestHttp httpAnalysis(std::string requestStr)
{
requestHttp reqHttp;
std::vector<std::string> httpStrs = split(requestStr, "\r\n");
for (size_t i = 0; i < httpStrs.size(); i++)
{
if (i == 0) {
//首行内容
std::vector<std::string> requestTop = split(httpStrs.at(i), " ");
reqHttp.Method = requestTop.at(0);
reqHttp.URL = requestTop.at(1);
reqHttp.version = requestTop.at(2);
if (reqHttp.URL.find("?") != std::string::npos)
{
std::string content = split(reqHttp.URL, "?").at(1);
std::vector<std::string> paramStrs = split(content, "&");
for (std::vector<std::string>::iterator iter = paramStrs.begin(); iter != paramStrs.end(); iter++)
{
std::vector<std::string> paramStr = split(*iter, "=");
reqHttp.param.insert(std::pair<std::string, std::string>(paramStr.at(0), paramStr.at(1)));
}
}
}
else if (i >0 && i < httpStrs.size() - 1) {
//头部内容
std::vector<std::string> requestHead = split(httpStrs.at(i), ": ");
if (httpStrs.at(i).find("Host") != std::string::npos) {
reqHttp.Host = requestHead.at(1);
}
else if (httpStrs.at(i).find("Connection") != std::string::npos) {
reqHttp.Connection = requestHead.at(1);
}
else if (httpStrs.at(i).find("Upgrade") != std::string::npos) {
reqHttp.Upgrade = requestHead.at(1);
}
else if (httpStrs.at(i).find("Sec-WebSocket-Key") != std::string::npos){
reqHttp.SecWebSocketKey = requestHead.at(1);
}
}
else{
//std::cout << "请求主体:"<< httpStrs.at(i) <<std::endl;
}
}
return reqHttp;
}
wsProtocol websocketAnalysis(char* frame)
{
wsProtocol wsFrame;
int pos = 0;
wsFrame.msgType = (uint8_t)((frame[pos] >> 7) & 0x1);
wsFrame.frameType = (uint8_t)(frame[pos] & 0xf);
pos++;
wsFrame.Mask = (uint8_t)((frame[pos] >> 7) & 0x1);
wsFrame.PayloadLen = (uint8_t)(frame[pos] & 0x7f);
pos++;
if (wsFrame.frameType == 0x1) {
if (wsFrame.PayloadLen == 126) {
memcpy(&wsFrame.PayloadLen, frame + pos, 2);
wsFrame.PayloadLen = ntohs(wsFrame.PayloadLen);
pos += 2;
}
else if (wsFrame.PayloadLen == 127) {
memcpy(&wsFrame.PayloadLen, frame + pos, 8);
wsFrame.PayloadLen = ntohl(wsFrame.PayloadLen);
pos += 8;
}
wsFrame.Payload = new char[wsFrame.PayloadLen + 1];
if (wsFrame.Mask == 1) {
memcpy(wsFrame.Maskingkey, frame + pos, 4);
pos += 4;
for (int i = 0; i < wsFrame.PayloadLen; i++) {
int j = i % 4;
wsFrame.Payload[i] = frame[pos + i] ^ wsFrame.Maskingkey[j];
}
}
else {
memcpy(wsFrame.Payload, frame + pos, wsFrame.PayloadLen);
}
wsFrame.Payload[wsFrame.PayloadLen] = '\0';
}
return wsFrame;
}
char* websocketDataSend(char * sendStr)
{
int len = strlen(sendStr)+2;
char* header = new char[len]; //报文
header[0] = 0x81;
header[1] = strlen(sendStr);
memcpy(header+2, sendStr, strlen(sendStr));
return header;
}
char* pongSend()
{
char header[2]; //报文头
header[0] = 0x8A;
header[1] = 0;
return header;
}
(4)stringUnit.h
#pragma once
#include <vector>
#include "cryptopp/cryptlib.h"
#include "cryptopp/sha.h"
#include "cryptopp/base64.h"
#include "cryptopp/filters.h"
#include <Windows.h>
/*
* 分割字符串
* @param str 待分割字符串
* @param splitStr 分割的字符
* @return 分割后的字符串数组
*/
std::vector<std::string> split(std::string str, std::string splitStr);
/*
* SHA1加密
* @param str 被加密的字符串
* @return 加密后的字符串
*/
std::string SHA1code(std::string str);
/*
* base64加密
* @param str 被加密的字符串
* @return 加密后的字符串
*/
std::string BASE64code(std::string str);
(5)stringUnit.cpp
#include "stringUnit.h"
std::vector<std::string> split(std::string str, std::string splitStr)
{
std::vector<std::string> stringVector;
int index1 = 0;
int index2 = 0;
int size = splitStr.length();
while (true)
{
index2 = str.find(splitStr, index1);
if (index2 != std::string::npos) {
std::string temp = str.substr(index1, index2-index1);
stringVector.push_back(temp);
}
else{
std::string temp = str.substr(index1, str.length() - index1);
stringVector.push_back(temp);
break;
}
index1 = index2 + size;
}
return stringVector;
}
std::string SHA1code(std::string str)
{
std::string distStr;
CryptoPP::SHA1 hash;
CryptoPP::StringSource(str, true, new CryptoPP::HashFilter(hash, new CryptoPP::StringSink(distStr)));
return distStr;
}
std::string BASE64code(std::string str)
{
std::string encoded;
CryptoPP::StringSource ss(str, true,
new CryptoPP::Base64Encoder(
new CryptoPP::StringSink(encoded)
) // Base64Encoder
);
return encoded;
}
相关链接:HTTP Headers
我的socket为阻塞模式,该服务器支持多线程,支持中文;支持http及websocket方法其他方法读者可以自行添加。其中websocket协议涉及SHA1加密算法和base64编码,我直接使用了开源库cryptopp,这个开源库里面的算法挺多的。
期间我也遇到了不少问题,因为小编不是一个c++大佬,怎么引用lib也搞不清楚,所以遇到了不少麻烦,关于cryptopp算法库,首先需要自行下载,下载链接为:Crypto++ Library 8.7 | Free C++ Class Library of Cryptographic Schemes 同时里面有很多算法说明 。下载下来之后会有4个工程,如下图:
可以编译动态链接库、静态链接库,我编译了cryplib,注意编译版本和你自己的要引用该库的项目应该一致我自己的项目是debug 64,所以我编译的也是debug 64,另外注意运行库的选择也需要和你自己的项目一直:
静态库的引用将所有的.h文件和生成的lib文件添加至项目中即可。后续我会上传源码可可调用接口版本。
html测试的代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>websocketTest</title>
</head>
<body>
<div id="test">websocketTest</div>
<script>
if ("WebSocket" in window){
var ws = new WebSocket("ws://127.0.0.1:80/");
ws.onopen = function()
{
console.log("建立连接!!!!!");
let i = 0;
ws.send(i);
setInterval(function(){
i++;
ws.send(i);
}, 1000);
};
ws.onmessage = function (evt)
{
var received_msg = evt.data;
if(received_msg != ""){
console.log("收到扫描数据!"+received_msg);
//window.location.href=received_msg;
}
};
}else{
alert("浏览器不支持websocket,将导致二维码扫描功能无法使用!");
}
</script>
</body>
</html>
运行效果图。
相关链接:https://www.w3cschool.cn/websocket_protocol/wav8jozt.html websocket协议