您的位置:首页 > 理论基础 > 计算机网络

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

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

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

  简单创建web服务器

  webserver.h

#include <iostream>
#include <string>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
#include <thread>//使用c++11的多线程

using namespace std;

class WebServer
{
public:
WebServer();
~WebServer();
int ServerInit(u_short port);
int ServerError(string str);
int ServerAccept();
int ServerClose();
int ServerRequest(int cli_fd);
int get_line(int cli_fd,char * buf,int size);//来自tinyhttpd

int Page_200(int cli_fd);
int Page_501(int cli_fd);
private:
int httpd;
};

int WebServer::ServerRequest(int cli_fd)
{
char buf[1024];
int size=1024;
int i=1;
memset(buf,0,sizeof(buf));
while((i>0)&&strcmp("\n",buf))
{
i=get_line(cli_fd,buf,sizeof(buf));
cout<<buf;
}
if(fork()==0)
{
//处理阶段
execl("/bin/ls","ls","/home/myuser/",NULL);
}
Page_200(cli_fd);
close(cli_fd);
return 0;
}
int WebServer::ServerAccept()
{
struct sockaddr_in cli_sin;
socklen_t cli_len=sizeof(cli_sin);
int cli_fd;
cli_fd=accept(httpd,(struct sockaddr *)&cli_sin,&cli_len);//阻塞等待连接
if(cli_fd==-1)
ServerError("Fail to accept");
cout<<"连接进来的IP: "<<inet_ntoa(cli_sin.sin_addr)<<":"<<ntohs(cli_sin.sin_port)<<endl;
return cli_fd;
}
int WebServer::ServerInit(u_short port)
{
struct sockaddr_in sin;
int on;
httpd=socket(PF_INET,SOCK_STREAM,0);
if(httpd==-1)
ServerError("Fail to Socket");
//init sockaddr_in
sin.sin_family=AF_INET;
sin.sin_port=htons(port);
sin.sin_addr.s_addr=htonl(INADDR_ANY);
bzero(&(sin.sin_zero),8);
setsockopt(httpd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
if(::bind(httpd,(struct sockaddr *)&sin,sizeof(struct sockaddr))==-1)
ServerError("Fail to bind");
//如果port指定为零那么就随机打开一个端口
if(port==0)
{
socklen_t len=sizeof(sin);
if(getsockname(httpd,(struct sockaddr *)&sin,&len)==-1)
ServerError("Fail to getsockname");
port=ntohs(sin.sin_port);
}
if(listen(httpd,100)<0)
ServerError("Fail to listen");
return port;
}
///////////////
int WebServer::get_line(int cli_fd,char * buf,int size)
{
int i=0;
char c='\0';
int n;
while((i<size-1)&&(c!='\n'))
{
n=recv(cli_fd,&c,1,0);
if(n>0)
{
if(c=='\r')
{
n=recv(cli_fd,&c,1,MSG_PEEK);
if((n>0)&&(c=='\n'))
recv(cli_fd,&c,1,0);
else
c='\n';
}
buf[i]=c;
i++;
}
else
c='\n';
}
buf[i]='\0';
return i;
}
int WebServer::ServerError(string str)
{
perror(str.c_str());
exit(-1);
}
int WebServer::ServerClose()
{
close(httpd);
return 0;
}
int WebServer::Page_200(int cli_fd)
{
char buf[1024];
sprintf(buf, "HTTP/1.1 200 OK\r\n");
send(cli_fd, buf, strlen(buf), 0);
sprintf(buf, "Server:wunaozai.cnblogs.com\r\n");
send(cli_fd, buf, strlen(buf), 0);
sprintf(buf, "Content-Type: text/html\r\n");
send(cli_fd, buf, strlen(buf), 0);
sprintf(buf, "\r\n");
send(cli_fd, buf, strlen(buf), 0);
sprintf(buf, "<HTML><HEAD><TITLE>Hello World\r\n");
send(cli_fd, buf, strlen(buf), 0);
sprintf(buf, "</TITLE></HEAD>\r\n");
send(cli_fd, buf, strlen(buf), 0);
sprintf(buf, "<BODY><h1>Hello World</h1>\r\n");
send(cli_fd, buf, strlen(buf), 0);
sprintf(buf, "</BODY></HTML>\r\n");
send(cli_fd, buf, strlen(buf), 0);
}
int WebServer::Page_501(int cli_fd)
{
char buf[1024];
sprintf(buf, "HTTP/1.1 501 Method Not Implemented\r\n");
send(cli_fd, buf, strlen(buf), 0);
sprintf(buf, "Server:wunaozai.cnblogs.com");
send(cli_fd, buf, strlen(buf), 0);
sprintf(buf, "Content-Type: text/html\r\n");
send(cli_fd, buf, strlen(buf), 0);
sprintf(buf, "\r\n");
send(cli_fd, buf, strlen(buf), 0);
sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");
send(cli_fd, buf, strlen(buf), 0);
sprintf(buf, "</TITLE></HEAD>\r\n");
send(cli_fd, buf, strlen(buf), 0);
sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");
send(cli_fd, buf, strlen(buf), 0);
sprintf(buf, "</BODY></HTML>\r\n");
send(cli_fd, buf, strlen(buf), 0);
}
WebServer::~WebServer()
{
}
WebServer::WebServer()
{
}


  webserver.cpp

#include "webserver.h"

int main(int argc,char **argv)
{
WebServer ws;//实例化web服务器
ws.ServerInit(8080);//打开8080端口
pid_t pid;
int cli_fd;
while(1)
{
cli_fd=ws.ServerAccept();//程序会在这个函数阻塞
ws.ServerRequest(cli_fd);//这个函数会创建一个进程对请求头进行处理并发送应答信息给客户端
}
ws.ServerClose();//关闭服务器

return 0;
}


  makefile

main:
g++ webserver.cpp -std=c++0x -g -o webserver
run:
./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服务器

int WebServer::ServerRequest(int cli_fd)
{
char buf[1024];
int size=1024;
int i,j;
char method[255];//用于保存请求方式
char url[512];
memset(buf,0,sizeof(buf));
//获取第一行请求信息 一般格式为: GET / HTTP/1.1
//                               POST / HTTP/1.1
size=get_line(cli_fd,buf,sizeof(buf));
cout<<"\t\t"<<buf<<endl;
i=0,j=0;
//截取第一个单词
while(!isspace(buf[j]) && (i<sizeof(method)-1))
{
method[i]=buf[j];
i++;j++;
}
method[i]='\0';
//取第一个与第二个单词之间的空格
while(isspace(buf[j]) && (j<sizeof(buf)))
j++;
//截取第二个单词
i=0;
while(!isspace(buf[j]) && (i<sizeof(url)-1) && (j<sizeof(buf)))
{
url[i]=buf[j];
i++;j++;
}
url[i]='\0';

if(strcasecmp(method,"GET") && strcasecmp(method,"POST"))
{
Page_501(cli_fd);
return -1;
}

if(strcasecmp(method,"GET")==0)
{
cout<<"此次请求的方式是GET方法"<<endl;
}
else if(strcasecmp(method,"POST")==0)
{
cout<<"此次请求的方式是POST方法"<<endl;
}
cout<<"此次请求的地址为:"<<url<<endl;

while((size>0)&&strcmp("\n",buf))
{
size=get_line(cli_fd,buf,sizeof(buf));
}

if(fork()==0)
{
//处理阶段
//execl("/bin/ls","ls","/home/myuser/",NULL);
Page_200(cli_fd);
}
close(cli_fd);
return 0;
}


  运行的结果



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



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


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

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

  本文地址: /article/6193142.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: