Socket网络编程--简单Web服务器(2)

*爱你&永不变心* 提交于 2019-12-24 22:25:09

  上一小节通过阅读开源的Web服务器--tinyhttpd。大概知道了一次交互的请求信息和应答信息的具体过程。接下来我就自己简单的实现一个Web服务器。

  下面这个程序只是实现一个简单的框架出来。这次先实现能够Accept客户端的请求。

  简单创建web服务器

  webserver.h

  1 #include <iostream>
  2 #include <string>
  3 #include <string.h>
  4 #include <stdio.h>
  5 #include <stdlib.h>
  6 #include <errno.h>
  7 #include <sys/types.h>
  8 #include <sys/socket.h>
  9 #include <netinet/in.h>
 10 #include <arpa/inet.h>
 11 #include <unistd.h>
 12 #include <pthread.h>
 13 #include <thread>//使用c++11的多线程
 14 
 15 using namespace std;
 16 
 17 class WebServer
 18 {
 19     public:
 20         WebServer();
 21         ~WebServer();
 22         int ServerInit(u_short port);
 23         int ServerError(string str);
 24         int ServerAccept();
 25         int ServerClose();
 26         int ServerRequest(int cli_fd);
 27         int get_line(int cli_fd,char * buf,int size);//来自tinyhttpd
 28 
 29         int Page_200(int cli_fd);
 30         int Page_501(int cli_fd);
 31     private:
 32         int httpd;
 33 };
 34 
 35 int WebServer::ServerRequest(int cli_fd)
 36 {
 37     char buf[1024];
 38     int size=1024;
 39     int i=1;
 40     memset(buf,0,sizeof(buf));
 41     while((i>0)&&strcmp("\n",buf))
 42     {
 43         i=get_line(cli_fd,buf,sizeof(buf));
 44         cout<<buf;
 45     }
 46     if(fork()==0)
 47     {
 48         //处理阶段
 49         execl("/bin/ls","ls","/home/myuser/",NULL);
 50     }
 51     Page_200(cli_fd);
 52     close(cli_fd);
 53     return 0;
 54 }
 55 int WebServer::ServerAccept()
 56 {
 57     struct sockaddr_in cli_sin;
 58     socklen_t cli_len=sizeof(cli_sin);
 59     int cli_fd;
 60     cli_fd=accept(httpd,(struct sockaddr *)&cli_sin,&cli_len);//阻塞等待连接
 61     if(cli_fd==-1)
 62         ServerError("Fail to accept");
 63     cout<<"连接进来的IP: "<<inet_ntoa(cli_sin.sin_addr)<<":"<<ntohs(cli_sin.sin_port)<<endl;
 64     return cli_fd;
 65 }
 66 int WebServer::ServerInit(u_short port)
 67 {
 68     struct sockaddr_in sin;
 69     int on;
 70     httpd=socket(PF_INET,SOCK_STREAM,0);
 71     if(httpd==-1)
 72         ServerError("Fail to Socket");
 73     //init sockaddr_in
 74     sin.sin_family=AF_INET;
 75     sin.sin_port=htons(port);
 76     sin.sin_addr.s_addr=htonl(INADDR_ANY);
 77     bzero(&(sin.sin_zero),8);
 78     setsockopt(httpd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
 79     if(::bind(httpd,(struct sockaddr *)&sin,sizeof(struct sockaddr))==-1)
 80         ServerError("Fail to bind");
 81     //如果port指定为零那么就随机打开一个端口
 82     if(port==0)
 83     {
 84         socklen_t len=sizeof(sin);
 85         if(getsockname(httpd,(struct sockaddr *)&sin,&len)==-1)
 86             ServerError("Fail to getsockname");
 87         port=ntohs(sin.sin_port);
 88     }
 89     if(listen(httpd,100)<0)
 90         ServerError("Fail to listen");
 91     return port;
 92 }
 93 ///////////////
 94 int WebServer::get_line(int cli_fd,char * buf,int size)
 95 {
 96     int i=0;
 97     char c='\0';
 98     int n;
 99     while((i<size-1)&&(c!='\n'))
100     {
101         n=recv(cli_fd,&c,1,0);
102         if(n>0)
103         {
104             if(c=='\r')
105             {
106                 n=recv(cli_fd,&c,1,MSG_PEEK);
107                 if((n>0)&&(c=='\n'))
108                     recv(cli_fd,&c,1,0);
109                 else
110                     c='\n';
111             }
112             buf[i]=c;
113             i++;
114         }
115         else
116             c='\n';
117     }
118     buf[i]='\0';
119     return i;
120 }
121 int WebServer::ServerError(string str)
122 {
123     perror(str.c_str());
124     exit(-1);
125 }
126 int WebServer::ServerClose()
127 {
128     close(httpd);
129     return 0;
130 }
131 int WebServer::Page_200(int cli_fd)
132 {
133     char buf[1024];
134     sprintf(buf, "HTTP/1.1 200 OK\r\n");
135     send(cli_fd, buf, strlen(buf), 0);
136     sprintf(buf, "Server:wunaozai.cnblogs.com\r\n");
137     send(cli_fd, buf, strlen(buf), 0);
138     sprintf(buf, "Content-Type: text/html\r\n");
139     send(cli_fd, buf, strlen(buf), 0);
140     sprintf(buf, "\r\n");
141     send(cli_fd, buf, strlen(buf), 0);
142     sprintf(buf, "<HTML><HEAD><TITLE>Hello World\r\n");
143     send(cli_fd, buf, strlen(buf), 0);
144     sprintf(buf, "</TITLE></HEAD>\r\n");
145     send(cli_fd, buf, strlen(buf), 0);
146     sprintf(buf, "<BODY><h1>Hello World</h1>\r\n");
147     send(cli_fd, buf, strlen(buf), 0);
148     sprintf(buf, "</BODY></HTML>\r\n");
149     send(cli_fd, buf, strlen(buf), 0);
150 }
151 int WebServer::Page_501(int cli_fd)
152 {
153     char buf[1024];
154     sprintf(buf, "HTTP/1.1 501 Method Not Implemented\r\n");
155     send(cli_fd, buf, strlen(buf), 0);
156     sprintf(buf, "Server:wunaozai.cnblogs.com");
157     send(cli_fd, buf, strlen(buf), 0);
158     sprintf(buf, "Content-Type: text/html\r\n");
159     send(cli_fd, buf, strlen(buf), 0);
160     sprintf(buf, "\r\n");
161     send(cli_fd, buf, strlen(buf), 0);
162     sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");
163     send(cli_fd, buf, strlen(buf), 0);
164     sprintf(buf, "</TITLE></HEAD>\r\n");
165     send(cli_fd, buf, strlen(buf), 0);
166     sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");
167     send(cli_fd, buf, strlen(buf), 0);
168     sprintf(buf, "</BODY></HTML>\r\n");
169     send(cli_fd, buf, strlen(buf), 0);
170 }
171 WebServer::~WebServer()
172 {
173 }
174 WebServer::WebServer()
175 {
176 }

  webserver.cpp

 1 #include "webserver.h"
 2 
 3 int main(int argc,char **argv)
 4 {
 5     WebServer ws;//实例化web服务器
 6     ws.ServerInit(8080);//打开8080端口
 7     pid_t pid;
 8     int cli_fd;
 9     while(1)
10     {
11         cli_fd=ws.ServerAccept();//程序会在这个函数阻塞
12         ws.ServerRequest(cli_fd);//这个函数会创建一个进程对请求头进行处理并发送应答信息给客户端
13     }
14     ws.ServerClose();//关闭服务器
15 
16     return 0;
17 }

  makefile

1 main:
2         g++ webserver.cpp -std=c++0x -g -o webserver
3 run:
4         ./webserver

  下面这个是运行时的截图

  增加了几个函数get_line(由于socket的读取方式好像没有一行一行的读取)各种Page信息还有一个ServerRequest函数。

  ServerRequest:这个函数里面有一个fork函数创建多进程。一开始我是把fork的创建放在主函数的,然后ServerRequest不用fork函数。但是最后会出现一个问题就是,每次在客户端发出请求后服务器一直没有给出应答,客户端浏览器一直处于加载状态,然后强制性终止程序,浏览器才会有反映。不知道原因,弄了很久。一直在想以前写的那篇HTTP是没有问题的。一查才知道原来我以前用的请求头Connection:close 而浏览器现在这个Connection默认的值是keep-alive。是长连接。所以才会出现这个情况。 

  get_line:由于socket没有一整行的读取数据,所以这里使用tinyhttpd这个程序里的代码。

  Page_200 Page_501 Page_404 ... ...

  到这里服务器可以简单的返回一个200ok的页面了。接下来要实现的是实现对第一行请求信息的处理,接下来的处理基本都是在ServerRequest这个函数里进行。

  带处理get/post方法的WEB服务器

 1 int WebServer::ServerRequest(int cli_fd)
 2 {
 3     char buf[1024];
 4     int size=1024;
 5     int i,j;
 6     char method[255];//用于保存请求方式
 7     char url[512];
 8     memset(buf,0,sizeof(buf));
 9     //获取第一行请求信息 一般格式为: GET / HTTP/1.1
10     //                               POST / HTTP/1.1
11     size=get_line(cli_fd,buf,sizeof(buf));
12     cout<<"\t\t"<<buf<<endl;
13     i=0,j=0;
14     //截取第一个单词
15     while(!isspace(buf[j]) && (i<sizeof(method)-1))
16     {
17         method[i]=buf[j];
18         i++;j++;
19     }
20     method[i]='\0';
21     //取第一个与第二个单词之间的空格
22     while(isspace(buf[j]) && (j<sizeof(buf)))
23         j++;
24     //截取第二个单词
25     i=0;
26     while(!isspace(buf[j]) && (i<sizeof(url)-1) && (j<sizeof(buf)))
27     {
28         url[i]=buf[j];
29         i++;j++;
30     }
31     url[i]='\0';
32 
33     if(strcasecmp(method,"GET") && strcasecmp(method,"POST"))
34     {
35         Page_501(cli_fd);
36         return -1;
37     }
38 
39     if(strcasecmp(method,"GET")==0)
40     {
41         cout<<"此次请求的方式是GET方法"<<endl;
42     }
43     else if(strcasecmp(method,"POST")==0)
44     {
45         cout<<"此次请求的方式是POST方法"<<endl;
46     }
47     cout<<"此次请求的地址为:"<<url<<endl;
48 
49     while((size>0)&&strcmp("\n",buf))
50     {
51         size=get_line(cli_fd,buf,sizeof(buf));
52     }
53 
54     if(fork()==0)
55     {
56         //处理阶段
57         //execl("/bin/ls","ls","/home/myuser/",NULL);
58         Page_200(cli_fd);
59     }
60     close(cli_fd);
61     return 0;
62 }

  运行的结果

  可以看出只要在浏览器地址栏写上什么就可以在GET后截取到,只是中文就显示成16进制了

  还有这个成功获取第一个页面后会有一个获取/favicon.ico这个请求,这个是自动的,我没有在地址栏输入的。如果有学过静态页面HTML编写的就知道,这个是网页的图标,一般在主目录的根目录下。

  在这里没有看到图标是由于这个favicon.ico不是通过简单text/html的Content-Type显示的所以这里就没有,等以后实现image发送就可以看到了。好了这一小节就到这里了。

 

  参考资料: http://blog.csdn.net/hanchaoman/article/details/5685582

  本文地址: http://www.cnblogs.com/wunaozai/p/3936295.html

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!