非c++程序员,因要写一个dll注入工具,需要交互,写了一个socket实现的http服务端。
由于非c++科班,代码可能会有错误,仅供参考。
#include <map>
#include<string>
//soket头文件
#include<winsock2.h>
#include <Ws2tcpip.h>
//链接库文件
#pragma comment(lib,"ws2_32.lib")
using namespace std;
#define PORT 9999//定义服务器端口号
//声明函数
DWORD WINAPI http_server(LPVOID lpParam);
void response(SOCKET connect_socket, string html);
string getURI(string str);
string getBody(string str);
map<string, wstring> getParams(string str);
int hex2num(string str);
wstring URLDecode(string str);
wstring ANSI2UTF8(string text);
int main()
{
//F1查看函数api
/*HANDLE hThread = CreateThread(NULL, 0, http_server, NULL, 0, NULL);
if (hThread != 0)
{
CloseHandle(hThread);
}*/
http_server(NULL);
return 0;
}
DWORD WINAPI http_server(LPVOID lpParam)
{
//参考官网例子:https://learn.microsoft.com/zh-cn/windows/win32/winsock/complete-server-code?source=recommendations
SOCKET listen_socket;//监听socket
SOCKET connect_socket;//连接socket
WORD ver = MAKEWORD(2, 2);//2.2版本的Socket
WSADATA dat;
int ret;
if (WSAStartup(ver, &dat))
{
OutputDebugString(L"WSAStartup失败!\n");
return 1;
}
//AF_INET:地址族,SOCK_STREAM:连接类型,IPPROTO_TCP:协议类型
listen_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listen_socket == INVALID_SOCKET)
{
WSACleanup();
return 1;
}
//第1个参数:秒,第2个参数微秒
struct timeval timeout = { 30,0 };
//设置接收超时
ret = setsockopt(listen_socket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeval));
if (ret == -1) {
WSACleanup();
return 1;
}
//设置发送超时
ret = setsockopt(listen_socket, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeval));
if (ret == -1) {
WSACleanup();
return 1;
}
//创建结构体 sockaddr_in对象储存自身信息
struct sockaddr_in sever_address;
memset(&sever_address, 0, sizeof(sever_address));
sever_address.sin_family = AF_INET;//设置服务器地址家族
//sever_address.sin_addr.s_addr = htonl(INADDR_ANY);//设置服务器IP地址
//sever_address.sin_addr.s_addr = inet_addr("127.0.0.1");//设置服务器IP地址
inet_pton(AF_INET, "127.0.0.1", &sever_address.sin_addr.s_addr);//vs2013版本以上使用新的函数转换IP地址
sever_address.sin_port = htons(PORT);//设置服务器端口号
//把名字和套接字绑定
ret = bind(listen_socket, (sockaddr*)&sever_address, sizeof(sever_address));
if (ret == SOCKET_ERROR)
{
closesocket(listen_socket);
WSACleanup();
return 1;
}
//listen函数在一般在调用bind之后调用,accept之前调用
ret = listen(listen_socket, 1);//1表示是未经过处理的连接请求队列可以容纳的最大数目
if (ret == SOCKET_ERROR)
{
closesocket(listen_socket);
WSACleanup();
return 1;
}
while (1)
{
OutputDebugString(L"等待客户端连接\n");
//创建结构体 sockaddr_in对象储存客户端信息
struct sockaddr_in client_address;
int len = sizeof(client_address);
memset(&client_address, 0, len);
//从监控队列取出一个socket,取出connect_socket后
//接受客户端连接,返回一个新的socket来和客户端通信
connect_socket = accept(listen_socket, (sockaddr*)&client_address, &len);
// 取出connect_socket后应该放入线程池执行,循环继续执行到accept()等待新的连接进来
if (connect_socket == INVALID_SOCKET) {
closesocket(connect_socket);
break;
}
char buf[1];
string buffer;
while (1) //循环接收发送的内容
{
//memset(&buf, 0, sizeof(buf));
//阻塞函数,等待接受内容
//如果发送方发送了多次信息,接收方没来得及进行recv,则数据堆积在输入缓冲区中,取数据的时候会都取出来。
//换句话说,recv并不能判断数据包的结束位置。这里简单使用超时来处理。
ret = recv(connect_socket, buf, sizeof(buf), 0);//每次接受的数据大小。结束返回-1
if (ret > 0)
{
buffer.push_back(buf[0]);
}
else break;
}
wstring out_buffer = ANSI2UTF8(buffer);
OutputDebugString(out_buffer.c_str());
string method = buffer.substr(0, buffer.find(" "));
string uri = getURI(buffer);
string body = getBody(buffer);
map<string, wstring> params = getParams(body);
string send_body = "成功";
if (uri.compare("/api/sendtxtmsg") == 0) {
send_body = "发送文本消息成功";
response(connect_socket, send_body);
}
else if (uri.compare("/api/sendpic") == 0) {
send_body = "发送图片消息成功";
response(connect_socket, send_body);
}
else if (uri.compare("/api/sendattatch") == 0) {
send_body = "发送文件消息成功";
response(connect_socket, send_body);
}
else {
send_body = "404";
response(connect_socket, send_body);
}
closesocket(connect_socket);
}
closesocket(listen_socket);
WSACleanup();
ret = getchar();
//Sleep(10*1000);//1000表示睡眠1000毫秒(及一秒)
return 0;
}
//响应数据到客户端
void response(SOCKET connect_socket, string body) {
char head[] = "HTTP/1.1 200 OK\r\nContent-Type: text/html;charset=GB2312\r\n\r\n";
send(connect_socket, head, strlen(head), 0);
//不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据
send(connect_socket, body.c_str(), strlen(body.c_str()), 0);
}
//截取请求地址
string getURI(string str) {
int posStart = str.find(" ") + 1;
int posEnd = str.find(" ", posStart);
string uri = str.substr(posStart, posEnd - posStart);
posStart = uri.find("?");
if (posStart > 0) {
uri = uri.substr(0, posStart);
}
return uri;
}
//截取请求体
string getBody(string str) {
if (str.empty()) return str;
int posStart = str.find("\r\n\r\n") + 4;
int size = str.size();
string body = str.substr(posStart, size - posStart);
return body;
}
//解析请求参数
map<string, wstring> getParams(string str) {
int pos;
map<string, wstring> params;
string param, key, val;
wstring wval;
str += "&";//扩展字符串以方便判断
int size = str.size();
for (int i = 0; i < size; i++)
{
pos = str.find("&", i);
if (pos > 0)
{
param = str.substr(i, pos - i);
i = pos;
pos = param.find("=");
key = param.substr(0, pos);
val = param.substr(pos + 1, param.size() - pos - 1);
wval = URLDecode(val);
params[key] = wval;
}
}
return params;
}
//16进制转10进制
int hex2num(string str)
{
char c;
int num;
int total = 0;
int size = str.size();
for (int i = 0; i < size; i++)
{
c = str[i];
switch (c)
{
case 'A': num = 10; break;
case 'B': num = 11; break;
case 'C': num = 12; break;
case 'D': num = 13; break;
case 'E': num = 14; break;
case 'F': num = 15; break;
default: num = c - '0'; break;
}
total += (num * (int)pow(16, size - 1 - i));//FC = 15*16^1 + 12*16^0
}
return total;
}
// url解码
wstring URLDecode(string str)
{
char c;
int num;
int size = str.size();
string result;
for (int i = 0; i < size; i++) {
c = str[i];
switch (c) {
case '+':
result.push_back(' ');
break;
case '%':
// 每3个字符串%xy将产生一个字节
if (i + 2 < size) {
num = hex2num(str.substr(i + 1, 2));
result.push_back((char)num);
i += 2;
}
break;
default:
result.push_back(c);
break;
}
}
wstring wresult = ANSI2UTF8(result);
return wresult;
}
//ANSI转UTF8
wstring ANSI2UTF8(string text) {
/*
MultiByteToWideChar 参数详解
1、CodePage:编码类型
2、dwFlags:UTF-8的dwFlags必须设置为0
3、lpMultiByteStr:要转换的字符串。注意c_str()函数:将字符串的内容转换为以 null 结尾的 C 样式字符串。
4、cbMultiByte:要转换的字符串的大小(以字节为单位)。 或者,如果字符串以 null 结尾,则可以将此参数设置为 -1。
5、lpWideCharStr:接收转换后的字符串
6、cchWideChar:指示的缓冲区的大小(以字符为单位)。 如果此值为 0,则该函数将返回所需的缓冲区大小(以字符为单位,包括任何终止 null 字符)并且不使用 lpWideCharStr 缓冲区。
*/
//获取转为UTF8多字节后需要的缓冲区大小
int len = MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, NULL, 0);
//创建多字节缓冲区
WCHAR* buffer = new WCHAR[len];
MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, buffer, len);
buffer[len - 1] = '\0';
wstring wstr(buffer);
delete[] buffer;
return wstr;
}
浏览器访问 http://localhost:9999/api/sendattatch