Linux下Web服务器开发

学习提示:

1. 在“桌面环境”中动手练习,若环境不流畅可选择WebIDE或字符界面。

2. 在教程下方“课程问答”中提出问题,或“共享桌面”寻求远程帮助。

3. 在教程下方“实验报告”中完成作业,记录心得。公开报告可以获得大家点评。

4. 在“我的代码库”中用GIT提交你的实验代码。

Web服务器

The way to learn a programming language is to write programs.

这门项目训练营最大的价值是实验楼和教师共同提供的教学服务,目的只有一个:让你把项目做出来并完全理解。所以请对自己负责,提出你遇到的任何问题,完成项目,你所花费的时间才有价值!

一、实验说明

欢迎参加C++语言经典项目实战训练营,在四周的时间里我们将一起完成四个小型及中型C++语言项目,涉及到C++语言技术的多方面。为了你能够有所收获,学习过程中务必注意:

进度很重要:必须跟上每周的进度,项目,问答。我们会认真对待每一位参与训练营的同学,请你不要因为困难半途而废。

问答很重要:遇到知识难点请多多提问,这是你的权利更是对自己负责的义务。

实践很重要:完成每周项目,解决项目中出现的一切问题。项目都提供完整的代码,但仅供学习中参考,应按照实验文档的思路自己实现,请勿直接拷贝代码。

实验报告很重要:详细记录你完成项目任务和解决问题的思路。

反馈很重要:请务必将你对课程的建议告诉我们,不同于视频,我们的文档可以很快更新升级以满足你的学习需求。

代码练习说明

实验楼环境使用GCC/G++ 4.8.4 编译环境。C++代码采用C++11标准。编写可以使用vim或gedit,并使用Git进行代码管理,实验报告需要以Markdown格式编写并公开。

这些技术都是很多程序员工作中天天用到的,如果你对其中任何知识不熟悉,可以先学习课程:

Linux基础入门

Vim编辑器

Git与实验楼代码库

Markdown与实验报告

如果需要保存代码到我的代码库,则需要用到git,目前实验楼的代码库可以在实验环境或者你自己的电脑上提交,请务必注意git push时输入的用户名是你登陆实验楼的邮箱(不是你代码库用户名),如果你是第三方登陆的,则需要先更新邮箱及登录密码后才可以在实验楼外部使用。

程序编写与编译最基本的方法(#标志后为注释):

# 打开桌面上的Xfce终端并执行后续命令# 进入到/home/shiyanlou目录cd /home/shiyanlou# 创建并打开源文件hello.cpp

gedit hello.cpp# 在gedit(类似windows上的记事本)中输入代码# 保存退出# 编译hello.cpp

g++ -std=c++11 hello.cpp -o hello# 执行生成的二进制文件

./hello

二、项目简介

1. 介绍

本实验使用 C++ 实现一个Web服务器。

这个项目会学习C++网络开发,kqueue IO复用机制,熟悉Linux下的C++程序编译方法,Makefile编写。

本节实验项目比较复杂,提供的示例代码接近3000行,但代码逻辑简单,难点在kqueue IO复用机制的理解,尽量独立实现,遇到难点可以参考示例代码或到实验楼问答中提问。

2. 项目需求

编写一个Web服务器,程序具备的基本功能:

支持多个客户端连接

支持HTTP协议中的GET,HEAD,OPTION三个方法

支持通过浏览器访问多种类型的文件资源

支持多虚拟主机,每个虚拟主机可以配置独立的资源池

3. 知识点

本实验实践的知识点包括:

C++ 面向对象程序设计

基本的Makefile

C++ 网络编程

kqueue IO复用机制

HTTP 协议基础

4. 项目效果图

程序运行的截图如下所示:

5. 项目完整代码

项目完整代码可以通过wget下载获得:

# 下载程序代码wget http://labfile.oss.aliyuncs.com/courses/454/webserver.zip

# 解压代码

unzip webserver.zip

# 进入代码文件夹查看

cd webserver

三、程序设计与实现

3.1 需求分析

大家平时上网时,浏览器中显示的HTML网页都是来源自Web服务器。本项目通过一个实例介绍如何实现一个简单的Web服务器。

本项目中实现的Web服务器需要支持下面几个功能:

支持多个客户端连接

支持HTTP协议中的GET,HEAD,OPTION三个方法

支持通过浏览器访问多种类型的文件资源

支持多虚拟主机,每个虚拟主机可以配置独立的资源池

实现具备这些基本功能的Web服务器,我们首先需要了解HTTP协议以及IO复用机制,在上一节的项目中我们已经学习了一种IO复用方法epoll,本项目采用kqueue机制,与epoll非常类似。

3.2 HTTP协议

HTTP协议介绍的好文章非常多,这里只列出项目中需要的知识点,推荐大家系统学习HTTP协议,请阅读:

HTTP协议详解

HTTP协议的一个主要特点就是客户端服务器模式,在上一个项目中我们有过介绍,基于socket的连接过程,如果大家没有印象可以去复习下。过程如下:

模型如下:

解释如下:

服务器端:

socket()创建监听Socket

bind()绑定服务器端口

listen()监听客户端连接

accept()接受连接

recv/send接收及发送数据

close()关闭socket

客户端:

socket()创建监听Socket

connect()连接服务器

recv/send接收及发送数据

close()关闭socket

其实实现的Web服务器是一个支持HTTP协议的TCP服务器。所以服务器启动过程仍然与上述过程相同,listen后等待客户端的连接。连接后开始进行HTTP协议的通信,通过请求与响应来建立Web访问。

HTTP的请求

HTTP请求由客户端发给服务器端,分三部分组成,分别是:请求行、消息报头、请求正文。

请求方法(所有方法全为大写)有多种,各个方法的解释如下:

GET 请求获取Request-URI所标识的资源

POST 在Request-URI所标识的资源后附加新的数据

HEAD 请求获取由Request-URI所标识的资源的响应消息报头

PUT 请求服务器存储一个资源,并用Request-URI作为其标识

DELETE 请求服务器删除Request-URI所标识的资源

TRACE 请求服务器回送收到的请求信息,主要用于测试或诊断

CONNECT 保留将来使用

OPTIONS 请求查询服务器的性能,或者查询与资源相关的选项和需求

项目里暂时实现了GET/HEAD/OPTION的处理,有兴趣的可以根据这三个处理函数的示例实现其他的。

HTTP的响应

在接收和解释请求消息后,服务器返回一个HTTP响应消息。HTTP响应也是由三个部分组成,分别是:状态行、消息报头、响应正文。

状态行格式:HTTP-Version Status-Code Reason-Phrase CRLF,其中,HTTP-Version表示服务器HTTP协议的版本;Status-Code表示服务器发回的响应状态代码;Reason-Phrase表示状态代码的文本描述。

状态代码有三位数字组成,第一个数字定义了响应的类别,且有五种可能取值:

1xx:指示信息--表示请求已接收,继续处理

2xx:成功--表示请求已被成功接收、理解、接受

3xx:重定向--要完成请求必须进行更进一步的操作

4xx:客户端错误--请求有语法错误或请求无法实现

5xx:服务器端错误--服务器未能实现合法的请求

当客户端与Web服务器建立连接后就会通过发送请求接收服务器响应来进行通信。而服务器端如果要处理多个客户端的请求,可以通过epoll或kqueue等IO机制来获得读写的消息通知,本项目采用kqueue实现。

3.4 kqueue 机制

kqueue与epoll非常相似,最初是2000年Jonathan
Lemon在FreeBSD系统上开发的一个高性能的事件通知接口。注册一批socket描述符到
kqueue 以后,当其中的描述符状态发生变化时,kqueue
将一次性通知应用程序哪些描述符可读、可写或出错了。

如果你已经理解了聊天室项目中介绍的epoll,那kqueue就很好理解,kqueue
提供 kqueue()、kevent()
两个系统调用和 struct kevent
结构。

kqueue机制详细介绍,推荐大家阅读下面的理论文章,虽然讲的是FreeBSD上的开发但在Linux上完全适用:

使用
kqueue 在
FreeBSD 上开发高性能应用服务器

kqueue的接口包括 kqueue()、kevent()
两个系统调用和 struct kevent
结构:

kqueue() 生成一个内核事件队列,返回该队列的文件描述索。其它 API
通过该描述符操作这个 kqueue。

kevent() 提供向内核注册 /
反注册事件和返回就绪事件或错误事件。

struct kevent 就是kevent()操作的最基本的事件结构。

struct kevent {

uintptr_t ident;       /* 事件 ID */

short     filter;       /* 事件过滤器 */

u_short   flags;        /* 行为标识 */

u_int     fflags;       /* 过滤器标识值 */

intptr_t  data;         /* 过滤器数据 */

void      *udata;       /* 应用透传数据 */

};

项目中我们会用到的内容包括事件过滤器,可以将 kqueue filter
看作事件。内核检测 ident
上注册的 filter
的状态,状态发生了变化,就通知应用程序。kqueue
定义了较多的 filter,本文只介绍
Socket 读写相关的
filter:

EVFILT_READ:TCP
监听 socket,如果在完成的连接队列
( 已收三次握手最后一个
ACK) 中有数据,此事件将被通知。收到该通知的应用一般调用
accept(),且可通过
data 获得完成队列的节点个数。 流或数据报
socket,当协议栈的
socket 层接收缓冲区有数据时,该事件会被通知,并且
data 被设置成可读数据的字节数。

EVFILT_WRIT:当 socket
层的写入缓冲区可写入时,该事件将被通知;data
指示目前缓冲区有多少字节空闲空间。

行为标志flags:

EV_ADD:指示加入事件到 kqueue

EV_DELETE:指示将传入的事件从 kqueue
中移除

过滤器标识值:

EV_ENABLE:过滤器事件可用,注册一个事件时,默认是可用的。

EV_DISABLE:过滤器事件不可用,当内部描述可读或可写时,将不通知应用程序。

这些参数都会在Web服务器实现中使用。当socket有连接或读写数据事件时,服务器读取客户端请求并将响应写回到客户端。kqueue充当的是事件触发的中间层。

为了我们可以使用kqueue,首先要在系统中安装libkqueue:

sudo apt-get update

sudo apt-get install libkqueue-dev

编译链接时也要在g++后添加-lkqueue才可以正常链接。

3.5 程序设计

根据上面的需求分析,设计所需的类。

首先需要一个HTTPServer类,用来提供服务器的启动关闭主循环等支持。HTTPServer对象在程序中唯一。

其次需要Client客户端类,这个类用来存储客户端的必要信息。HTTPServer对象中需要包含多个当前连接的Client对象。

再次,对于Client和HTTPServer之间的通信内容需要设置HTTPRequest和HTTPResponse类,而这两个类具备很多通用的信息,设计二者的基类为HTTPMessage类。

最后HTTPServer需要能够返回服务器资源给客户端,因此需要资源类Resource(文件,图片等文件)以及包含资源的资源池ResourceHost。这里可以为不同的虚拟主机设置不同的ResourceHost。

上面的几个类是Web服务器构成的主体,其中HTTPRequest,HTTPResponse,HTTPMessage三个类都是用来存储消息,
而客户端与服务器之间的消息都是以队列的方式进行读取和写入的,因此在HTTPMessage的上一层,我们添加一个新的基类ByteBuffer来实现 基础字节流的存储和读写处理。

综上项目中实现下面的类:

ByteBuffer:定义一个缓存队列用来存储数据

HTTPMessage:继承ByteBuffer,增加HTTP协议特有的信息。

HTTPRequest:HTTP请求类,继承HTTPMessage

HTTPResponse:HTTP响应类,继承HTTPMessage

Resource:资源类,HTTP响应消息中的文件资源数据

ResourceHost:资源池,用来将服务器上的文件加载到Resource对象

Client:客户端类,用来存储客户端的必要信息

HTTPServer:服务器类,提供服务器的启动关闭主循环等支持

3.6 代码结构

根据上述细化需求,我们先创建必要的程序文件。

首先为要实现的程序命名为webserver:

# 进入到代码库自动生成的目录# 为了方便git提交代码请先开通代码库

cd /home/shiyanlou/Code/shiyanlou_cs454

# 创建代码目录mkdir webserver

cd webserver

# 为每个需要实现的类创建一个.h和一个.cpp文件# 并将源文件都放在src/目录下mkdir src# touch src/XXX.h src/XXX.cpp# 创建Makefile

touch Makefile

# 创建示例目录,该目录可以作为Web服务器的资源池mkdir bin/mkdir bin/htdoc

touch bin/htdoc/test.html

本项目参考代码基于https://github.com/RamseyK/httpserver进行实现。每个头文件中的函数都进行了标注,核心模块HTTPServer.cpp也会进行详细介绍。其他cpp文件由于内容相对简单,直接使用原作者的英文注释,如果有任何不清楚的问题可以随时在实验楼问答提问。

下面我们将开始实现需要的类,建议大家按照以下步骤先自己实现,最后再对项目参考代码进行对照学习。

3.7 ByteBuffer

ByteBuffer 定义一个缓存队列用来存储HTTP访问中的请求及返回数据,这个缓存队列的作用相当于CPU和硬盘之间的内存,用来存放Web服务器和客户端之间通信的数据,需要具备下面的功能:

从队列头部读取数据

向队列尾部写入新的数据

当队列存储区域满的时候可以自动扩充

支持随机读取和写入多种数据类型到指定位置(使用模板实现)

支持清空,克隆,对比,扩容

支持遍历队列查找指定的数据

ByteBuffer的核心接口如下:

class ByteBuffer {

// 读写位置索引

unsigned int rpos, wpos;

// 缓存内容使用容器存储

std::vector<byte> buf;

// 队列中剩余的元素数量

unsigned int bytesRemaining();

// 清空队列并重置读写索引

void clear();

// 拷贝当前ByteBuffer对象

ByteBuffer* clone();

// 对比两个ByteBuffer对象

bool equals(ByteBuffer* other);

// 设置存储空间大小

void resize(unsigned int newSize);

// 返回存储空间大小

unsigned int size();

// 读取队列头部的数据但不移动rpos

byte peek();

// 读取队列头部的数据同时移动rpos

byte get();

// 读取指定位置的数据

byte get(unsigned int index);

// 读取队列头部指定长度的数据到buf中

void getBytes(byte* buf, unsigned int len);

// 读取指定数据类型的元素:char,double,float,int,long,short

char getChar();

char getChar(unsigned int index);

// 将src写入队列

void put(ByteBuffer* src);

// 将b写入队列

void put(byte b);

// 将b写入队列指定位置

void put(byte b, unsigned int index);

// 将长度为len的缓存b写入队列头部

void putBytes(byte* b, unsigned int len);

// 将长度为len的缓存b写入队列指定位置

void putBytes(byte* b, unsigned int len, unsigned int index);

// 写入指定数据类型的元素:char,double,float,int,long,short

void putChar(char value);

void putChar(char value, unsigned int index);

}

看上去非常繁琐,但可以使用模板实现基础函数,大部分函数都能够通过调用函数模板的方式简化实现内容。

3.8 HTTPMessage

继承ByteBuffer实现HTTPMessage类。这个类需要包含:

HTTP头部

HTTP版本号

消息数据:包括ByteBuffer中的数据及额外的数据(响应消息中返回的资源及请求消息中额外的参数)

HTTP消息解析函数,根据HTTP协议的规范对消息头部和内容进行解析

HTTP消息创建函数,创建符合HTTP协议要求的HTTP消息

HTTP头部管理函数,添加HTTP头部信息

这个类比较简单,只需要把上述几个函数依次实现,核心函数包括:

class HTTPMessage : public ByteBuffer {private:

// 消息头部信息

std::map<std::string, std::string> *headers;

protected:

// HTTP版本号

std::string version;

// 消息数据

// 响应消息中表示资源,请求消息中表示额外的参数

byte* data;

public:

// 创建消息

virtual byte* create() = 0;

// 解析消息

virtual bool parse() = 0;

};

3.9 HTTPRequest

HTTP请求类继承HTTPMessage,需要实现以下数据项及函数功能:

请求的方法:GET/HEAD/OPTION等

请求的URI

实现HTTPMessage的创建和解析虚函数

核心成员声明:

// HTTPRequest:HTTP请求消息,从客户端发给服务器的消息class HTTPRequest : public HTTPMessage {private:

// 请求的方法类型

int method;

// 请求的URL

std::string requestUri;

protected:

// 初始化消息

virtual void init();

public:

virtual byte *create();

virtual bool parse();

// 辅助函数

// 方法字符串与数字相互转换

int methodStrToInt(std::string name);

std::string methodIntToStr(unsigned int mid);

};

3.10 HTTPResponse

HTTP响应类继承HTTPMessage,需要实现以下数据项及函数功能:

响应状态码和信息

实现HTTPMessage的创建和解析虚函数

核心成员声明:

// HTTPResponse:HTTP响应消息,从服务器发给客户端的消息class HTTPResponse : public HTTPMessage {private:

// 响应状态码和信息

int status;

std::string reason;

protected:

virtual void init();

public:

virtual byte* create();

virtual bool parse();

};

3.11 Resource

HTTP响应消息中的Resource除了包含文件数据以外,最重要的特点是具备mimeType标识,MIME
(Multipurpose Internet Mail Extensions) 是描述消息内容类型的因特网标准。MIME
消息能包含文本、图像、音频、视频以及其他应用程序专用的数据。

Resource类中需要的成员:

资源数据

数据大小

数据MIME类型

资源在服务器上存储的路径

核心成员声明如下:

// Resource:HTTP响应消息中的文件资源数据class Resource {

private:

// 文件资源

byte* data;

// 资源大小

unsigned int size;

// 资源类型

std::string mimeType;

// 资源在服务器上存储的路径

std::string location;

// 是否是目录类型

bool directory;

};

3.11 ResourceHost

ResourceHost是一个Resource的管理组件,可以通过一个目录来构建。需要支持的成员包括:

基础目录,其他的访问路径都是该目录下的相对路径

根据访问地址URI获取Resource资源对象

将资源写入到文件系统,用来支持上传操作

Resource中的MimeType的获取可以通过判断扩展名来实现,需要构建扩展名和MimeType的映射表。

我们需要定义一个数据结构包含所有的MIME信息,示例代码中采用宏的方式从文件MimeTypes.inc中读取:

// 构建扩展名与MimeType对应的字典,从文件MimeTypes.inc中获取

std::unordered_map<std::string, std::string> mimeMap = {

#define STR_PAIR(K,V) std::pair<std::string, std::string>(K,V)

#include "MimeTypes.inc"

};

MimeTypes.inc中的内容是若干行:

STR_PAIR("bmp", "image/bmp"),

STR_PAIR("book", "application/book"),

...

核心成员声明如下:

// ResourceHost:资源池,用来将服务器上的文件加载到Resource对象class ResourceHost {private:

// 基础目录,其他的访问路径都是该目录下的相对路径

std::string baseDiskPath;

// 构建扩展名与MimeType对应的字典,从文件MimeTypes.inc中获取

std::unordered_map<std::string, std::string> mimeMap = {

#define STR_PAIR(K,V) std::pair<std::string, std::string>(K,V)

#include "MimeTypes.inc"

};

public:

// 将资源写入到文件系统

void putResource(Resource* res, bool writeToDisk);

// 根据URI获取Resource资源对象

Resource* getResource(std::string uri);

};

3.12 Client

客户端的数据会存放在HTTPServer中的一个映射表中,用客户端连接的socket做key。每个客户端对象都应该包含客户端的socket,sockaddr_in地址信息,数据发送队列。

客户端的操作需要支持添加新的数据到发送队列,对发送队列进行出队,清空等操作。

发送队列使用std::queue,每个存储单元需要单独设计要包含发送的数据及数据偏移,并要增加是否需要在发送完成后断开连接的标志。发送存储单元命名为SendQueueItem类,在Client类声明中需要使用。

核心成员声明如下:

// HTTP客户端// class Client {

// 连接的socket

int socketDesc;

// 地址信息

sockaddr_in clientAddr;

// 数据发送队列

std::queue<SendQueueItem*> sendQueue;

public:

// 发送队列操作

// 添加新的数据到发送队列

void addToSendQueue(SendQueueItem* item);

// 发送队列长度

unsigned int sendQueueSize();

// 获取发送队列中第一个元素

SendQueueItem* nextInSendQueue();

// 出队操作,删除第一个元素

void dequeueFromSendQueue();

// 清空发送队列

void clearSendQueue();

};

3.13 HTTPServer

这是整个项目的核心,具有最复杂的类和实现过程。

首先分析HTTPServer需要具备的数据成员,刚才已经提到Client客户端列表是必须的,此外还需要包含:

监听的端口号

监听的socket

服务器地址信息

kqueue 描述符

kevent 事件队列

客户端字典,映射客户端的socket和客户端对象

资源主机及文件系统列表

虚拟主机,映射请求的地址到不同的ResourceHost

根据需求中HTTPServer需要支持下面几大类功能:

处理客户端连接

处理客户端请求

发送响应消息给客户端

服务器管理

每个功能集合中又可以细分出不同的功能。

处理客户端连接

接受连接:accept接受连接并将新的socket添加到kqueue中,创建客户端对象添加到客户端列表中

获取客户端对象:根据socket查找客户端列表返回客户端对象

断开连接:清理kqueue中的socket,关闭socket,清理客户端列表中的对象

处理客户端读取事件:recv()接收客户端数据并构造HTTPRequest对象,交给handleRequest()函数处理。

处理客户端写入事件:获取发送队列中的SendQueueItem,send()发送给客户端

处理客户端请求

处理请求:对请求方法进行分类分别交给不同的处理函数

处理GET/HEAD/OPTIONS请求:构造HTTPResponse,调用sendResponse发送给客户端

发送响应消息给客户端

发送响应状态码:构造HTTPResponse,调用sendResponse发送给客户端

发送响应消息:将HTTPResponse封装后添加到客户端的发送队列

服务器管理

启动:包含各种数据和资源的初始化,监听socket的建立,绑定,kqueue的创建和初始化。

停止:释放资源,清理客户端列表,清理kqueue,清理socket

主循环:进入事件循环,等待kqueue事件触发,有事件触发后需要先判断是新的客户端连接还是客户端读写事件

核心成员声明如下:

// HTTPServer:Web服务器类,提供服务器的创建,启动,停止等管理操作class HTTPServer {

// 监听的端口号

int listenPort;

// 监听的socket

int listenSocket;

// 服务器地址信息

struct sockaddr_in serverAddr;

// kqueue 描述符

int kqfd;

// kevent队列

struct kevent evList[QUEUE_SIZE];

// 客户端字典,映射客户端的socket和客户端对象

std::unordered_map<int, Client*> clientMap;

// 资源主机及文件系统列表

std::vector<ResourceHost*> hostList;

// 虚拟主机,映射请求的地址到不同的ResourceHost

std::unordered_map<std::string, ResourceHost*> vhosts;

// 处理客户端连接

void acceptConnection();

void disconnectClient(Client* cl, bool mapErase = true);

void readClient(Client* cl, int data_len);

bool writeClient(Client* cl, int avail_bytes);

// 处理客户端请求

void handleRequest(Client* cl, HTTPRequest* req);

void handleGet(Client* cl, HTTPRequest* req, ResourceHost* resHost);

void handleOptions(Client* cl, HTTPRequest* req);

// 发送响应消息给客户端

void sendStatusResponse(Client* cl, int status, std::string msg = "");

void sendResponse(Client* cl, HTTPResponse* resp, bool disconnect);

// 启动及停止Web服务器

bool start(int port);

void stop();

// Web服务器主循环

void process();

};

当HTTPServer启动时我们默认设置当前路径下的htdoc文件夹为ResourceHost,并添加到HTTPServer对象中。而htdoc下我们添加一个test.html文件,作为可以访问的Resource,test.html中的内容可以很简单,例如:

<html><head><title>shiyanlou demo site</title></head>

<body>

Hello Shiyanlou!<br /></body></html>

这个示例页面可以在后面的测试中通过访问localhost:8080/test.html访问到。

3.14 主函数

主函数中实现包括两部分内容:

注册各种信号处理函数,能够让Web服务中止时可正常释放资源

创建HTTPServer对象,依次启动,进入主循环

需要处理的包括中断信号SIGABRT,SIGINT,SIGTERM,这些信号发生时要将HTTPServer的停止标志置为True,下次循环时就可以退出。SIGPIPE信号需要被忽略,避免"Broken
pipe"出现。

示例代码片段:

// 忽视 SIGPIPE "Broken pipe" 信号

signal(SIGPIPE, handleSigPipe);

// 注册中断处理

signal(SIGABRT, &handleTermSig);

signal(SIGINT, &handleTermSig);

signal(SIGTERM, &handleTermSig);

// 创建并启动HTTPServer实例

svr = new HTTPServer();

svr->start(8080);

// 进入主循环

svr->process();

// 停止服务器

svr->stop();

delete svr;

3. 编译及运行

将你完成的文件保存为/home/shiyanlou/Code/shiyanlou_cs454/webserver,在同样的目录下我们编辑Makefile文件:

cd /home/shiyanlou/Code/shiyanlou_cs454/webserver

vim Makefile

Makefile文件里的内容:

CC := g++SRCDIR := srcBINDIR := binBUILDDIR := buildTARGET := httpserverUNAME := $(shell uname)

# Debug FlagsDEBUGFLAGS := -g3 -O0 -WallRTCHECKS := -fmudflap -fstack-check -gnato

# Production FlagsPRODFLAGS := -Wall -O2

CFLAGS := -std=c++11 -Iinclude/ $(DEBUGFLAGS)LINK := -lpthread -lkqueue $(DEBUGFLAGS)

SRCEXT := cppSOURCES := $(shell find $(SRCDIR) -type f -name *.$(SRCEXT))OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.o))

$(TARGET): $(OBJECTS)

@echo " Linking..."$(LINK); $(CC) $^ -o $(BINDIR)/$(TARGET) $(LINK)

$(BUILDDIR)/%.o: $(SRCDIR)/%.$(SRCEXT)

@mkdir -p $(BUILDDIR)

@echo " CC $<"; $(CC) $(CFLAGS) -c -o [email protected] $<

clean:

@echo " Cleaning..."; rm -r $(BUILDDIR) $(BINDIR)/$(TARGET)*

.PHONY: clean

Makefile内容很多,大部分都是变量定义,核心内容只有下面两行:

@echo " CC $<"; $(CC) $(CFLAGS) -c -o [email protected] $< 编译每个CPP文件,生成.o目标文件

$(CC) $^ -o $(BINDIR)/$(TARGET) $(LINK) 链接上一步骤生成的所有目标文件,得到可执行的httpserver文件

保存Makefile后,我们只需要在目录下执行make就可以生成可执行文件httpserver。

编译过程截图:

Makefile会把可执行文件放到了bin/目录下,因为bin/目录下的htdoc/已经写在代码中作为默认ResourceHost了,所以测试启动后的Web服务器可以访问htdoc下的文件。

现在进入运行测试阶段,首先启动服务端:

cd bin/

./httpserver

运行截图如下:

如果中间有任何问题,需要根据输出的错误信息查验下代码是否有BUG。欢迎随时到实验楼问答提问。

3.8 完整代码参考

我们提供本项目完整的代码及详细注释供参考,由于代码比较多,文档中不再粘贴,建议大家下载查看。

# 下载程序代码wget http://labfile.oss.aliyuncs.com/courses/454/webserver.zip

# 解压代码

unzip webserver.zip

# 进入代码文件夹查看

cd webserver

本项目参考代码基于https://github.com/RamseyK/httpserver修改。代码License仍然遵循Apache
License, Version 2.0。

四、项目扩展

本实验实现了一个Web服务器程序。基于本课节学习,大家可以在此代码基础上实现扩展:

支持更多的HTTP请求方法,例如POST

支持配置文件,比如配置多个虚拟主机,资源池,端口号等

支持PHP页面解析,可以加入单独的模块

五、小结

通过本节实验的学习,我们开发了Web服务器程序,学习了C++语言的基本语法及面向对象开发,网络程序开发,HTTP协议及kqueue
IO复用机制。

完成项目后可以公开你的实验报告,并点击实验报告下方的分享按钮分享到微博,将会获得教师点评,同时优秀的实验报告官微将转发推荐!

再次提醒,任何问题欢迎到实验楼问答中提问,老师会及时回答你的困惑。

特别说明:

实验作业与学习心得请写在下方“实验报告”(支持markdown格式)里并公开,每周选取优秀报告奖励IT书籍!

您已经完成本课程所有实验

时间: 2024-10-13 09:54:15

Linux下Web服务器开发的相关文章

嵌入式linux下web服务器搭建

一.移植编译生成boa二进制文件 Boa是一种非常小巧的Web服务器,其可执行代码只有大约60KB左右.作为一种单任务Web服务器,Boa只能依次完成用户的请求,而不会fork出新的进程来处理并发连接请求.但Boa支持CGI,能够为CGI程序fork出一个进程来执行.Boa的设计目标是速度和安全. 首先下载boa源码包,下载链接:http://www.boa.org/. 解压到特定目录,本人使用的是/opt目录,使用命令: # tar zxvf boa-0.94.13.tar.gz –C /op

Linux Apache web服务器 配置详细教程

3 Linux Apache web服务器 v2.4.29学习要点: 1.apache用途,工作模式,httpd.conf的配置重要参数2.虚拟主机 工作模式的参数优化 3.1 Apache 概述: 3.1.1 Apache 概述Apache是世界使用排名第一的Web服务器软件.它可以运行在几乎所有广泛使用的计算机平台上,由于其跨平台和安全性被广泛使用,是最流行的Web服务器端软件之一.它快速.可靠并且可通过简单的API扩充,将Perl/Python等解释器编译到服务器中.同时Apache音译为

linux搭建web服务器

linux httpd 假设服务器地址为192.168.80.20/24 1.   将准备安装的httpd软件包共享给everyone , (1)在linux上mount.cifs  //真机IP地址/共享文件夹名   /media / ls  /meidia/ 查看 tar    xjvf   httpd-2.4.10.tar.bz2    -C  /usr/src       解压至/usr/src下 下面两个插件是httpd2.4以后的版本所需要的 http://ftp.jaist.ac.

Linux下搭建Java开发环境

Red Hat Package Manager 简称rpm rpm格式的文件就是我们可以使用RPM命令进行管理的软件包格式的文件 JDK的安装 #sh /root/Desktop/jdk-6u23-linux-i586-rpm.bin .bin 可以使用sh命令来解压执行 Linux的用户的登录过程 /etc/profile /etc/profile.d(各个脚本) /etc/bash.bashrc /home/[username]具体用户目录/.bashrc .bash_profile JDK

linux下不同服务器间数据传输(rcp,scp,rsync,ftp,sftp,lftp,wget,curl)

因为工作原因,需要经常在不同的服务器见进行文件传输,特别是大文件的传输,因此对linux下不同服务器间数据传输命令和工具进行了研究和总结.主要是rcp,scp,rsync,ftp,sftp,lftp,wget,curl. rcp rcp不是一种安全的的传输文件的方式,rcp通过rsh(rsh见下面)来执行远程命令,要使用rcp必须经过一些配置,现在rcp已经被scp取代了,常用scp来进行文件传输.要使用rcp,需要具备以下条件: (1)如果系统中有/etc/hosts 文件,应确保该文件包含要

Linux教程:如何在Linux下进行C++开发?

Linux是一类Unix计算机操作系统的统称,Linux操作系统的内核的名字也是“Linux”, 在Linux下进行C++开发,需要注意许多问题,比如:减少不必要的编辑动作,减少编辑的时间. Windows下,开发工具多以集成开发环境IDE的形式展现给最终用户.例如,VS2005集成了编辑器,宏汇编ml,C /C++编译器cl,资源编译器rc,调试器,文档生成工具, nmake.它们以集成方式提供给最终用户,对于初学者而言十分方便. 但是,这种商业模式,直接导致用户可定制性差,不利于自动化,集成

Linux下配置PHP开发环境

转载于: http://www.uxtribe.com/php/405.html 该站下有系列PHP文章. 在Linux下搭建PHP环境比Windows下要复杂得多.除了安装Apache,PHP等软件外,还要安装一些相关工具,设置必要参数.而且,如果要使用PHP扩展库,还要进行编译.安装之前要准备如下安装包: http-2.2.8.tar.gz.下载地址:http://www.apache.org. php-5.2.5.tar.gz.下载地址:http://www.php.net/downloa

linux下不同服务器间数据传输(rcp,scp,rsync,ftp,sftp,lftp,wget,curl)(zz)

linux下不同服务器间数据传输(rcp,scp,rsync,ftp,sftp,lftp,wget,curl) 分类: linux2011-10-10 13:21 8773人阅读 评论(1) 收藏 举报 服务器linuxftp服务器文档commandssh 目录(?)[+] 因为工作原因,需要经常在不同的服务器见进行文件传输,特别是大文件的传输,因此对linux下不同服务器间数据传输命令和工具进行了研究和总结.主要是rcp,scp,rsync,ftp,sftp,lftp,wget,curl. r

linux下不同服务器间数据传输(wget,scp)

一.wget是Linux下最常用的http/ftp文件下载工具1.wget断点续传,只需要加上-c参数即可,例如:代码:wget-chttp://www.abc.com/abc.zip-Oabc.zip2.当服务器上的文件比本地指定目录下的文件更新时才下载,-P指定目录,-N表示检查文件文件是否更新代码:wget-N-P/hom 一.wget是Linux下最常用的http/ftp文件下载工具1.wget断点续传,只需要加上-c参数即可,例如:代码: wget -c http://www.abc.