手搓 HTTP服务器 手把手带你实现高并发HTTP服务器 C++ HTTP服务器 服务器项目实战 高性能服务器实战 服务器项目 服务器

手搓 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);
  • 6、fclose() 关闭文件
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 文件放到执行程序下面

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/435159.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Milvus - 架构设计详解

Milvus 是一个专为在大规模密集向量数据集上进行相似性搜索而设计的开源向量数据库系统。其架构建立在流行的向量搜索库之上&#xff0c;如 Faiss、HNSW、DiskANN 和 SCANN&#xff0c;能够处理数百万、数十亿甚至数万亿的向量数据。为了全面了解 Milvus 架构&#xff0c;我们首…

苏州 数字化科技展厅展馆-「世岩科技」一站式服务商

数字化科技展厅展馆设计施工是一个综合性强、技术要求高的项目&#xff0c;涉及到众多方面的要点。以下是对数字化科技展厅展馆设计施工要点的详细分析&#xff1a; 一、明确目标与定位 在设计之初&#xff0c;必须明确展厅的目标和定位。这包括确定展厅的主题、目标受众、展…

实用SQL小总结

WHERE 条件 column 为纯英文字符 或 不包含任何字符 语法&#xff1a; SELECT * FROM your_table WHERE REGEXP(your_column,^[A-Za-z]$); SELECT * FROM your_table WHERE NOT REGEXP(your_column,^[A-Za-z]$);例&#xff1a; SELECT DISTINCT t.pldlibho FROM kibb_pldlyw…

Naive UI 选择器 Select 的:render-label 怎么使用(Vue3 + TS)

项目场景&#xff1a; 在Naive UI 的 选择器 Select组件中 &#xff0c;如何实现下面的效果 &#xff0c;在下拉列表中&#xff0c;左边展示色块&#xff0c;右边展示文字。 Naive UI 的官网中提到过这个实现方法&#xff0c;有一个render-label的api&#xff0c;即&#xff…

数据库操作:数据类型

0. 铺垫 1.数值类型 注&#xff1a;此图的最大值都要减1&#xff1b;因为我的错误&#xff0c;后面会改正&#xff1b; 1.0、tinyint 类型大小——1字节 create table tt1(num tinyint); insert into tt1 values(1); insert into tt1 values(128); -- 越界插入&#xff0c;…

【Android】 IconFont的使用

SVG 的特点&#xff1a; 矢量图形&#xff1a;SVG 使用基于路径的矢量图形&#xff0c;这意味着图形可以无限放大而不失真&#xff0c;非常适合需要多种分辨率的应用。 可伸缩性&#xff1a;SVG 文件的大小通常比位图小&#xff0c;这使得它们在网页上加载更快。 编辑和创作&…

Text-to-SQL方法研究

有关Text-to-SQL实现细节&#xff0c;可以查阅我的另一篇文章text-to-sql将自然语言转换为数据库查询语句 1、面临的挑战 自然语言问题往往包含复杂的语言结构,如嵌套语句、倒装句和省略等,很难准确映射到SQL查询上。此外,自然语言本身就存在歧义,一个问题可能有多种解读。消除…

11.C++程序中的常用函数

我们将程序中反复执行的代码封装到一个代码块中&#xff0c;这个代码块就被称为函数&#xff0c;它类似于数学中的函数&#xff0c;在C程序中&#xff0c;有许多由编译器定义好的函数&#xff0c;供大家使用。下面就简单说一下&#xff0c;C中常用的函数。 1.sizeof sizeof函…

spring boot 项目中redis的使用,key=value值 如何用命令行来查询并设置值。

1、有一个老项目&#xff0c;用到了网易云信&#xff0c;然后这里面有一个AppKey&#xff0c;然后调用的时候要在header中加入这些标识&#xff0c;进行与服务器进行交互。 2、开发将其存在了redis中&#xff0c;一开始的时候&#xff0c;我们测试用的老的key&#xff0c;然后提…

ROS学习笔记(二):鱼香ROS — 超便捷的一键安装/配置/换源指令(Ubuntu/ROS/ROS2/IDE等)

文章目录 前言鱼香ROS1 一键安装&#xff1a;快速搭建开发环境2 具体使用2.1 如何开始&#xff1f;2.2 我的常用配置方案2.3 安装示例 3 总结相关链接 前言 关于Ubuntu与ROS的常规安装&#xff0c;可以看这几篇。 SLAM实操入门&#xff08;一&#xff09;&#xff1a;在已有…

【若依RuoYi-Vue | 项目实战】帝可得后台管理系统(三)

文章目录 一、商品管理1、需求说明2、生成基础代码&#xff08;1&#xff09;创建目录菜单&#xff08;2&#xff09;配置代码生成信息&#xff08;3&#xff09;下载代码并导入项目 3、商品类型改造&#xff08;1&#xff09;基础页面 4、商品管理改造&#xff08;1&#xff0…

【ADC】使用仪表放大器驱动 SAR 型 ADC 时的输入输出范围

概述 本文学习于TI 高精度实验室课程&#xff0c;介绍使用仪表放大器时 SAR ADC 驱动放大器的注意事项。具体包括&#xff1a;介绍如何使用仪表放大器设计数据转换器驱动电路。 仪表放大器&#xff08;Instrumentation Amplifier&#xff0c;下文简称 INA&#xff09;可抑制输…

开关电源为什么要进行负载测试,负载测试都包含哪些项目?

开关电源在现代电子设备中占据着重要的地位&#xff0c;其性能的稳定性和可靠性直接影响着电子设备的正常运行。为了确保开关电源的质量&#xff0c;需要对其进行负载测试。负载测试可以模拟实际工作环境中的负载情况&#xff0c;检测开关电源在不同负载条件下的输出特性、稳定…

wireshark使用要点

目录 IP过滤 端口过滤 内容过滤 过滤udp 过滤tcp IP过滤 ip.src XXX.XXX.XXX.XXX 只显示消息源地址为XXX.XXX.XXX.XXX的信息 ip.dst XXX.XXX.XXX.XXX 只显示消息目的地址为XXX.XXX.XXX.XXX的信息 ip.addr XXX.XXX.XXX.XXX显示消息源地址为XXX.XXX.XXX.XXX&#xff0…

Python库matplotlib之四

Python库matplotlib之四 小部件(widget)RadioButtons构造器APIs应用实列 Slider构造器APIs应用实列 小部件(widget) 小部件(widget)可与任何GUI后端一起工作。所有这些小部件都要求预定义一个Axes实例&#xff0c;并将其作为第一个参数传递。 Matplotlib不会试图布局这些小部件…

DTH11温湿度传感器

DHT11 是一款温湿度复合传感器&#xff0c;常用于单片机系统中进行环境温湿度的测量。以下是对 DHT11 温湿度传感器的详细讲解&#xff1a; 一、传感器概述 DHT11 数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温湿度传感…

【STM32】江科大STM32笔记汇总(已完结)

STM32江科大笔记汇总 STM32学习笔记课程简介(01)STM32简介(02)软件安装(03)新建工程(04)GPIO输出(05)LED闪烁& LED流水灯& 蜂鸣器(06)GPIO输入(07)按键控制LED 光敏传感器控制蜂鸣器(08)OLED调试工具(09)OLED显示屏(10)EXTI外部中断(11)对射式红外传感器计次 旋转编码器…

C++:模拟实现vector

目录 成员变量与迭代器 size capacity empty 迭代器有关函数 实现默认成员函数的前置准备 reserve ​编辑 ​编辑 push_back 构造函数 无参构造 迭代器区间构造 n个val来进行构造 析构函数 拷贝构造函数 赋值重载 增删查改 clear resize pop_back inser…

git add成功后忘记commit的文件丢了?

本文目标&#xff1a;开发人员&#xff0c;在了解git fsck命令用法的条件下&#xff0c;进行git add成功但由于误操作导致丢失的文件找回&#xff0c;达到找回丢失文件的程度。 文章目录 1 痛点2 解决方案3 总结/练习 1 痛点 开发过程中&#xff0c;分支太多&#xff08;基线分…

通信工程学习:什么是MIMO多输入多输出技术

MIMO:多输入多输出技术 MIMO(Multiple-Input Multiple-Output)多输入多输出技术是一种在无线通信中广泛应用的技术,它通过利用多个天线进行数据传输和接收,可以显著提高无线通信系统的性能和容量。以下是对MIMO技术的详细解释: 一、定义与原理 MIMO技术…