当前位置:首页 > CN2资讯 > 正文内容

C实现简单web服务器-1(windows)

2天前CN2资讯

为了了解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 /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还是有区别的。

    你可能想看:

    扫描二维码推送至手机访问。

    版权声明:本文由皇冠云发布,如需转载请注明出处。

    本文链接:https://www.idchg.com/info/31009.html

    分享给朋友:

    “C实现简单web服务器-1(windows)” 的相关文章