c++ socket实现http及websocket通信

       最近由于工程需要,需要在本地实现网页与本地程序实时通信,但网页又不能直接通过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
 

#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");
}

(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,这个开源库里面的算法挺多的。

用Socket写的HTTP类,商业级C++源码。
rar 5星 超过95%的资源 16KB
下载

期间我也遇到了不少问题,因为小编不是一个c++大佬,怎么引用lib也搞不清楚,所以遇到了不少麻烦,关于cryptopp算法库,首先需要自行下载,下载链接为:https://www.cryptopp.com/  同时里面有很多算法说明 。下载下来之后会有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协议

WebSocket 协议

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值