Socket网络编程--简单Web服务器(2)
上一小节通过阅读开源的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/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 如果文中有什么错误,欢迎指出。以免更多的人被误导。有需要沟通的,可以站内私信,文章留言,或者关注“无脑仔的小明”公众号私信我。一定尽力回答。 |