上一小节通过阅读开源的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[];
int size=;
int i=;
memset(buf,,sizeof(buf));
while((i>)&&strcmp("\n",buf))
{
i=get_line(cli_fd,buf,sizeof(buf));
cout<<buf;
}
if(fork()==)
{
//处理阶段
execl("/bin/ls","ls","/home/myuser/",NULL);
}
Page_200(cli_fd);
close(cli_fd);
return ;
}
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==-)
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,);
if(httpd==-)
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),);
setsockopt(httpd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
if(::bind(httpd,(struct sockaddr *)&sin,sizeof(struct sockaddr))==-)
ServerError("Fail to bind");
//如果port指定为零那么就随机打开一个端口
if(port==)
{
socklen_t len=sizeof(sin);
if(getsockname(httpd,(struct sockaddr *)&sin,&len)==-)
ServerError("Fail to getsockname");
port=ntohs(sin.sin_port);
}
if(listen(httpd,)<)
ServerError("Fail to listen");
return port;
}
///////////////
int WebServer::get_line(int cli_fd,char * buf,int size)
{
int i=;
char c='\0';
int n;
while((i<size-)&&(c!='\n'))
{
n=recv(cli_fd,&c,,);
if(n>)
{
if(c=='\r')
{
n=recv(cli_fd,&c,,MSG_PEEK);
if((n>)&&(c=='\n'))
recv(cli_fd,&c,,);
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(-);
}
int WebServer::ServerClose()
{
close(httpd);
return ;
}
int WebServer::Page_200(int cli_fd)
{
char buf[];
sprintf(buf, "HTTP/1.1 200 OK\r\n");
send(cli_fd, buf, strlen(buf), );
sprintf(buf, "Server:wunaozai.cnblogs.com\r\n");
send(cli_fd, buf, strlen(buf), );
sprintf(buf, "Content-Type: text/html\r\n");
send(cli_fd, buf, strlen(buf), );
sprintf(buf, "\r\n");
send(cli_fd, buf, strlen(buf), );
sprintf(buf, "<HTML><HEAD><TITLE>Hello World\r\n");
send(cli_fd, buf, strlen(buf), );
sprintf(buf, "</TITLE></HEAD>\r\n");
send(cli_fd, buf, strlen(buf), );
sprintf(buf, "<BODY><h1>Hello World</h1>\r\n");
send(cli_fd, buf, strlen(buf), );
sprintf(buf, "</BODY></HTML>\r\n");
send(cli_fd, buf, strlen(buf), );
}
int WebServer::Page_501(int cli_fd)
{
char buf[];
sprintf(buf, "HTTP/1.1 501 Method Not Implemented\r\n");
send(cli_fd, buf, strlen(buf), );
sprintf(buf, "Server:wunaozai.cnblogs.com");
send(cli_fd, buf, strlen(buf), );
sprintf(buf, "Content-Type: text/html\r\n");
send(cli_fd, buf, strlen(buf), );
sprintf(buf, "\r\n");
send(cli_fd, buf, strlen(buf), );
sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");
send(cli_fd, buf, strlen(buf), );
sprintf(buf, "</TITLE></HEAD>\r\n");
send(cli_fd, buf, strlen(buf), );
sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");
send(cli_fd, buf, strlen(buf), );
sprintf(buf, "</BODY></HTML>\r\n");
send(cli_fd, buf, strlen(buf), );
}
WebServer::~WebServer()
{
}
WebServer::WebServer()
{
}

  webserver.cpp

 #include "webserver.h"

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

  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[];
int size=;
int i,j;
char method[];//用于保存请求方式
char url[];
memset(buf,,sizeof(buf));
//获取第一行请求信息 一般格式为: GET / HTTP/1.1
// POST / HTTP/1.1
size=get_line(cli_fd,buf,sizeof(buf));
cout<<"\t\t"<<buf<<endl;
i=,j=;
//截取第一个单词
while(!isspace(buf[j]) && (i<sizeof(method)-))
{
method[i]=buf[j];
i++;j++;
}
method[i]='\0';
//取第一个与第二个单词之间的空格
while(isspace(buf[j]) && (j<sizeof(buf)))
j++;
//截取第二个单词
i=;
while(!isspace(buf[j]) && (i<sizeof(url)-) && (j<sizeof(buf)))
{
url[i]=buf[j];
i++;j++;
}
url[i]='\0'; if(strcasecmp(method,"GET") && strcasecmp(method,"POST"))
{
Page_501(cli_fd);
return -;
} if(strcasecmp(method,"GET")==)
{
cout<<"此次请求的方式是GET方法"<<endl;
}
else if(strcasecmp(method,"POST")==)
{
cout<<"此次请求的方式是POST方法"<<endl;
}
cout<<"此次请求的地址为:"<<url<<endl; while((size>)&&strcmp("\n",buf))
{
size=get_line(cli_fd,buf,sizeof(buf));
} if(fork()==)
{
//处理阶段
//execl("/bin/ls","ls","/home/myuser/",NULL);
Page_200(cli_fd);
}
close(cli_fd);
return ;
}

  运行的结果

  可以看出只要在浏览器地址栏写上什么就可以在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

Socket网络编程--简单Web服务器(2)的更多相关文章

  1. Socket网络编程--简单Web服务器(6)

    本来是想实现ssl连接的,但是弄了好久都不成功,就索性不做了,等以后有能力再做了.所以这一小节就是本次的最后一节了.就简单的说几个注意点. 1.加个配置文件 使用单例模式,使用一个类,该类保存一些信息 ...

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

    这一次的Socket系列准备讲Web服务器.就是编写一个简单的Web服务器,具体怎么做呢?我也不是很清楚流程,所以我找来了一个开源的小的Web服务器--tinyhttpd.这个服务器才500多行的代码 ...

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

    上一小节已经实现了浏览器发送请求,然后服务器给出应答信息,然后浏览器显示出服务器发送过来的网页.一切看起来都是那么的美好.这一小节就准备实现可以根据地址栏url的不同来返回指定的网页.目前还不考虑带参 ...

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

    上一小节已经实现了对图片的传输,接下来就是判断文件是否为js,css,png等格式.我们增加一个函数用于判断格式 int WebServer::get_filetype(char *type,char ...

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

    这一小节我们将实现服务器对get和post的请求进行对cgi程序的调用.对于web服务器以前的章节已经实现了对get和post请求的调用接口,接下来给出对应接口的实现. int WebServer:: ...

  6. C++ socket 网络编程 简单聊天室

    操作系统里的进程通讯方式有6种:(有名/匿名)管道.信号.消息队列.信号量.内存(最快).套接字(最常用),这里我们来介绍用socket来实现进程通讯. 1.简单实现一个单向发送与接收 这是套接字的工 ...

  7. C#中使用Socket实现简单Web服务器

    上一篇博客中介绍了怎样使用socket访问web服务器.关键有两个: 熟悉Socket编程: 熟悉HTTP协议. 上一篇主要是通过socket来模拟浏览器向(任何)Web服务器发送(HTTP)请求,重 ...

  8. Java Web 基础(一) 基于TCP的Socket网络编程

    一.Socket简单介绍 Socket通信作为Java网络通讯的基础内容,集中了异常.I/O流模式等众多知识点.学习Socket通信,既能够了解真正的网络通讯原理,也能够增强对I/O流模式的理解. 1 ...

  9. python之Socket网络编程

    什么是网络? 网络是由节点和连线构成,表示诸多对象及其相互联系.在数学上,网络是一种图,一般认为专指加权图.网络除了数学定义外,还有具体的物理含义,即网络是从某种相同类型的实际问题中抽象出来的模型.在 ...

随机推荐

  1. 007 关于Spark下的第二种模式——standalone搭建

    一:介绍 1.介绍standalone Standalone模式是Spark自身管理资源的一个模式,类似Yarn Yarn的结构: ResourceManager: 负责集群资源的管理 NodeMan ...

  2. 053 关于hive的存储格式

    1.存储格式 textfile rcfile orc parquet 2.存储方式 按行存储 ->textfile 按列存储 ->parquet 3.压缩比 4.存储textfile的原文 ...

  3. Redis持久化(persistence)

    Redis 持久化 Redis 提供了多种不同级别的持久化方式: RDB 持久化可以在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot). AOF 持久化记录服务器 ...

  4. js算法初窥06(算法模式03-函数式编程)

    在解释什么是函数式编程之前,我们先要说下什么是命令式编程,它们都属于编程范式的一种.命令式编程其实就是一块一块的代码,其中包括了我们要执行的逻辑或者判断或者一些运算.也就是按部就班的一步一步完成我们所 ...

  5. struts1 标签引入

    1 tld文件导入 目录结构如下 2 jsp 文件头部标签引入 <%@ page pageEncoding="gbk" contentType="text/html ...

  6. 7,EasyNetQ-控制队列名称

    EasyNetQ在为队列生成名称时的默认行为是使用   消息类型名称+subscription Id 例如,名称空间EasyNetQ.Tests.Integration中的PartyInvitatio ...

  7. android studio 查看预览所有屏幕分辨率下的显示

    你可以打开在窗口的右侧预览面板设置上的布局.你可以通过修改面板顶部面板改变各种选项来修改预览,包括预览设备,主题,平台版本等等,可以同时预览多个设备上布局,可以从“设备”选项中下拉”预览所有屏幕的尺寸 ...

  8. Microsoft Corporation 去掉 windows 修改 启动加载 版权

    windows 修改 开机界面 boot启动界面 windows 修改 启动加载 版权 windows 系统如何修改开机画面的版权文字“Microsoft Corporation ... ◎Micro ...

  9. 洛谷.2042.[NOI2005]维护数列(Splay)

    题目链接 2017.12.24 第一次写: 时间: 2316ms (1268ms) 空间: 19.42MB (19.5MB)(O2) 注:洛谷测的时间浮动比较大 /* 插入一段数:将这些数先单独建一棵 ...

  10. Python3基础之字典

    Python数据类型之字典(Dictionary) 字典特征 特征 可变.无序.映射.键值对 形式 {key0:value0, key1:value1, key2:value3, ..., } key ...