手搓 HTTP服务器 高并发HTTP服务器 C++ HTTP服务器
1、什么是socket
Socket是一种用于网络通信的编程接口,允许不同计算机之间通过网络发送和接收数据。它在客户端和服务器之间创建连接,支持多种协议,如TCP和UDP。
2、实现socket 通讯
1、socket通讯步骤
- 1、创建socket
- 2、绑定socket
- 3、监听socket
- 4、accpet 接受客户端连接请求
- 5、收发数据
2、具体实现
1、前骤
WSAStartupsocket编程要调用各种socket函数,但是需要库Ws2_32.lib和头文件Winsock2.h,这里的WSAStartup就是为了向操作系统说明,我们要用哪个库文件,让该库文件与当前的应用程序绑定,从而就可以调用该版本的socket的各种函数了。
#include <winsock.h>
#include <stdio.h>
#include <thread>//socket 需要的库函数
#pragma comment(lib,"ws2_32.lib");int main(){//初始化系统库指向,告知操作系统要使用socket库,版本号位 2WSADATA ws;WSAStartup(MAKEWORD(2, 2), &ws);//todo 后续操作 }
2、创建socket
- 参数解释
int socket(int af,int type,int protocol);
af: 地址族 一般使用AF_INET
type: 连接类型,我们是TCP 使用SOCK_STREAMTCP: SOCK_STREAMUDP: SOCK_DGRAM
protocol: 默认 0返回值: socket的句柄,如果该值大于0说明创建成功,小于等于0,创建失败!
- 代码实现
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock <= 0){printf("create socket fail\r\n");return 0;
}
3、bind 绑定socket
- 参数解释
int bind(int socket,const struct sockaddr *address,socklent address_len);1、socket 套接字2、address 地址结构体struct sockaddr_in {short sin_family; // 2 字节 ,地址族,e.g. AF_INET, AF_INET6unsigned short sin_port; // 2 字节 ,16位TCP/UDP 端口号 e.g. htons(3490),struct in_addr sin_addr; // 4 字节 ,32位IP地址char sin_zero[8]; // 8 字节 ,不使用};
3、address_len address 大小4、返回值: 0 绑定成功 其他值,绑定失败
- 代码实现
sockaddr_in saddr;
saddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //此处需要的值填写方式分为两种 1、采用inet_addr("绑定的ip地址") 2、绑定本地端口任一ip地址,直接填写 INADDR_ANY
saddr.sin_family = AF_INET;
saddr.sin_port = htons(8888); //此处htons 是将本地字节序转为网络字节序,因此我们设置端口号需要使用htons(8888)来转一次int b = bind(sock,(sockaddr*)&saddr,sizeof(saddr));
if(b != 0){printf("bind fail\r\n");return 0;
}
4、监听 Listen
- 参数解释
int listen(int sockfd, int backlog)1、sockfd:是调用socket()函数创建的socket描述符(套接字号)2、backlog:指定内核为此套接字维护的最大连接个数,该队列不能太长也不能没有,因为当队列太长时,需要耗费一定的资源进行维护和管理,没有的话也会降低操作系统的效率3、返回值: 成功时返回0,错误时返回-1。
- 代码实现
int l = listen(sock,100);
if(l != 0){printf("listen fail\r\n");return 0;
}
5、accept 接收客户端连接
- 参数解释
int client = accept (SOCKET s,sockaddr *addr,int *addrlen);
1、 s 服务器创建的socket
2、 addr 客户端连接后,客户端的地址相关数据会存在addr 里面
3、 addrlen addr 的长度,这里取的是地址
4、返回值 客户端套接字socket 当然,socket 小于或等于0 ,这个套接字无效注意
1、accept 默认是阻塞的,每次接收到客户端的连接就会向下运行一次
2、如果想取消阻塞 改为手动控制接受,可以采用 ioctlsocket函数取消阻塞unsigned long ul = 1;
int res = ioctlsocket(sock, FIONBIO, &ul);
if(res != 0){printf("ioctlsocket set fail\r\n");return 0;
}
- 代码实现
//这里我们使用了一个无限循环一直等待客户端连接while(true)
{sockaddr_in caddr;int caddrLen = sizeof(caddr);int client = accept(sock,(sockaddr*)&caddr,&caddrLen);if(client <= 0){continue;}//todo 收发消息, //1、这里开启了一个线程用于专门处理收发消息//2、th.detect 表示该线程独立//3、[=] 表示lambda表达式内部使用外部参数以值类型传递,//4、如果采用引用类型传递可以改为[&] //5、如果是[] 表示不引用外部参数,//6、当然也可以引用具体参数[client] std::thread th([=](){Run(client); //收发消息函数});th.detach();
}
6、收发消息
- 参数解释
int send (SOCKET s,char * buf,int len,int flags);1、s : 发送对象套接字socket
2、buff : 发送的数据
3、len : 发送的数据长度
4、flags: 默认0
5、返回值: 小于或等于0 发送失败,否则,发送成功
int recv (SOCKET s,char * buf,int len,int flags);
1、s : 发送对象套接字socket
2、buff : 接收的数据存储位置指针
3、len : 存储位置长度
4、flags: 默认0
5、返回值: 接收的数据长度,如果小于或等于0 接收失败
- 代码实现
void Run(int client)
{char buff[1024] = { 0 };while (true){//这里recv 默认是阻塞的,接收到消息会向下执行一次,因此采用while循环//解除阻塞任然采用ioctlsocket int len = recv(client, buff, sizeof(buff), 0);if (len <= 0){printf("接收失败,客户端已断开!");break;}//这里直接将收到的数据发送出去len = send(client, buff, len, 0);if (len <= 0) {printf("发送失败,客户端已断开!");break;}}
}
3、socket 服务器实现完整代码
#include <winsock.h>
#include <stdio.h>
#include <stdlib.h>
#include <thread>#pragma comment(lib,"ws2_32.lib");void Run(int client)
{char buff[1024] = { 0 };while (true){//这里recv 默认是阻塞的,接收到消息会向下执行一次,因此采用while循环//解除阻塞任然采用ioctlsocket int len = recv(client, buff, sizeof(buff), 0);if (len <= 0){printf("接收失败,客户端已断开!\r\n");break;}//这里直接将收到的数据发送出去len = send(client, buff, len, 0);if (len <= 0) {printf("发送失败,客户端已断开!\r\n");break;}}
}int main()
{WSADATA ws;WSAStartup(MAKEWORD(2, 2), &ws);int sock = socket(AF_INET, SOCK_STREAM, 0);if (sock <= 0) {printf("create socket fail\r\n");return 0;}sockaddr_in saddr;saddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //此处需要的值填写方式分为两种 1、采用inet_addr("绑定的ip地址") 2、绑定本地端口任一ip地址,直接填写 INADDR_ANYsaddr.sin_family = AF_INET;saddr.sin_port = htons(8888); //此处htons 是将本地字节序转为网络字节序,因此我们设置端口号需要使用htons(8888)来转一次int b = bind(sock, (sockaddr*)&saddr, sizeof(saddr));if (b != 0) {printf("bind fail\r\n");return 0;}int l = listen(sock, 100);if (l != 0) {printf("listen fail\r\n");return 0;}while (true){sockaddr_in caddr;int caddrLen = sizeof(caddr);int client = accept(sock, (sockaddr*)&caddr, &caddrLen);if (client <= 0) {continue;}//todo 收发消息, //1、这里开启了一个线程用于专门处理收发消息//2、th.detect 表示该线程独立//3、[=] 表示lambda表达式内部使用外部参数以值类型传递,//4、如果采用引用类型传递可以改为[&] //5、如果是[] 表示不引用外部参数,//6、当然也可以引用具体参数[client] std::thread th([=]() {//此处使用 inet_ntoa 将 sin_addr 转为ipchar* ip = inet_ntoa(caddr.sin_addr);//此处使用 ntohs 将sin_port 转为端口号int port = ntohs(caddr.sin_port);printf("客户端%s - %d已连接!\r\n", ip, port);Run(client); //收发消息函数});th.detach();}//关闭套接字closesocket(sock);//清理WSACleanup();return 0;
}
3、正则表达式
- 正则表达式在线测试地址
https://www.jyshare.com/front-end/854/
1、语法参考
2、实现一个简单的匹配
- 解释
匹配字符串 GET /test HTTP/1.1正则表达式 (^[A-Za-z]+) ([/A-Za-z]+) HTTP/1.11、 ^ 表示必须以什么开始,我这里是以字母开始,
2、 [A-Za-z] 表示 从大写字符A-Z和小写字符a-z中任意一个匹配上就算成功, +号表示至少匹配到一个
3、 () 括号表示分组
int main()
{std::string input = "GET /test HTTP/1.1";std::regex reg("(^[A-Za-z]+) ([/A-Za-z]+) HTTP/1.1");std::smatch sma;auto res = std::regex_search(input, sma, reg);if (!res){printf("match fail\r\n");return 0;}//括号分组后 默认的第一组是原字符串//第二组就是第一个括号//第三组就是第二个括号,依次类推auto sub_match1 = sma[0]; //1、GET /test HTTP/1.1auto sub_match2 = sma[1]; //2、GETauto sub_match3 = sma[2]; //3、/testreturn 0;
}
4、字符串操作
字符串操作方法可能会出现不安全问题的报错,请在main 方法的c/cpp文件第一行定义,如果定义过就不需要重复定义了
#define _CRT_SECURE_NO_WARNINGS
1、strstr
- 从左向右查找指定字符串并截取到字符串末尾
std::string re = strstr("11&22&33&44", "&");//re = &22&33&44
2、std::String 中的 substr
- 通过给定的索引截取字符串
std::string inputStr = "123456789";
std::string re = inputStr.substr(0, 5);//re = 12345
3、strchr
- 从左向右查找指定字符并截取到字符串末尾
std::string re = strstr("11&22&33&44", '&');//re = &22&33&44
3、strrchr
- 从右向左查找指定字符并截取到字符串末尾
std::string re = strstr("11&22&33&44", '&');//re = &44
5、strcmp
- 使用ascii的方式比较字符串是否相等
- 相等返回0
int re = strcmp("aaa", "aaa");
6、std::String 中的 replace
- 指定替换位置,指定替换长度,替换指定的字符串
- string replace (size_t pos, size_t len, const string& str);
- pos 替换开始位置
- len 被替换字符串长度
- str 新的字符串
std::string inputStr = "abcd1234";
std::string re = inputStr.replace(0,4,"");// re = 1234
5、获取字符串长度
- std::string 直接通过size() 属性获取
- char * 通过 strlen 返回值就是字符串长度
5、文件操作
文件操作方法可能会出现不安全问题的报错,请在main 方法的c/cpp文件第一行定义,如果定义过就不需要重复定义了
#define _CRT_SECURE_NO_WARNINGS
1、c 语言操作文件 FILE
1、fopen 打开文件
FILE* fopen(char const* _FileName,char const* _Mode)参数 1、文件路径 2、打开方式
打开方式: "r" 打开一个用于读取的文件。该文件必须存在。"w" 创建一个用于写入的空文件。如果文件名称与已存在的文件相同,则会删除已有文件的内容,文件被视为一个新的空文件。"a" 追加到一个文件。写操作向文件末尾追加数据。如果文件不存在,则创建文件。"r+" 打开一个用于更新的文件,可读取也可写入。该文件必须存在。"w+" 创建一个用于读写的空文件。"a+" 打开一个用于读取和追加的文件。
2、fseek() 移动文件指针位置
int fseek(FILE * _Stream,long _Offset,int _Origin);参数 1、文件指针 2、偏移距离,这个偏移是在参数3移动位置的枚举类型确定了的基础上再偏移固定距离,如: 3选择为文件开始,那么偏移距离就是这个固定距离,如果选择为文件末尾,那么就是文件大小+固定距离 3、移动位置枚举SEEK_CUR //当前光标SEEK_END //文末SEEK_SET //文首
3、ftell() 获取文件大小
int ftell(FILE * _Stream);参数:1、文件指针
返回值:文件大小
4、fread() 读取文件
size_t fread(void* _Buffer,size_t _ElementSize,size_t _ElementCount,FILE * _Stream);参数:1、读取的流存放位置指针2、存放位置的参数元素大小3、存放位置的参数长度4、文件指针5、返回值 读取数据的长度//定义一个char 类型的数组存放char buff[1024] = { 0 };//读取文件内容存放到char 数组中,当然一次可能读取不完,需要多次读取和转移int len = fread(buff, sizeof(char), sizeof(buff), fp);
5、fwrite() 写入文件
size_t fwrite(void* _Buffer,size_t _ElementSize,size_t _ElementCount,FILE * _Stream);参数:1、写入的流存放位置指针2、存放位置的参数元素大小3、存放位置的参数长度4、文件指针5、返回值 写入数据的长度//定义一个char 类型的数组存放char buff[1024] = { 0 };//读取文件内容存放到char 数组中,当然一次可能读取不完,需要多次读取和转移int len = fwrite(buff, sizeof(char), sizeof(buff), fp);
fclose(fp);
7、案例
//安全报警定义
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>int main()
{FILE* fp = fopen(R"(C:\Users\70970\Desktop\111.txt)", "rb");if (fp == nullptr){printf("打开读取文件失败!");return 0;}//将光标移动到文末fseek(fp, 0, SEEK_END);//获取文件大小int fileSize = ftell(fp);printf("文件大小%d!",fileSize);//将光标移动到文件头,便于读取操作,如果是从文末写入,就直接在文末就可以了,输出默认移动到文末fseek(fp, 0, SEEK_SET);char buff[1024] = { 0 };//定义输出FILE* fw = fopen(R"(C:\Users\70970\Desktop\222.txt)", "wb");if(fw == nullptr){printf("打开写入文件失败!");return 0;}//循环读取和写出while (true){int len = fread(buff, sizeof(char), sizeof(buff), fp);if (len <= 0) {break;}len = fwrite(buff, sizeof(char), len, fw);if(len <= 0){printf("写入失败!");break;}}//关闭fclose(fw);fclose(fp);return 0;
}
2、c++ fstream类操作文件
1、open 打开文件
void open(const char* _Filename, ios_base::openmode _Mode)参数:1、文件名2、打开模式std::ios::in 输入std::ios::out 输出(覆盖源文件)std::ios::app 所有数据追加在末尾(在源文件末尾追加内容)std::ios::ate 打开一个输出文件并移动到文件末尾。数据可以写入文件的任何位置std::ios::trunc 如果文件已存在,则丢弃文件内容(ios::out的缺省方式)std::ios::binary 以二进制格式输入输出(用于图片,视频,可执行文件等)
2、seekg 移动文件读取时的位置
basic_istream& seekg(off_type _Off, ios_base::seekdir _Way) 参数:1、偏移距离2、移动方式枚举ios::beg 从文件头开始计算偏移量ios::end 从文件末尾开始计算偏移量ios::cur 从当前位置开始计算偏移量file.seekp(32L, ios::beg); 将写入位置设置为从文件开头开始的第 33 个字节(字节 32)
file.seekp(-10L, ios::end); 将写入位置设置为从文件末尾开始的第 11 个字节(字节 10)
file.seekp(120L, ios::cur); 将写入位置设置为从当前位置开始的第 121 个字节(字节 120)
3、seekp 移动文件写入时的位置
basic_istream& seekp(off_type _Off, ios_base::seekdir _Way) 参数:1、偏移距离2、移动方式枚举ios::beg 从文件头开始计算偏移量ios::end 从文件末尾开始计算偏移量ios::cur 从当前位置开始计算偏移量file.seekg(2L, ios::beg); 将读取位置设置为从文件开头开始的第 3 个字节(字节 2)
file.seekg(-100L, ios::end); 将读取位置设置为从文件末尾开始的第 101 个字节(字节 100)
file.seekg(40L, ios::cur); 将读取位置设置为从当前位置开始的第 41 个字节(字节 40)
file.seekg(0L, ios:rend); 将读取位置设置为文件末尾
4、tellg 获取文件读取时偏移位置
int fileSize = fs.tellg();//读取时偏移位置
5、tellp 获取文件写入时偏移位置
int fileSize = fs.tellp();//写入时偏移位置
6、eof 文末标识
光标是否在文末
bool iseof = fr.eof();
7、gcount 实际读取长度
先执行读取后,再调用gcount 获取读取长度
fr.read();
int readLen = fr.gcount();
7、std::getline 逐行读取文件
- 1、std::getline 是标准库的方法
按照默认的换行为一行进行读取
basic_istream<_Elem, _Traits>& getline(basic_istream<_Elem, _Traits>& _Istr, basic_string<_Elem, _Traits, _Alloc>& _Str);参数:1、文件指针2、输出读取内容#include <iostream>
#include <fstream>
#include <string>int main() {std::fstream file("example.txt", std::ios::in);if (!file.is_open()) {std::cerr << "Error opening file." << std::endl;return 1;}// 逐行读取文件std::string line;while (std::getline(file, line)) {std::cout << line << std::endl;}// 关闭文件file.close();return 0;
}
8、read 读取文件
int main()
{std::fstream fr;fr.open(R"(C:\Users\Desktop\111.txt)", std::ios::in);fr.seekg(0, std::ios::end);int fileSize = fr.tellg();fr.seekg(0, std::ios::beg);char* buff = new char[fileSize];fr.read(buff, fileSize);printf("%s", buff);fr.close();delete []buff;return 0;
}
10、write 写入文件
basic_istream& write(_Elem* _Str, streamsize _Count);参数:1、写入数据存放位置2、存放位置长度
返回值: 实际写入内容长度char buff[1024] = { 0 };
int len = fs.write(buff, sizeof(buff));
11、close 关闭
fs.close();
12、案例
#define _CRT_SECURE_NO_WARNINGS#include <fstream>
#include <stdio.h>
#include <string>int main()
{std::fstream fr,fw;fr.open(R"(C:\Users\Desktop\111.txt)", std::ios::in);fr.seekg(0, std::ios::end);int fileSize = fr.tellg();printf("文件大小%d\r\n", fileSize);fr.seekg(0, std::ios::beg);fw.open(R"(C:\Users\Desktop\222.txt)", std::ios::out);char buff[1024] = { 0 };while (!fr.eof()){fr.read(buff, sizeof(buff));//实际读取长度int readLen = fr.gcount();//如果不用文末标识,可判断readLen 是否小于等于0,如果是,直接退出循环if(readLen <= 0) break;fw.write(buff, readLen);}return 0;
}
6、实现HTTP通讯
HTTP 通讯就是根据浏览器请求类型返回对应的数据,通讯完毕关闭连接。
1、解析浏览器请求的URL
后台接收到浏览器请求数据样式,我们用正则表达式截取第一行URL即可
GET /Index.html HTTP/1.1
Host: 127.0.0.1:8888
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: "Google Chrome";v="129", "Not=A?Brand";v="8", "Chromium";v="129"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9
Cookie: AdminCode=vA9U
1、分析浏览器请求和后台接收数据(截取第一行)
浏览器请求地址
- 127.0.0.1:8888
- 127.0.0.1:8888/Index.html
- 127.0.0.1:8888/loginSpecial/css/default.css
- 127.0.0.1:8888/loginSpecial/js/controlLogin.js
- 127.0.0.1:8888/loginSpecial/layui/css/modules/layer/default/icon.png
- 127.0.0.1:8888/loginSpecial/images/favicon.ico
- 127.0.0.1:8888/loginSpecial/js/controlLogin.js?Index=1
服务器收到
- GET / HTTP/1.1
- GET /Index.html HTTP/1.1
- GET /loginSpecial/css/default.css HTTP/1.1
- GET /loginSpecial/js/controlLogin.js HTTP/1.1
- GET /loginSpecial/layui/css/modules/layer/default/icon.png HTTP/1.1
- GET /loginSpecial/images/favicon.ico HTTP/1.1
- GET //loginSpecial/js/controlLogin.js?Index=1 HTTP/1.1
2、通过正则表达式解析截取第一行
这里接收到的数据直接进行正则表达式处理
//源数据,这里只拿了第一行测试char buff[10240] = "GET /Index.html HTTP/1.1";printf("%s\r\n", buff);if(len <= 0) return;//todo 解析接收字符串 GET / HTTP/1.1std::regex reg(R"((^[A-Za-z]+) ([/A-Za-z0-9-.=_?]+) HTTP/1.)");std::smatch sma;std::string src = buff;bool result = std::regex_search(src, sma, reg);if (!result) {printf("no match\r\n%s", buff);return;}std::string method = sma[1]; //GET /POSTstd::string url = sma[2]; ///Index.html
3、处理url
- 1、将问号后面的数据扔掉,问号后面的数据一般为参数(如查询参数),我们这里暂时扔掉
size_t index = url.find("?");
if(index != -1)
{std::string subStr = strstr(url.c_str(), "?");url.replace(url.size() - subStr.size(), subStr.size(), "");
}
- 2、判断url是否是/,如果是/我们默认下发Index.html文件
if(strcmp(url.c_str(),"/") == 0)
{url = "/Index.html";
}
- 3、判断文件类型,组装返回文件的Content-Type
std::string type = strrchr(url.c_str(), '.');
if(strcmp(type.c_str(),".js") == 0)
{//reMsg += "application/javascript";reMsg += "application/javascript;charset=utf-8";
}else if (strcmp(type.c_str(), ".html") == 0)
{reMsg += "text/html";
}else if (strcmp(type.c_str(), ".png") == 0)
{reMsg += "image/png";
}else if (strcmp(type.c_str(), ".ico") == 0)
{reMsg += "image/ico";
}
else if (strcmp(type.c_str(), ".css") == 0)
{reMsg += "text/css";
}
else
{reMsg += "text/html";
}
-
4、根据文件类型获取文件大小
-
5、组装返回头
返回头格式:
1、HTTP/1.1 200 OK\r\n //开头 以\r\n分割
2、Content-Type: image/png\r\n //设置返回类型 以\r\n分割
3、Content-Length: 10\r\n //设置内容长度 以\r\n分割
文本头结束最后是两个\r\n
示例:
HTTP/1.1 200 OK\r\nContent-Type: image/png\r\nContent-Length: 10\r\n\r\n
开始组装头并发送
std::string reMsg = "HTTP/1.1 200 OK\r\n";reMsg += "Content-Type: ";//todo type;std::string type = strrchr(url.c_str(), '.');if(strcmp(type.c_str(),".js") == 0){//reMsg += "application/javascript";reMsg += "application/javascript;charset=utf-8";}else if (strcmp(type.c_str(), ".html") == 0){reMsg += "text/html";}else if (strcmp(type.c_str(), ".png") == 0){reMsg += "image/png";}else if (strcmp(type.c_str(), ".ico") == 0){reMsg += "image/ico";}else if (strcmp(type.c_str(), ".css") == 0){reMsg += "text/css";}else{reMsg += "text/html";}reMsg += "\r\n";reMsg += "Content-Length: ";//todo sizewhile (true){index = url.find("/");if(index == -1){break;}url.replace(index, 1, "\\");}//_pgmptr 是获取当前执行程序的路径std::string basePath = _pgmptr;//获取当前执行程序的路径if(basePath.find("\\") == -1) return; std::string subStr = strrchr(basePath.c_str(), '\\');basePath.replace(basePath.size() - subStr.size(), subStr.size(), "");//此处我将资源文件放入wwww文件夹下,所以有个www的路径std::string fileName = basePath + "\\" + "www" + url;FILE* fp = nullptr;fopen_s(&fp, fileName.c_str(), "rb");if (fp == nullptr) {return;}fseek(fp, 0, SEEK_END);int fileSize = ftell(fp);fseek(fp, 0, SEEK_SET);reMsg += fileSize;reMsg += "\r\n\r\n";//发送头send(client, reMsg.c_str(), reMsg.size(), 0);
发送内容
char sendBuff[1024] = { 0 };
while (true)
{int rLen = fread(sendBuff, sizeof(char), sizeof(sendBuff), fp);if(rLen <= 0) break;rLen = send(client, sendBuff, rLen, 0);if(rLen <= 0) break;
}
fclose(fp);
7、完整案例源码
#define _CRT_SECURE_NO_WARNINGS
#include <regex>
#include <winsock.h>
#include <stdlib.h>
#include <stdio.h>
#include <thread>void HandleUrl(int client,std::string url)
{if(url.empty()) return;size_t index = url.find("?");if(index != -1){std::string subStr = strstr(url.c_str(), "?");url.replace(url.size() - subStr.size(), subStr.size(), "");}printf("%s\r\n", url.c_str());if(strcmp(url.c_str(),"/") == 0){url = "/Index.html";}std::string reMsg = "HTTP/1.1 200 OK\r\n";reMsg += "Content-Type: ";//todo type;std::string type = strrchr(url.c_str(), '.');if(strcmp(type.c_str(),".js") == 0){//reMsg += "application/javascript";reMsg += "application/javascript;charset=utf-8";}else if (strcmp(type.c_str(), ".html") == 0){reMsg += "text/html";}else if (strcmp(type.c_str(), ".png") == 0){reMsg += "image/png";}else if (strcmp(type.c_str(), ".ico") == 0){reMsg += "image/ico";}else if (strcmp(type.c_str(), ".css") == 0){reMsg += "text/css";}else{reMsg += "text/html";}reMsg += "\r\n";reMsg += "Content-Length: ";//todo sizewhile (true){index = url.find("/");if(index == -1){break;}url.replace(index, 1, "\\");}std::string basePath = _pgmptr;if(basePath.find("\\") == -1) return; std::string subStr = strrchr(basePath.c_str(), '\\');basePath.replace(basePath.size() - subStr.size(), subStr.size(), "");std::string fileName = basePath + "\\" + "www" + url;FILE* fp = nullptr;fopen_s(&fp, fileName.c_str(), "rb");if (fp == nullptr) {return;}fseek(fp, 0, SEEK_END);int fileSize = ftell(fp);fseek(fp, 0, SEEK_SET);reMsg += fileSize;reMsg += "\r\n\r\n";send(client, reMsg.c_str(), reMsg.size(), 0);char sendBuff[1024] = { 0 };while (true){int rLen = fread(sendBuff, sizeof(char), sizeof(sendBuff), fp);if(rLen <= 0) break;rLen = send(client, sendBuff, rLen, 0);if(rLen <= 0) break;}fclose(fp);
}void Run(sockaddr_in caddr,int client)
{char buff[10240] = { 0 };int len = recv(client, buff, sizeof(buff), 0);printf("%s\r\n", buff);if(len <= 0) return;//todo 解析接收字符串 GET / HTTP/1.1std::regex reg(R"((^[A-Za-z]+) ([/A-Za-z0-9-.=_?]+) HTTP/1.)");std::smatch sma;std::string src = buff;bool result = std::regex_search(src, sma, reg);if (!result) {printf("no match\r\n%s", buff);return;}HandleUrl(client, sma[2]);
}int main()
{WSADATA ws;WSAStartup(MAKEWORD(2, 2), &ws);int sock = socket(AF_INET, SOCK_STREAM, 0);if(sock <= 0){printf("create socket fail\r\n");return 0;}sockaddr_in saddr;//saddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");saddr.sin_addr.S_un.S_addr = INADDR_ANY;saddr.sin_family = AF_INET;saddr.sin_port = htons(8888);int b = bind(sock, (sockaddr*)&saddr, sizeof(saddr));if(b != 0){printf("bind fail\r\n");return 0;}int l = listen(sock, 1000);if(l != 0){printf("listen fail\r\n");return 0;}while (true){sockaddr_in caddr;int caddrLen = sizeof(caddr);int client = accept(sock, (sockaddr*)&caddr, &caddrLen);if(client <= 0) break;std::thread th([=](){Run(caddr, client);closesocket(client);});th.detach();}closesocket(sock);WSACleanup();return 0;
}
8、高并发测试
采用AB进行高并发测试
.\ab.exe -n 1000 -c 100 "http://127.0.0.1:8888/"
9、资料路径
链接: https://pan.baidu.com/s/1vR_wB-Jrwn1lCFRmIRbCHw?pwd=56ts 提取码: 56ts
提示: www 文件放到执行程序下面