TinywebServer知识点(2)

原项目地址(点击跳转)

博主添加注释后项目地址(点击跳转)


目前需要进一步补充学习的知识点:

  • 数据库
  • C++设计模式



一、代码架构

1.项目框架

img



2.编译运行

在ubuntu下,打开TinyWebServer-master目录,终端运行

1
$ sh ./build.ch

或者直接终端运行

1
make ./server

上面两个都可以编译程序代码

终端输入ifconfig查看服务器本机ip,服务器程序的默认端口号为9006

在浏览器中使用如下,即可访问服务器

1
ip:Port  如: 192.168.126.1289006


3.服务器运行时流程图

img




二、HTTP介绍

1.HTTP报文

(1)请求报文

HTTP请求报文由请求行(request line)、请求头部(header)、空行和请求数据四个部分组成有。两种请求报文

  • 请求行,用来说明请求类型(方法),要访问的资源以及所使用的HTTP版本
  • 请求头部,紧接着请求行(即第一行)之后的部分,用来说明服务器要使用的附加信息
  • 空行,请求头部后面的空行是必须的即使第四部分的请求数据为空,也必须有空行
  • 请求数据也叫主体,可以添加任意的其他数据

**GET**请求报文

1
2
3
4
5
6
7
8
9
10
GET /562f25980001b1b106000338.jpg HTTP/1.1
Host:img.mukewang.com
User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36
Accept:image/webp,image/*,*/*;q=0.8
Referer:http://www.imooc.com/
Accept-Encoding:gzip, deflate, sdch
Accept-Language:zh-CN,zh;q=0.8
空行
请求数据为空

**POST**请求报文(注意POST的请求内容不为空)

1
2
3
4
5
6
7
8
POST / HTTP1.1
Host:www.wrox.com
User-Agent:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)
Content-Type:application/x-www-form-urlencoded
Content-Length:40
Connection: Keep-Alive
空行
name=Professional%20Ajax&publisher=Wiley

GETPOST的区别

其他博客(点击跳转)

  • 最直观的区别就是GET把参数包含在URL中,POST通过request body传递参数
  • GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留
  • GET请求在URL中传送的参数是有长度限制。(大多数)浏览器通常都会限制url长度在2K个字节,而(大多数)服务器最多处理64K大小的url
  • GET产生一个TCP数据包;POST产生两个TCP数据包。对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);而对于POST,浏览器先发送header,服务器响应100(指示信息—表示请求已接收,继续处理)continue,浏览器再发送data,服务器响应200 ok(返回数据)

HTTP多种请求方法

序号 方法 描述
1 GET 请求指定的页面信息,并返回实体主体
2 HEAD 类似于GET请求,只不过返回的响应中没有具体的内容,用于获取报头
3 POST 向指定资源提交要处理的数据进行处理请求(例如提交表单或上传文件)。数据包含在请求体中,POST请求可能会导致新的资源的建立和/或已有资源的修改
4 PUT 从客户端向服务器传送的数据取代指定的文档的内容
5 DELETE 请求服务器删除指定的页面
6 CONNECT HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器
7 OPTIONS 允许客户端查看服务器的性能
8 TRACE 回显服务器收到的请求,主要用于测试或诊断

(2)响应报文

响应报文由状态行、消息报头、空行、响应正文四个部分组成

  • 状态行,由HTTP协议版本号,状态码,状态消息 三部分组成
  • 消息报头,用来说明客户端要使用的一些附加信息
  • 空行,消息报头后面的空行是必须的
  • 响应正文,服务器返回给客户端的文本信息等


2.HTTP请求/响应步骤

  1. 客户端连接到web服务器

    • 一个HTTP客户端,通常是浏览器,与 Web 服务器的 HTTP 端口(默认为80)建立一个 TCP 套接字连接。例如,http://www.baidu.com。(URL)
  2. 发送HTTP请求

    • 通过 TCP 套接字,客户端向 Web 服务器发送一个文本的请求报文,一个请求报文由请求行、请求头部、空行和请求数据 4 部分组成
  3. 服务器接受请求并返回HTTP响应

    • Web 服务器解析请求,定位请求资源。服务器将资源复本写到 TCP 套接字,由客户端读取。一个响应由状态行、响应头部、空行和响应数据 4 部分组成
  4. 释放连接TCP连接

    • connection模式为 close,则服务器主动关闭 TCP连接,客户端被动关闭连接,释放TCP连接;若connection 模式为 keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求
  5. 客户端浏览器解析HTML内容

    • 客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响应头,响应

      头告知以下为若干字节的 HTML文档和文档的字符集。客户端浏览器读取响应数据 HTML,根据

      HTML 的语法对其进行格式化,并在浏览器窗口中显示

例如:在浏览器地址栏键入URL,按下回车之后会经历以下流程

  1. 浏览器向DNS服务器请求解析该URL中域名所对应的IP地址
  2. 解析出 IP 地址后,根据该IP地址和默认端口 80,和服务器建立 TCP 连接
  3. 浏览器发出读取文件( URL 中域名后面部分对应的文件)的 HTTP 请求,该请求报文作为 TCP 三次握手的第三个报文的数据发送给服务器
  4. 服务器对浏览器请求作出响应,并把对应的 HTML 文本发送给浏览器
  5. 释放 TCP 连接
  6. 浏览器将该 HTML 文本并显示内容

image-20240519112520901

HTTP 协议是基于 TCP/IP 协议之上的应用层协议,基于 请求-响应 的模式HTTP 协议规定,请求从客户端发出,最后服务器端响应该请求并返回。换句话说,肯定是先从客户端开始建立通信的,服务器端在没有接收到请求之前不会发送响应



3.项目中HTTP处理步骤

分为三个步骤:

  • 连接处理:浏览器端发出http连接请求,主线程创建http对象接收请求并将所有数据读入对应buffer,将该对象插入任务队列,等待工作线程从任务队列中取出一个任务进行处理。
  • 处理报文请求:工作线程取出任务后,调用进程处理函数,通过主、从状态机对请求报文进行解析。
  • 返回响应报文:解析完之后,生成响应报文,返回给浏览器端

后续会根据代码进行步骤的解析




三、定时器处理非活跃连接

1.原理

如果一个客户端与服务器长时间连接,并且不进行数据的交互,这个连接就没有存在的意义还占据了服务器的资源。在这种情况下,服务器就需要一种手段检测无意义的连接,并对这些连接进行处理

除了处理非活跃的连接之外,服务器还有一些定时事件,比如关闭文件描述符等

为实现这些功能,服务器就需要为各事件分配一个定时器

该项目使用SIGALRM信号来实现定时器,首先每一个定时事件都处于一个升序链表上,通过alarm()函数周期性触发SIGALRM信号,而后信号回调函数利用管道通知主循环,主循环接收到信号之后对升序链表上的定时器进行处理:若一定时间内无数据交换则关闭连接

知乎对应文章的代码有注释(点击跳转)



2.框图

img

文字描述:

  • 服务器首先创建定时器容器链表,然后用统一事件源将异常事件,读写事件和信号事件统一处理,根据不同事件的对应逻辑使用定时器。
  • 具体的,浏览器与服务器连接时,创建该连接对应的定时器,并将该定时器添加到定时器容器链表上;
  • 处理异常事件时,执行定时事件,服务器关闭连接,从链表上移除对应定时器;
  • 处理定时信号时,将定时标志设置为true,以便执行定时器处理函数;
  • 处理读/写事件时,若某连接上发生读事件或某连接给浏览器发送数据,将对应定时器向后移动,否则,执行定时事件。


四、日志系统

为了记录服务器的运行状态,错误信息,访问数据的文件等,需要建立一个日志系统。本项目中,使用单例模式(C++设计模式)创建日志系统

这个部分直接结合源码,从log.h入手进行阅读,先查看同步写入的方式,在进行异步写入日志以及阻塞队列的阅读



五、数据库连接池

该项目在处理用户连接时,采用的是:每一个HTTP连接获取一个数据库连接,获取其中的用户账号密码进行对比(有点损耗资源,实际场景下肯定不是这么做的),而后再释放该数据库连接。

那为什么要创建数据库连接池呢?

数据库访问的一般流程为:当系统需要访问数据库时,先系统创建数据库连接,完成数据库操作,然后系统断开数据库连接。——从中可以看出,若系统需要频繁访问数据库,则需要频繁创建和断开数据库连接,而创建数据库连接是一个很耗时的操作,也容易对数据库造成安全隐患

在程序初始化的时候,集中创建多个数据库连接,并把他们集中管理,供程序使用,可以保证较快的数据库读写速度,更加安全可靠

其实数据库连接池跟线程池的思想基本是一致的。

在该项目中不仅实现了数据库连接池,还将数据库连接的获取与释放通过RAII机制封装,避免手动释放




六、封装同步类

为便于实现同步类的RAII机制,该项目在pthread库的基础上进行了封装,实现了类似于C++11的mutex、condition_variable。

可以阅读文件夹lock中的源码进行这方面的学习