第一步:新建项目
1、打开VS点击创建新项目
2、选择空项目并点下一步(切记不能选错项目类型)
3、填写项目名称和路径,点击创建即可
新建好后项目是这样的比较干净
4、右击源文件,点击添加,新建http.cpp文件
第二步:前期准备
在http.cpp最上面引入依赖,并撰写main方法,打印错误日志的方法
#include<stdio.h>#include<string.h>#include<WinSock2.h>#include<sys/types.h>#include<sys/stat.h>#pragma comment(lib,"WS2_32.lib")#define PRINTF(str) printf("[%s - %d]"#str"%s",__func__,__LINE__,str);//打印错误日志void error_die(const char* str) { perror(str); exit(1);}int main(void) { return 0;}
第三步:网络初始化
初始化可以分为五步:1、网络通讯初始化===>>>2、创建套接字===>>>3、绑定端口===>>>4、绑定套接字===>>>5、创建监听队列
代码实现如下:
int startup(unsigned short *port) { //1、网络通讯初始化 WSADATA data; int res = WSAStartup(MAKEWORD(1,1), &data); if (res) { error_die("init fail"); } //2、创建套接字 int server_socket = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP); if (server_socket == -1) { error_die("sock create fail"); } //3、绑定端口 int opt = 1; res = setsockopt(server_socket,SOL_SOCKET,SO_REUSEADDR,(const char*) & opt, sizeof(opt)); if (res) { error_die("port bing fail"); } //4、绑定套接字 struct sockaddr_in server_addr; memset(&server_addr,0,sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(*port); server_addr.sin_addr.s_addr = htonl(INADDR_ANY); res = bind(server_socket,(struct sockaddr*) &server_addr, sizeof(server_addr)); if (res<0) { error_die("sock bing fail"); } //5、创建监听队列 int nameLen = sizeof(server_addr); if (*port == 0) { res = getsockname(server_socket, (struct sockaddr*)&server_addr,&nameLen); if (res) { error_die("dynamic sock create fail"); } *port = server_addr.sin_port; } res = listen(server_socket, 5); if (res < 0) { error_die("listen queque create fail"); } return server_socket;};
main方法修改如下:
int main(void) { //1、初始化 unsigned short port = 8000; int server_sock = startup(&port); printf("http have benn started ,listening [%d] port...",port); return 0;}
第四步:处理用户请求
1、报文背景知识
浏览器发起新的访问时,会向服务器端发送一个请求报文。例如,在浏览器地址输入 127.0.0.1:8000 回车后,服务器端收到的完整报文如下:
GET / HTTP/1.1/n
Host: 127.0.0.1:8000/n
Connection: keep-alive/n
Cache-Control: max-age=0/n
Upgrade-Insecure-Requests: 1/n
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36/n
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.9/n
Sec-Fetch-Site: none/n
Sec-Fetch-Mode: navigate/n
Sec-Fetch-User: ?1/n
Sec-Fetch-Dest: document/n
Accept-Encoding: gzip, deflate, br/n
Accept-Language: zh-CN,zh;q=0.9/n
/n
请求报文由4四个部分组成:请求行、请求头部行、空行、请求数据。具体格式如下:
2、具体处理
具体处理代码如下:
//从指定的客户端套接字读取一行数据,保持到buff中,返回实际读取到了字节数int get_line(int sock, char* buff, int size) { char c = 0; int i = 0; while (i < size - 1 && c != '/n') { int n = recv(sock, &c, 1, 0); if (n > 0) { if (c == '/r') { n = recv(sock, &c, 1, MSG_PEEK); if (n > 0 && c == '/n') { recv(sock, &c, 1, 0); } else { c = '/n'; } } buff[i++] = c; } else { c = '/n'; } } buff[i] = 0; return 0;}//向指定套接字,发送一个未支持提示还没有实现的错误页面void unimplement(int client) { char buf[1024]; sprintf(buf, "HTTP/1.0 501 Method Not Implemented/r/n"); send(client, buf, strlen(buf), 0); sprintf(buf, SERVER_STRING); send(client, buf, strlen(buf), 0); sprintf(buf, "Content-Type: text/html/r/n"); send(client, buf, strlen(buf), 0); sprintf(buf, "/r/n"); send(client, buf, strlen(buf), 0); sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented/r/n"); send(client, buf, strlen(buf), 0); sprintf(buf, "</TITLE></HEAD>/r/n"); send(client, buf, strlen(buf), 0); sprintf(buf, "<BODY><P>HTTP request method not supported./r/n"); send(client, buf, strlen(buf), 0); sprintf(buf, "</BODY></HTML>/r/n"); send(client, buf, strlen(buf), 0);}//向指定套接字,发送一个未支持提示还没有实现的错误页面void not_found(int client) { char buf[1024]; sprintf(buf, "HTTP/1.0 404 NOT FOUND/r/n"); send(client, buf, strlen(buf), 0); sprintf(buf, SERVER_STRING); send(client, buf, strlen(buf), 0); sprintf(buf, "Content-Type: text/html/r/n"); send(client, buf, strlen(buf), 0); sprintf(buf, "/r/n"); send(client, buf, strlen(buf), 0); sprintf(buf, "<HTML><TITLE>Not Found</TITLE>/r/n"); send(client, buf, strlen(buf), 0); sprintf(buf, "<BODY><P>The server could not fulfill/r/n"); send(client, buf, strlen(buf), 0); sprintf(buf, "your request because the resource specified/r/n"); send(client, buf, strlen(buf), 0); sprintf(buf, "is unavailable or nonexistent./r/n"); send(client, buf, strlen(buf), 0); sprintf(buf, "</BODY></HTML>/r/n"); send(client, buf, strlen(buf), 0);}//发送响应的头信息void headers(int client) { char buff[1024]; strcpy(buff, "HTTP/1.0 200 OK/r/n"); send(client, buff, strlen(buff), 0); strcpy(buff, "Server:MyHttpd/0.1/r/n"); send(client, buff, strlen(buff), 0); strcpy(buff, "Content-type:text/html/n"); send(client, buff, strlen(buff), 0); strcpy(buff, "/r/n"); send(client, buff, strlen(buff), 0);}//发送文件void cat(int client,FILE* resource) { char buff[4096]; int count = 0; while (1) { int ret = fread(buff, sizeof(char), sizeof(buff), resource); if (ret <= 0) { break; } send(client, buff, ret, 0); count += ret; } printf("total send [%d] to client/n",count);}void server_file(int client,const char* fileName) { char numchars = 1; char buff[1024]; while (numchars > 0 && strcmp(buff, "/n")) { numchars = get_line(client, buff, sizeof(buff)); PRINTF(buff); } FILE* resource = fopen(fileName,"r"); if (resource==NULL) { not_found(client); } else { //发送头信息 headers(client); //发送文件 cat(client, resource); printf("file send success"); } fclose(resource);}DWORD WINAPI accept_request(LPVOID arg) { char buff[1024]; int client = (SOCKET)arg; //1、获取第一行 int numchars = get_line(client, buff,sizeof(buff)); PRINTF(buff); char method[255]; int j = 0 ,i =0; while (!isspace(buff[j])&&i < sizeof(method)-1) { method[i++] = buff[j++]; } method[i] = 0; PRINTF(method); //2、检查请求方法是否支持 if (stricmp(method,"GET")&& stricmp(method, "POST")) { //向浏览器返回错误提示页面 unimplement(client); return 0; } //3、解析资源路径 char url[255]; i = 0; while (isspace(buff[j]) && j < sizeof(buff)) { j++; } while (!isspace(buff[j])&& sizeof(url)-1 && j < sizeof(buff)) { url[i++] = buff[j++]; } url[i] = 0; PRINTF(url); char path[512] = ""; sprintf(path, "htdocs%s", url); if (path[strlen(path)-1]=='/') { strcat(path, "index.html"); } PRINTF(path); struct stat status ; if (stat(path,&status)==-1) { //把请求包里的东西读完 while (numchars>0&&strcmp(buff,"/n")) { numchars = get_line(client, buff, sizeof(buff)); } PRINTF(buff); not_found(client); }else { if ((status.st_mode & S_IFMT)==S_IFDIR) { strcat(path, "index.html"); } server_file(client,path); } closesocket(client); return 0;}
github地址:
https://github.com/1756336885/miniWeb.git
gitee地址:
miniWeb: 迷你版的web,用C++撰写,后期会添加数据库,中间件相关的操作
参考文章:
2-创建项目_哔哩哔哩_bilibili
C语言手写HTTPD网站服务器_126775241csdn-CSDN博客