TinyWS —— 一个C++写的简易WEB服务器(一)

写在前面

每个码农可能都会偶尔有自己做一个常用软件的想法,比如操作系统,编译器,邮件服务器/客户端,文字编辑器等等。这里面有些很难,比如操作系统,做一个最简单的也要付出很大的努力,可是大部分常用工具都是可以比较容易的做一个简易版本(当然也是只能玩玩而已)。于是我做了一个非常简陋的WEB服务器 —— TinyWS。这里主要是记录下自己整个过程中的一些想法。

TinyWS是用C++”从头开始“做的,也就是说,除了C/C++的标准库和操作系统的系统调用,并没有使用第三方库。我并不喜欢C++(甚至有些厌恶其纷繁复杂的语法规则),正因如从此,虽然其是我的工作语言,但我也学的很粗糙。这次使用它主要也是为了自己能学习一下吧,毕竟拿了公司的钱,hee。

如果使用Python等其他”高级“的语言,会更快的实现,事实上几乎所有的WEB框架都会自带一个(当然都比TinyWS强大的多)。但如果使用这些语言,恐怕也很难真正的”从头开始“。

目前,代码已经托管在 https://git.oschina.net/augustus/TinyWS.git

可以用git clone下来。由于我可能会偶尔做一些修改,不能保证git 库上的代码与blog里的完全一致(实际上也不可能把所有的代码都贴在这里)。另外,TinyWS是基于linux写的(ubuntu 14.10 + eclipse luna,eclipse工程我也push到了git库),故在Windows上可能无法正常编译(主要是系统调用 部分可能会不同)。

原理

WEB的原理很简单,大家都懂,我就简单写几句,否则直接贴代码可能比较突兀。

WEB实际上也是一个客户端/服务器的程序,而它们之间基本使用HTTP/HTTPS/FTP等协议通信。协议不过是数据传输的一种方式,而对于传输的内容来说,WEB基本是html文档,当然也可以传其他的任何文件,不过作为一个玩具,TinyWS只支持HTTP协议。

WEB的客户端就是浏览器,实质是一个html的解释器,而我们要做的,就是提供一个服务器,让浏览器可以访问到HTML文档。浏览器是通过uri来访问服务器端的资源,比如一个保存在服务器上的index.html文档,在浏览器端,可以使用http://serverip:port/index.html 这样的方式就可以取回这个文档并解析。我们要解决的问题其实就是浏览器发出这个请求之后,给予正确的回应。

我们知道主机之间的网络通信实际上最终都是通过socket传数据。而socket的本质是操作系统内核实现一个映射,使得用户程序使用网络就像使用本地文件一样。即使用socket打开一个端口后,会返回一个文件描述符,之后所有的操作都和读写一个本地文件完全相同了。了解了这个,实际上我们就已经解决了一半的问题。

另一半的问题就是我们如何实现HTTP协议。好在HTTP是一个比较简单的协议,其核心是一个”请求与应答“的过程,”请求“是一些称为”方法“的操作过程,实际上就是告诉服务器,要请求服务器返回某资源(uri)或者对资源进行某些操作。常用的方法就是GET和POST,目前TinyWS只实现了GET方法,其他的方法可能后面也会做一下吧。

对于socket和HTTP,有许多专题可以查,这里就不罗嗦了。

RequestManager

TinyWS核心的业务实际就是接收HTTP请求,并给予正确的应答,所以这里先从上层业务讲起吧。TinyWS运行之后,首先会打开socket并监听某端口,之后就会运行RequestManager的run方法,不断的等待HTTP请求到来。请求到来之后,会解析内容,分析出客户端的请求方法和uri,从而交给相关的”方法“去处理。

// RequestManager.h
class RequestManager
{
public:
    RequestManager(int connfd);
    void run();

private:
    Request* getRequestHandle();

private:
    int fileDescriptor;

    Request* request;
};

其中Request 就是具体方法的基类,其子类可以是GET,POST等等。

// RequestManager.cpp
namespace
{
class Parser
{
public:
    Parser(int connfd)
    {
        parseRequestHeaders(connfd);
    }

    const std::string getMethodName()
    {
        return method;
    }

    const std::string getUri()
    {
        return uri;
    }

private:
    void parseRequestHeaders(int fd)
    {
        IoReader reader(fd);
        std::vector<std::string> header;
        reader.getLineSplitedByBlank(header);

        method = header[0];
        uri = header[1];
        version = header[2];
    }

private:
    std::string method;
    std::string uri;
    std::string version;
};
}

RequestManager::RequestManager(int connfd) : fileDescriptor(connfd), request(0)
{
}

void RequestManager::run()
{
    if(getRequestHandle())
        request->execute();
}

Request* RequestManager::getRequestHandle()
{
    Parser parser(fileDescriptor);
    return request = RequestCreater::getRequestHandler(parser.getMethodName(), fileDescriptor, parser.getUri());
}

在CPP文件中,首先要解析客户端的请求数据,分析出method,uri,version(协议版本,这里实际上并没有用到)。这个工作有Parser类完成,由于只有这一处使用,封在了匿名namespace中。解析中使用了IoReader类,它负责从socket读入数据,封装了底层的IO操作,这个后面再说。

回到正题。RequestManager的实现中,其实使用了一个工厂类( RequestCreater),根据解析出的method,创造不同的方法实例,这里虽然只支持GET,但仍然使用了工厂,是考虑到后面还会实现POST等其他方法,应该也不算过度设计吧,hee。

// Request.hclass Request
{
public:
    void init(int fd, std::string uri);
    void execute();
    virtual ~Request()
    {

    }

protected:
    int getFileDescriptor() const;
    const std::string& getUri() const;

private:
    virtual void doExecute() = 0;

private:
    int fileDescriptor;
    std::string uri;
};

Request是一个抽象类,每一个子类都需要实现doExecute方法才能实例化。这里也使用了一个简单的”模板方法“,让整个继承体系对外接口统一。

// Request.cpp
void Request::init(int fd, std::string uri)
{
     this->fileDescriptor = fd;
     this->uri = uri;
}

void Request::execute()
{
    doExecute();
}

int Request::getFileDescriptor() const
{
    return fileDescriptor;
}

const std::string& Request::getUri() const
{
    return uri;
}

真正干活的是Request的子类GetRequest。不过不早了,今天先到这里,下次再说吧。

时间: 01-10

TinyWS —— 一个C++写的简易WEB服务器(一)的相关文章

TinyWS —— 一个C++写的简易WEB服务器(三)

写在前面 代码已经托管在 https://git.oschina.net/augustus/TinyWS.git 可以用git clone下来.由于我可能会偶尔做一些修改,不能保证git 库上的代码与blog里的完全一致(实际上也不可能把所有的代码都贴在这里).另外,TinyWS是基于linux写的(ubuntu 14.10 + eclipse luna,eclipse工程我也push到了git库),故在Windows上可能无法正常编译(主要是系统调用 部分可能会不同). 前面的内容可参考上一篇

TinyWS —— 一个C++写的简易WEB服务器(二)

写在前面 代码已经托管在 https://git.oschina.net/augustus/TinyWS.git 可以用git clone下来.由于我可能会偶尔做一些修改,不能保证git 库上的代码与blog里的完全一致(实际上也不可能把所有的代码都贴在这里).另外,TinyWS是基于linux写的(ubuntu 14.10 + eclipse luna,eclipse工程我也push到了git库),故在Windows上可能无法正常编译(主要是系统调用 部分可能会不同). 前面的内容可参考上一篇

Socket 初识 用Socket建立一个简易Web服务器

摘自<Asp.Net 本质论>作者:郝冠军 //在.Net中.system.Net命名空间提供了网络编程的大多数数据据类型以及常用操作,其中常用的类型如下:/*IPAddress 类表示一个IP地址* IPEndPoint类用来表示一个IP地址和一个端口号的组合,成为网络的端点.* System.Net.Sockets命名空间中提供了基于Socked编程的数据类型.* Socket类封装了Socked的操作.* 常见的操作:* Listen:设置基于连接通信的Socket进入监听状态,并设置等

写一个简易web服务器、ASP.NET核心知识(4)

前言 昨天尝试了,基于对http协议的探究,我们用控制台写了一个简单的浏览器.尽管浏览器很low,但是对于http协议有个更好的理解. 说了上面这一段,诸位猜到我要干嘛了吗?(其实不用猜哈,标题里都有,又都不瞎...我就是调侃一下,说些没营养的笑话.我认为这样能不那么枯燥,尽管不好笑吧,但这不重要!) 没错,今天要尝试的东西,是自己写一个web服务器.初衷依旧和昨天一样,旨在理解一些东西,而不是真的写出一个多牛的东西. 第一次尝试(V1.0) 1.理论支持 其实关于http协议的理论方面我在<写

[js高手之路]node js系列课程-创建简易web服务器与文件读写

web服务器至少有以下几个特点: 1.24小时不停止的工作,也就是说这个进程要常驻在内存中 2.24小时在某一端口监听,如: http://localhost:8080, www服务器默认端口80 3.要能够处理基本的请求:如get, post 在node js中创建一台服务器非常的简单,因为node自带http模块,该模块可以帮助我们非常快速搭建一台web服务器,来处理一个简单的请求. 1 const http = require("http"); 2 var server = ht

简易Web服务器实现

手痒就自己实现了一下简易的web服务器,由于只是简易的web服务器,所以并没有什么特别高深的技术含量. 1.      TCP通信(socket) 2.      IO流 3.      线程池技术 服务器架构也简单: Request类主要是实现解析URL的功能,以获取html文件的路径. Response类实现读取html文件并且向浏览器输出html文件内容. Server类整合Request类和Response类,实现线程的run方法. Test类实现线程池,同时也是服务启动类. 下图是浏览

一个基于webrick 的简单web服务器

使用ruby 自带的webrick 可以非常方便地实现一个web服务器. webrick.rb 基本代码如下: #!/usr/bin/env ruby require 'webrick' root = File.expand_path 'html' server = WEBrick::HTTPServer.new :Port => 8000, :DocumentRoot => root trap 'INT' do server.shutdown end server.start 使用命令rub

python简易web服务器学习笔记(三)

import sys, os, BaseHTTPServer #------------------------------------------------------------------------------- class ServerException(Exception): '''For internal error reporting.''' pass #--------------------------------------------------------------

一个简单的Stocket编程Web服务器

在.Net中,System.Net命名空间提供了网络编程的大多数据类型以及常用操作,其中常用的类型如下: IPAddress类表示一个IP地址. IPEndPoint类表示一个IP地址和一个端口好的组合. System.Net.Socket命名空间中提供了基于Stocket编程的数据类型. Socket类封装了Socket的操作. 常用操作如下: Listen:设置连接队列的长度. Accept:等待一个新的连接,当通讯到达时候,返回一个针对行连接的Stocket对象. Receive:通过St