C实现简单web服务器-1(windows)
来源:互联网 发布:斗破沙城翅膀进阶数据 编辑:程序博客网 时间:2024/05/01 08:30
为了了解web服务器工作原理最实在的就是用C自己实现一个了,虽然实现的功能很少,不过却可以加深自己对http/1.1、web服务器的理解。
这里还是简单介绍下http/1.1协议吧:
web服务器的话需要处理接收的请求报文,比如chrome的话请求报文是这样的:(响应报文的话是需要自己构造的,下一篇介绍)
首先第一行:GET /favicon.ico HTTP/1.1
常见方法一般有GET、HEAD、POST,post一般用于表单,head是只请求首部,测试网页状态的时候就会用head方法,比如:curl -I www.xxx.com/index.html使用的就是HEAD方法
方法后面接url(比如这里就是想要请求/favicon.ico图片了),再后面就是http版本号了
第二行:HOST:localhost
指的就是请求的主机,可以用IP:PORT或域名+PORT指定;如果为空,应该返回400 bad request;像apache的虚拟主机配置(基于域名的主机),同一个ip却可以工作多台web服务器就是根据这个字段来区分是哪台web的请求的。
有三种虚拟主机配置:基于域名,基于ip,基于端口的;
对于http,默认端口是80,如果是基于端口的,用户每次请求还需要输入port,而且很多用户甚至不懂的怎么做,所以这种方法不常用;
基于ip的,现如今ip地址紧缺,使用基于ip也并不是什么好方法;
所以最常用的就是这个基于域名的虚拟主机了(申请一个域名相对与ip来说还是便宜很多的),不过别想太多,我并没有实现这个功能,有兴趣的可以自己去实现,也很简单,对host字段和你的几个虚拟主机进行比较然后转发即可。
第三行:Connection: keep-alive
使用持续连接,http/1.1默认就是持续的,这样的好处在如果你请求一个1.html,该html里面有100张图片时:非持续连接需要请求100+1次,持续连接只需要1次;http使用的是tcp传输协议,每次连接断开需要3次握手和4次挥手,这样就大大降低效率了;而且对于每次的连接,客户和服务器都要分配TCP缓冲区和保持TCP变量,也给服务器加大的负担。
对于apache2.4来说在../apache2/conf/extra/httpd-default.conf里面可以选择开启或关闭持续连接:
第5行:User-Agent: …
用户代理(这里即你的浏览器类型),你也可以根据这个来决定要不要让他访问,apache2.4中在http.conf或httpd-vhosts.conf中可以这样设置:
第6行:Accept-Encoding…
Accept-Encoding 是浏览器发给服务器,声明浏览器支持的编码类型,从这里可以看出chrome传输是使用gzip压缩传输的
第7行:Accept-Language: …
zh-CN就是支持中文了
下面来看看代码:
// SimpleHTTPServer.cpp// 功能:实现简单的web服务器功能,能同时响应多个浏览器的请求:// 1、如果该文件存在,则在浏览器上显示该文件;// 2、如果文件不存在,则返回404-file not found页面// 3、只支持GET、HEAD方法// HTTP1.1 与 1.0不同,默认是持续连接的(keep-alive)#include <Winsock2.h>#include <time.h>#include <stdio.h>#include <string.h>#include <direct.h> // 目录头文件#pragma comment(lib,"Ws2_32.lib")// http 默认端口是80,如果80端口被占用那么改个端口即可#define DEFAULT_PORT 80 #define BUF_LENGTH 1024#define MIN_BUF 128#define USER_ERROR -1#define SERVER "Server: csr_http1.1\r\n"int file_not_found(SOCKET sAccept);int file_ok(SOCKET sAccept, long flen);int send_file(SOCKET sAccept, FILE *resource);int send_not_found(SOCKET sAccept);DWORD WINAPI SimpleHTTPServer(LPVOID lparam){ SOCKET sAccept = (SOCKET)(LPVOID)lparam; char recv_buf[BUF_LENGTH]; char method[MIN_BUF]; char url[MIN_BUF]; char path[_MAX_PATH]; int i, j; // 缓存清0,每次操作前都要记得清缓存,养成习惯; // 不清空可能出现的现象:输出乱码、换台机器乱码还各不相同 // 原因:不清空会输出遇到 '\0'字符为止,所以前面的不是'\0' 也会一起输出 memset(recv_buf,0,sizeof(recv_buf)); if (recv(sAccept,recv_buf,sizeof(recv_buf),0) == SOCKET_ERROR) //接收错误 { printf("recv() Failed:%d\n",WSAGetLastError()); return USER_ERROR; } else printf("recv data from client:%s\n",recv_buf); //接收成功,打印请求报文 //处理接收数据 i = 0; j = 0; // 取出第一个单词,一般为HEAD、GET、POST while (!(' ' == recv_buf[j]) && (i < sizeof(method) - 1)) { method[i] = recv_buf[j]; i++; j++; } method[i] = '\0'; // 结束符,这里也是初学者很容易忽视的地方 // 如果不是GET或HEAD方法,则直接断开本次连接 // 如果想做的规范些可以返回浏览器一个501未实现的报头和页面 if (stricmp(method, "GET") && stricmp(method, "HEAD")) { closesocket(sAccept); //释放连接套接字,结束与该客户的通信 printf("not get or head method.\nclose ok.\n"); printf("***********************\n\n\n\n"); return USER_ERROR; } printf("method: %s\n", method); // 提取出第二个单词(url文件路径,空格结束),并把'/'改为windows下的路径分隔符'\' // 这里只考虑静态请求(比如url中出现'?'表示非静态,需要调用CGI脚本,'?'后面的字符串表示参数,多个参数用'+'隔开 // 例如:www.csr.com/cgi_bin/cgi?arg1+arg2 该方法有时也叫查询,早期常用于搜索) i = 0; while ((' ' == recv_buf[j]) && (j < sizeof(recv_buf))) j++; while (!(' ' == recv_buf[j]) && (i < sizeof(recv_buf) - 1) && (j < sizeof(recv_buf))) { if (recv_buf[j] == '/') url[i] = '\\'; else if(recv_buf[j] == ' ') break; else url[i] = recv_buf[j]; i++; j++; } url[i] = '\0'; printf("url: %s\n",url); // 将请求的url路径转换为本地路径 _getcwd(path,_MAX_PATH); strcat(path,url); printf("path: %s\n",path); // 打开本地路径下的文件,网络传输中用r文本方式打开会出错 FILE *resource = fopen(path,"rb"); // 没有该文件则发送一个简单的404-file not found的html页面,并断开本次连接 if(resource==NULL) { file_not_found(sAccept); // 如果method是GET,则发送自定义的file not found页面 if(0 == stricmp(method, "GET")) send_not_found(sAccept); closesocket(sAccept); //释放连接套接字,结束与该客户的通信 printf("file not found.\nclose ok.\n"); printf("***********************\n\n\n\n"); return USER_ERROR; } // 求出文件长度,记得重置文件指针到文件头 fseek(resource,0,SEEK_SET); fseek(resource,0,SEEK_END); long flen=ftell(resource); printf("file length: %ld\n", flen); fseek(resource,0,SEEK_SET); // 发送200 OK HEAD file_ok(sAccept, flen); // 如果是GET方法则发送请求的资源 if(0 == stricmp(method, "GET")) { if(0 == send_file(sAccept, resource)) printf("file send ok.\n"); else printf("file send fail.\n"); } fclose(resource); closesocket(sAccept); //释放连接套接字,结束与该客户的通信 printf("close ok.\n"); printf("***********************\n\n\n\n"); return 0;}// 发送404 file_not_found报头int file_not_found(SOCKET sAccept){ char send_buf[MIN_BUF]; // time_t timep; // time(&timep); sprintf(send_buf, "HTTP/1.1 404 NOT FOUND\r\n"); send(sAccept, send_buf, strlen(send_buf), 0);// sprintf(send_buf, "Date: %s\r\n", ctime(&timep));// send(sAccept, send_buf, strlen(send_buf), 0); sprintf(send_buf, "Connection: keep-alive\r\n"); send(sAccept, send_buf, strlen(send_buf), 0); sprintf(send_buf, SERVER); send(sAccept, send_buf, strlen(send_buf), 0); sprintf(send_buf, "Content-Type: text/html\r\n"); send(sAccept, send_buf, strlen(send_buf), 0); sprintf(send_buf, "\r\n"); send(sAccept, send_buf, strlen(send_buf), 0); return 0;}// 发送200 ok报头int file_ok(SOCKET sAccept, long flen){ char send_buf[MIN_BUF]; // time_t timep;// time(&timep); sprintf(send_buf, "HTTP/1.1 200 OK\r\n"); send(sAccept, send_buf, strlen(send_buf), 0); sprintf(send_buf, "Connection: keep-alive\r\n"); send(sAccept, send_buf, strlen(send_buf), 0);// sprintf(send_buf, "Date: %s\r\n", ctime(&timep));// send(sAccept, send_buf, strlen(send_buf), 0); sprintf(send_buf, SERVER); send(sAccept, send_buf, strlen(send_buf), 0); sprintf(send_buf, "Content-Length: %ld\r\n", flen); send(sAccept, send_buf, strlen(send_buf), 0); sprintf(send_buf, "Content-Type: text/html\r\n"); send(sAccept, send_buf, strlen(send_buf), 0); sprintf(send_buf, "\r\n"); send(sAccept, send_buf, strlen(send_buf), 0); return 0;}// 发送自定义的file_not_found页面int send_not_found(SOCKET sAccept){ char send_buf[MIN_BUF]; sprintf(send_buf, "<HTML><TITLE>Not Found</TITLE>\r\n"); send(sAccept, send_buf, strlen(send_buf), 0); sprintf(send_buf, "<BODY><h1 align='center'>404</h1><br/><h1 align='center'>file not found.</h1>\r\n"); send(sAccept, send_buf, strlen(send_buf), 0); sprintf(send_buf, "</BODY></HTML>\r\n"); send(sAccept, send_buf, strlen(send_buf), 0); return 0;}// 发送请求的资源int send_file(SOCKET sAccept, FILE *resource){ char send_buf[BUF_LENGTH]; while (1) { memset(send_buf,0,sizeof(send_buf)); //缓存清0 fgets(send_buf, sizeof(send_buf), resource); // printf("send_buf: %s\n",send_buf); if (SOCKET_ERROR == send(sAccept, send_buf, strlen(send_buf), 0)) { printf("send() Failed:%d\n",WSAGetLastError()); return USER_ERROR; } if(feof(resource)) return 0; } }int main(){ WSADATA wsaData; SOCKET sListen,sAccept; //服务器监听套接字,连接套接字 int serverport=DEFAULT_PORT; //服务器端口号 struct sockaddr_in ser,cli; //服务器地址,客户端地址 int iLen; printf("-----------------------\n"); printf("Server waiting\n"); printf("-----------------------\n"); //第一步:加载协议栈 if (WSAStartup(MAKEWORD(2,2),&wsaData) !=0) { printf("Failed to load Winsock.\n"); return USER_ERROR; } //第二步:创建监听套接字,用于监听客户请求 sListen =socket(AF_INET,SOCK_STREAM,0); if (sListen == INVALID_SOCKET) { printf("socket() Failed:%d\n",WSAGetLastError()); return USER_ERROR; } //创建服务器地址:IP+端口号 ser.sin_family=AF_INET; ser.sin_port=htons(serverport); //服务器端口号 ser.sin_addr.s_addr=htonl(INADDR_ANY); //服务器IP地址,默认使用本机IP //第三步:绑定监听套接字和服务器地址 if (bind(sListen,(LPSOCKADDR)&ser,sizeof(ser))==SOCKET_ERROR) { printf("blind() Failed:%d\n",WSAGetLastError()); return USER_ERROR; } //第五步:通过监听套接字进行监听 if (listen(sListen,5)==SOCKET_ERROR) { printf("listen() Failed:%d\n",WSAGetLastError()); return USER_ERROR; } while (1) //循环等待客户的请求 { //第六步:接受客户端的连接请求,返回与该客户建立的连接套接字 iLen=sizeof(cli); sAccept=accept(sListen,(struct sockaddr*)&cli,&iLen); if (sAccept==INVALID_SOCKET) { printf("accept() Failed:%d\n",WSAGetLastError()); break; } //第七步,创建线程接受浏览器请求 DWORD ThreadID; CreateThread(NULL,0,SimpleHTTPServer,(LPVOID)sAccept,0,&ThreadID); } closesocket(sListen); WSACleanup(); return 0;}
编译运行后打开浏览器输入:http://localhost/index.html
http默认是80端口,如果端口被占用改成其他的这时就要加上port了:
http://localhost:port/index.html
PS:server.cpp所在的目录就是网站的根目录
这里只做了最简单基础的功能,如果想扩充可以试着加上CGI的功能,即动态响应客户端的请求,因为windows下并没有自带perl、php或python脚本所以就没有实现了。
如果不想使用浏览器,下一篇也贴上了一个简单客户端的C代码,可以自己选择使用HEAD或GET方法请求。
部分参考unix的tinyhttpd1.0源码,有兴趣的可以去看看,虽然只有500多行,不过最基础的功能却都齐了。这里提醒一下那是unix的,部分头文件和linux还不完全一样,需要稍加修改才可以在linux下运行,而且版本是1.0的也要注意下,现在1.1版本和1.0还是有区别的
- C实现简单web服务器-1(windows)
- C实现简单web服务器-2(windows)
- 简单web服务器实现(C语言)
- c语言实现简单的web服务器
- [linux] c 实现简单的web服务器
- C语言实现简单的web服务器
- 用C语言实现的简单Web服务器(Linux)
- 用C语言实现的简单Web服务器(Linux)
- 用c写的一个简单web服务器实现
- 用C语言实现的简单Web服务器
- 用C语言实现的简单Web服务器(Linux)
- linux C学习之实现简单的web服务器
- C#实现简单WEB服务器
- C#实现简单WEB服务器
- 简单的web服务器实现
- c#实现简单Web服务器
- 实现简单的web服务器
- 实现简单的Web服务器
- 最短路径—Dijkstra算法和Floyd算法
- 第十三周项目3:立体类族共有的抽象类
- EEPROM和flash的区别
- Java Note - Everything is an object
- codeforces/#305 Div1/547/C Mike and Foam 【容斥】
- C实现简单web服务器-1(windows)
- Largest Rectangle in a Histogram(动态规划找最大矩形面积)
- 清平调 · 李白
- [编程题] 奇数位上都是奇数或者偶数位上都是偶数
- 关于hibernate通过注解方式自动生成表时字段的顺序问题
- makefile小记v2
- C++实现Bellmanford算法
- akoj-1272-字母统计
- linux下的apache配置文件详解