整体框架

1.复习http

2.部署HTTP服务端 nginx

3.部署HTTP服务端 workflow(第三方框架)

4.使用workflow实现一个网盘,基于HTTP协议

5.实现备份 阿里云OSS 或者 ceph

6.消息队列 rabbitmq(erlang) 跨越网络进行通信

7.容器 轻量级的虚拟机 docker

8.RPC 远程过程调用 —》微服务

9.注册中心 consul

10.搜索引擎项目

HTTP

HTTP超文本 协议头是一个文本协议,让网络通信用起来像函数调用一样,让客户端对应主调函数,客户端对应被调函数,请求被看作参数,响应被看作返回值。

http协议的性质

  • 原子性
  • 无状态性

两个http事务在应用层面没有关联

REST设计方案

目标:让HTTP接口用起来像函数调用一样

函数调用分为三部分

1.对象(path,URI的路径) 找到它的成员函数:增(POST)删(DELETE)改(PUT)查(GET)

2.传递参数 报文体(序列化:把复杂二点数据结构转换为普通的字节流(方案:xml/json))

3.收返回值 报文体 (同样需要序列化(XML/json))

POST和PUT的区别

幂等性:出现了多次重复请求如果结果还是一致的说明是幂等的。

想要实现幂等性,,要有唯一id,不同的请求有不同的id,重复的请求有重复的id

实现方式:

  1. MySQL/redis 自动增长id

  2. 雪花算法:支持在大规模分布式系统中生成唯一id

使用HTTP服务端的三种方案

  1. 从berkeley Socket库开始开发:修改reactor:1.把eadLine改为read报文 2.httpparser

  2. 使用现成的产品,Apache Httpd/nginx

  3. 基于框架来写,boost.asio(支持两种范式:1.异步回调 2.协程) 再在这个基础上构建boost.beast

    brpc(协程方案)

    workflow(异步回调模型)

nginx vs apache httpd

apache是一种基于线程的模型

代码框架

1
2
3
4
5
while(1){
accpet;
pthread_create/fork;
TODO;
}

特点:

  1. 业务开发很简单,但是效率低
    • 内存:每一个连接都要占用一个线程,内存占用率很高,低并发
    • CPU:线程数太多,大量的时间进行上下文切换
    • 调度是内核态

nginx特点

nginx是事件驱动模型,使用进程池

好处:

  1. 高并发量:内存,一个线程占有多个文件对象(IO多路复用 select/epoll)
  2. 单线程:没有上下文切换
  3. 调度是用户态的

缺点:

1
2
3
4
5
6
7
while(1){
select/epoll --wait;
switch(fd){
case fd:
hander(fd);//万一这一步的处理时间特别长 要把复杂的业务拆分,才可以让这个业务不会阻塞其他的业务
}
}
  1. 要把一个完整的业务拆分一个个的事件,开发效率低

两者的使用场景

业务简单,并发量高 事件驱动

  • 静态资源,把磁盘的内容原封不动的返回给客户端
  • 代理

业务复杂,并发量低 基于进程/线程

  • 绘制界面
  • 价格/库存

一般组合使用(反向代理服务器:更靠近客户端的叫正向代理,靠近服务器的叫反向代理):

image-20240722113205517

nginx可以放缓存,如果请求来的话,请求的资源如果存在就直接返回,否则就交给业务服务器(apache,tomcat)

也可以做负载均衡

如何使用nginx

安装

1
sudo apt install nginx

启动(安装完成会自启动)

1
cd /usr/sbin/nginx

配置文件目录

1
/etc/nginx/nginx.conf

当前工作目录(配置文件的搜索的起始目录)

1
/usr/share/naginx/

查看系统已经存在的nginx进程

1
ps -elf|grep nginx

关掉已经存在的nginx进程

1
sudo kill -9 进程id

启动nginx进程(不可在普通用户下执行)

切换到root下执行

1
sudo su
1
nginx -c /etc/nginx/nginx.conf

更改配置文件

1
vim /etc/nginx/nginx.conf
  • worker_process 1 工作进程的个数
  • user 用户名
  • events{ … } 单次可以处理epoll的个数
  • http{…}
    • listen 是监听的端口号
    • server_name 主机名
    • location()

重启nginx服务端指令

1
2
nginx -s quit -c /etc/nginx/nginx.conf
nginx -c /etc/nginx/nginx.conf

部署一个产品文档

  1. 下载文档,存在服务端的某个目录下

    • 更改nginx.conf 的http下的root 后面换成资源路径(表示从这个资源路径开始寻找文件)
  2. 把起始目录作为root的参数

  3. 重启nginx

  4. 访问的时候,只需要再root后面资源路径加上资源的路径,就可以拼接出来一个完整的路径

网盘系统

ed1b5ee3a71eb5ef3d1c905156314563.png

反向代理和负载均衡

反向代理:

1.轮询 不能用缓存

2.哈希 最后一下取余数会导致缓存大面积失效

3.环形哈希 一致性哈希

827eddfb990ca0685c6962064758028b.png

框架

1.事件驱动模型 reactor libevent libev libhv

​ recv用户代码去完成

2.异步回调 和事件驱动的区别在于 操作系统会把recv也做好,而事件驱动不做

​ 并且操作系统recv的时候,主线程不会阻塞,会去干其他的事情,有一个执行流等待数据,等数据取完并执行完相关的业务以后(回调函数)再告知主线程

3.协程(随之可以暂停和恢复的函数)例如有好几个recv,如果执行阻塞的时候就直接切换函数,但只能处理io密集型,不能做cpu密集型

  • 使用协程的框架 brpc libco

workflow框架

是一个异步回调框架

业务的最小单元:任务 recv+hanlder

aad7973688406853dc31d0a1e580770f.png

使用workflow的一般流程

  1. 将任务分解成任务 类型,handler
  2. 创建任务,设置任务的属性以及不同任务之间的关联
  3. 将任务交给框架(异步)

安装workflow

1.解压

1
tar xf 文件名

2.进入解压好的文件夹,可以看到使用cmake代码构建系统

1
2
3
4
sudo apt install cmake
mkdir build
cd build/
cmake ..

cmake ..可能会报错

1
sudo apt install lib库  -dev

3.编译&链接

1
make //不要加sudo

4.安装

1
2
sudo make install /user/local/include/
sudo make install /user/local/lib/

5.sudo ldconfig

6.验证库是否安装成功

1
g++ *.cc -o 可执行文件名 -lworkflow

使用第三方库的编程

1.头文件(*.cc *.h) 编译

2.库文件(*.so/*.a) 链接

waitGroup

让某一个线程处于等待状态,等待n件事情发生

里面要用到三个函数

1
2
3
WaitGroup(int n);//n代表要等待的事情个数
void done();//做完了一件事
void wait();//等待直到所有事情做完

示例

1
2
3
4
5
6
7
#include <workflow/WFFailities.h>

static WFFacilities:WAitGrou waitGroup(1);//static表示只在本文件内部生效

int main(){
waitGroup.wait();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <workflow/WFFailities.h>
#include <signal.h>
static WFFacilities:WAitGrou waitGroup(1);//static表示只在本文件内部生效

void sigHandler(int signum){
waitGroup.done();
}


int main(){
signal(SIGINT,siganlder);
waitGroup.wait();
return 0;
}

客户端任务

任务执行流程:发请求,收响应,解析响应–>callback

前三部分由框架实现,基本工作,后面的callback用户自定义的回调函数

HTTP类型的任务

http任务是使用工厂函数创建的,不直接使用构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <workflow/WFFailities.h>
//WFTaskFactory.h文件里面包含了所有创建任务相关的函数
#include <workflow/WFTaskFactory.h>
#include <signal.h>
static WFFacilities:WAitGrou waitGroup(1);//static表示只在本文件内部生效

void sigHandler(int signum){
waitGroup.done();
}


int main(){
signal(SIGINT,siganlder);
WFHttpTask * httpTask = WFTaskFactory::create_http_task(
"http://www.baidu.com",//url
10,//重定向次数上限
10,//重试次数
nullptr);//回调函数
waitGroup.wait();
return 0;
}

创建好任务以后要交给框架(启动任务,由框架进行调度)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <workflow/WFFailities.h>
//WFTaskFactory.h文件里面包含了所有创建任务相关的函数
#include <workflow/WFTaskFactory.h>
#include <signal.h>
static WFFacilities:WAitGrou waitGroup(1);//static表示只在本文件内部生效

void sigHandler(int signum){
waitGroup.done();
}


int main(){
signal(SIGINT,siganlder);
WFHttpTask * httpTask = WFTaskFactory::create_http_task(
"http://www.baidu.com",//url
10,//重定向次数上限
10,//重试次数
nullptr);//回调函数
//在启动任务之前设置任务的属性
//只能改请求,不能改响应
protoco::HttpRequest *rq = httpTask->get_req();//获取请求的指针
req->add_header_pair("ky1","value1");//添加首部字段
httpTask->start();
waitGroup.wait();
return 0;
}

交给框架以后:

  1. 检查一下,资源是否就绪
  2. 任务的基本工作:建立连接–发送请求–收响应–解析响应(对于所有的http任务都是一样的,框架代码处理)
  3. 收到响应之后,执行用户设计的代码(回调函数,可以读请求和响应,可以根据响应执行后续的代码)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//获取所有的请求信息和响应信息
#include <workflow/WFFailities.h>
//WFTaskFactory.h文件里面包含了所有创建任务相关的函数
#include <workflow/WFTaskFactory.h>
#include <signal.h>
static WFFacilities:WAitGrou waitGroup(1);//static表示只在本文件内部生效

void sigHandler(int signum){
waitGroup.done();
}
void httpCallBack(WFHttpTask *httpTask){
protocol::HttpRequest *req = httpTask->get_req();
cout << req->get_method() << '\n';
cout << req->get_request_uri() << '\n';
cout << req->get_http_version() << '\n';
protocol::HttpResponse *resp = httpTask->get_resp();
cout << resp->get_ttp_version() << '\n';
cout << resp->get_status_code() << '\n';
cout << resp->get_resaon_phrase() << '\n';
}

int main(){
signal(SIGINT,siganlder);
WFHttpTask * httpTask = WFTaskFactory::create_http_task(
"http://www.baidu.com",//url
10,//重定向次数上限
10,//重试次数
nullptr);//回调函数
//在启动任务之前设置任务的属性
//只能改请求,不能改响应
protoco::HttpRequest *rq = httpTask->get_req();//获取请求的指针
req->add_header_pair("ky1","value1");//添加首部字段
httpTask->start();
waitGroup.wait();
return 0;
}

如果网址错误,那么请求就会失败,所以要检查报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//获取所有的请求信息和响应信息
#include <workflow/WFFailities.h>
//WFTaskFactory.h文件里面包含了所有创建任务相关的函数
#include <workflow/WFTaskFactory.h>
#include <signal.h>
static WFFacilities:WAitGrou waitGroup(1);//static表示只在本文件内部生效

void sigHandler(int signum){
waitGroup.done();
}
void httpCallBack(WFHttpTask *httpTask){
//检查报错 复制框架的代码
int state = httpTask->get_state();
int error = httpTask->get_error();
switch(state){
case WFT_STATE_SYS_ERROR:
cerr << "system error:" << strerror(error) << "\n";
break;
case WFT_STATE_DNS_ERROR:
cerr << "DNS error:" << gai_strerror(error) << '\n';
break;
case WFT_STATE_SSL_ERROR:
cerr << "SSL error :" << error << '\n';
break;
case WFT_STATE_TASK_ERROR:
cerr << "Task error :" << error << '\n';
break;
case WFT_STATE_SUCCESS:
break;
}
if(state != WFT_STATE_SUCCESS){
cerr << "Failed. Press Ctrl-C to exit.\n";
return;
}




//请求的信息
protocol::HttpRequest *req = httpTask->get_req();
cout << req->get_method() << '\n';
cout << req->get_request_uri() << '\n';
cout << req->get_http_version() << '\n';

//响应的信息
protocol::HttpResponse *resp = httpTask->get_resp();
cout << resp->get_ttp_version() << '\n';
cout << resp->get_status_code() << '\n';
cout << resp->get_resaon_phrase() << '\n';
}

int main(){
signal(SIGINT,siganlder);
WFHttpTask * httpTask = WFTaskFactory::create_http_task(
"http://www.baidu.com",//url
10,//重定向次数上限
10,//重试次数
nullptr);//回调函数
//在启动任务之前设置任务的属性
//只能改请求,不能改响应
protoco::HttpRequest *rq = httpTask->get_req();//获取请求的指针
req->add_header_pair("ky1","value1");//添加首部字段
httpTask->start();
waitGroup.wait();
}

处理首部字段和报文体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
//获取所有的请求信息和响应信息
#include <workflow/WFFailities.h>
//WFTaskFactory.h文件里面包含了所有创建任务相关的函数
//包含处理http的一些工具
#include <workflow/HttpUtil.h>
#include <workflow/WFTaskFactory.h>
#include <signal.h>
static WFFacilities:WAitGrou waitGroup(1);//static表示只在本文件内部生效

void sigHandler(int signum){
waitGroup.done();
}
void httpCallBack(WFHttpTask *httpTask){
//检查报错 复制框架的代码
int state = httpTask->get_state();
int error = httpTask->get_error();
switch(state){
case WFT_STATE_SYS_ERROR:
cerr << "system error:" << strerror(error) << "\n";
break;
case WFT_STATE_DNS_ERROR:
cerr << "DNS error:" << gai_strerror(error) << '\n';
break;
case WFT_STATE_SSL_ERROR:
cerr << "SSL error :" << error << '\n';
break;
case WFT_STATE_TASK_ERROR:
cerr << "Task error :" << error << '\n';
break;
case WFT_STATE_SUCCESS:
break;
}
if(state != WFT_STATE_SUCCESS){
cerr << "Failed. Press Ctrl-C to exit.\n";
return;
}




//请求的信息
protocol::HttpRequest *req = httpTask->get_req();
cout << req->get_method() << '\n';
cout << req->get_request_uri() << '\n';
cout << req->get_http_version() << '\n';

//遍历首部字段
protocol::HttpHeaderCursor reqCursor(req);
string key,value;
while(reqursor.next(key,value)){
cout << key << value << '\n';
}

//响应的信息
protocol::HttpResponse *resp = httpTask->get_resp();
cout << resp->get_ttp_version() << '\n';
cout << resp->get_status_code() << '\n';
cout << resp->get_resaon_phrase() << '\n';
protocol::HttpHeaderCursor reqCursor(resp);
while(reqursor.next(key,value)){
cout << key << value << '\n';
}
}

int main(){
signal(SIGINT,siganlder);
WFHttpTask * httpTask = WFTaskFactory::create_http_task(
"http://www.baidu.com",//url
10,//重定向次数上限
10,//重试次数
nullptr);//回调函数
//在启动任务之前设置任务的属性
//只能改请求,不能改响应
protoco::HttpRequest *rq = httpTask->get_req();//获取请求的指针
req->add_header_pair("ky1","value1");//添加首部字段
httpTask->start();
waitGroup.wait();
}

workflow框架里面 http报文头和报文体是不再一起的

获取报文体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//获取所有的请求信息和响应信息
#include <workflow/WFFailities.h>
//WFTaskFactory.h文件里面包含了所有创建任务相关的函数
//包含处理http的一些工具
#include <workflow/HttpUtil.h>
#include <workflow/WFTaskFactory.h>
#include <signal.h>
static WFFacilities:WAitGrou waitGroup(1);//static表示只在本文件内部生效

void sigHandler(int signum){
waitGroup.done();
}
void httpCallBack(WFHttpTask *httpTask){
//检查报错 复制框架的代码
int state = httpTask->get_state();
int error = httpTask->get_error();
switch(state){
case WFT_STATE_SYS_ERROR:
cerr << "system error:" << strerror(error) << "\n";
break;
case WFT_STATE_DNS_ERROR:
cerr << "DNS error:" << gai_strerror(error) << '\n';
break;
case WFT_STATE_SSL_ERROR:
cerr << "SSL error :" << error << '\n';
break;
case WFT_STATE_TASK_ERROR:
cerr << "Task error :" << error << '\n';
break;
case WFT_STATE_SUCCESS:
break;
}
if(state != WFT_STATE_SUCCESS){
cerr << "Failed. Press Ctrl-C to exit.\n";
return;
}




//请求的信息
protocol::HttpRequest *req = httpTask->get_req();
cout << req->get_method() << '\n';
cout << req->get_request_uri() << '\n';
cout << req->get_http_version() << '\n';

//遍历首部字段
protocol::HttpHeaderCursor reqCursor(req);
string key,value;
while(reqursor.next(key,value)){
cout << key << value << '\n';
}

//响应的信息
protocol::HttpResponse *resp = httpTask->get_resp();
cout << resp->get_ttp_version() << '\n';
cout << resp->get_status_code() << '\n';
cout << resp->get_resaon_phrase() << '\n';
protocol::HttpHeaderCursor reqCursor(resp);
while(reqursor.next(key,value)){
cout << key << value << '\n';
}

const void *body;//不可修改指向的内容,可以修改指向
size_t size;
resp->get_parsed_body(&body,&size);
cout << static_cast<const har *>(body) << '\n';//报文如果是文本数据可以转
}

int main(){
signal(SIGINT,siganlder);
WFHttpTask * httpTask = WFTaskFactory::create_http_task(
"http://www.baidu.com",//url
10,//重定向次数上限
10,//重试次数
nullptr);//回调函数
//在启动任务之前设置任务的属性
//只能改请求,不能改响应
protoco::HttpRequest *rq = httpTask->get_req();//获取请求的指针
req->add_header_pair("ky1","value1");//添加首部字段
httpTask->start();
waitGroup.wait();
}