1、FastDFS
1. 项目架构图
1.1 一些概念
-
什么是服务器
- 硬件: 一台配置高的电脑
- 软件: 电脑必须有一个能够解析http协议的软件
-
常见的Web服务器
-
tomcat服务器
- apache组织产品, 开源的免费服务器
-
weblogic 服务器
- bea公司, 收费的服务器
- 不交费, 访问量受限制
-
IIS服务器
-
Internet Information Server
-
微软公司主推的服务器
-
-
nginx
- 小巧且高效的HTTP服务器
- 也可以做一个高效的负载均衡反向代理
- 邮件服务器
- pop3/smtp/imap
-
1.2 项目架构图
- 客户端
- 网络架构:
- b/s
- 必须使用http协议
- c/s
- 协议可以随意选择
- Qt -> http
- b/s
- 网络架构:
- 服务器
- Nginx
- 能处理静态请求 -> html, jpg
- 动态请求无法处理
- 服务器集群之后, 每台服务器上部署的内容必须相同
- fastCGI
- 帮助服务器处理动态请求
- Nginx
- 反向代理服务器
- 客户端并不能直接访问web服务器, 直接访问到的是反向代理服务器
- 客户端静请求发送给反向代理服务器, 反向代理将客户端请求转发给web服务器
- 关系型数据库
- 存储文件属性信息
- 用户的属性信息
- redis - 非关系型数据库 (内存数据库)
- 作用提出程序效率
- 存储是服务器经常要从关系型数据中读取的数据
- FASTDFS - 分布式文件系统
- 存储文件内容
- 供用户下载
2. 分布式文件系统
2.1 传统文件系统
- 传统的文件系统格式:
- ntfs / fat32 / ext3 / ext4
- 可以被挂载和卸载
2.2 分布式文件系统
文件系统的全部, 不在同一台主机上,而是在很多台主机上,多个分散的文件系统组合在一起,形成了一个完整的文件系统。
分布式文件系统:
- 需要有网络
- 多台主机
- 不需要在同一地点
- 需要管理者
- 编写应用层的管理程序
- 不需要编写
3. FastDFS
3.1 fastDFS介绍
-
fastDFS概述
- 是用c语言编写的一款开源的分布式文件系统。
- 余庆 - 淘宝的架构师
- 为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,注重高可用、高性能等指标
- 冗余备份: 纵向扩容
- 线性扩容: 横向扩容
- 可以很容易搭建一套高性能的文件服务器集群提供文件==上传、下载==等服务。
- 图床
- 网盘
- 是用c语言编写的一款开源的分布式文件系统。
-
fastDFS框架中的三个角色
- 追踪器 ( Tracker ) - 管理者 - 守护进程
- 管理存储节点
- 存储节点 - storage - 守护进程
- 存储节点是有多个的
- 客户端 - 不是守护进程, 这是程序猿编写的程序
- 文件上传
- 文件下载
- 追踪器 ( Tracker ) - 管理者 - 守护进程
-
fastDFS三个角色之间的关系
- 追踪器
- 最先启动追踪器
- 存储节点
- 第二个启动的角色
- 存储节点启动之后, 会单独开一个线程
- 汇报当前存储节点的容量, 和剩余容量
- 汇报数据的同步情况
- 汇报数据被下载的次数
- 客户端
- 最后启动
- 上传
- 连接追踪器, 询问存储节点的信息
- 我要上传1G的文件, 询问那个存储节点有足够的容量
- 追踪器查询, 得到结果
- 追踪器将查到的存储节点的IP+端口发送给客户端
- 通过得到IP和端口连接存储节点
- 将文件内容发送给存储节点
- 连接追踪器, 询问存储节点的信息
- 下载
- 连接追踪器, 询问存储节点的信息
- 问一下, 要下载的文件在哪一个存储节点
- 追踪器查询, 得到结果
- 追踪器将查到的存储节点的IP+端口发送给客户端
- 通过得到IP和端口连接存储节点
- 下载文件
- 连接追踪器, 询问存储节点的信息
- 上传
- 最后启动
4. fastDFS集群 - (了解即可)
- 追踪器集群
- 为什么集群?
- 避免单点故障
- 多个Tracker如何工作?
- 轮询工作
- 如何实现集群?
- 修改配置文件
- 为什么集群?
- 存储节点集群
- fastDFS管理存储节点的方式?
- 通过分组的方式完成的
- 集群方式(扩容方式)
- 横向扩容 - 增加容量
- 添加一台新的主机 -> 容量增加了(增加新的组)
- 假设当前有两个组: group1, group2
- 需要添加一个新的分组 -> group3
- 新主机属于第三组
- 需要添加一个新的分组 -> group3
- 不同组的主机之间不需要通信
- 纵向扩容 - 数据备份(组中增加新的主机)
- 假设当前有两个组: group1, group2
- 将新的主机放到现有的组中
- 每个组的主机数量从1 -> N
- 这n台主机的关系就是相互备份的关系
- 同一个组中的主机需要通信
- 每组的容量 == 容量最小的这台主机
- 假设当前有两个组: group1, group2
- 横向扩容 - 增加容量
- 如何实现?
- 通过修改配置文件可实现
- fastDFS管理存储节点的方式?
3.2 fastDFS安装
-
fastDFS安装
- libfastcommon-1.36.zip
- fastdfs的基础库包
- unzip xxx.zip
- ./make.sh
- ./make.sh install
- fastdfs-5.10.tar.gz
- tar zxvf xxx.tar.gz
- ./make.sh
- ./make.sh install
- libfastcommon-1.36.zip
-
测试
#fastDFS安装的所有的可执行程序: /usr/bin/fdfs_* fdfs_test
3.3 fastDFS配置文件
配置文件默认位置: /etc/fdfs
client.conf.sample storage.conf.sample storage_ids.conf.sample tracker.conf.sample
对配置文件修改时,修改备份而不是源文件
sudo cp client.conf.sample client.conf#复制源文件
vim client.conf #修改复制文件
-
tracker 配置文件
# 将追踪器和部署的主机的IP地址进程绑定, 也可以不指定 # 如果不指定, 会自动绑定当前主机IP, 如果是云服务器建议不要写 bind_addr=192.168.247.135 # 追踪器监听的端口 port=22122 # 追踪器存储日志信息的目录, xxx.pid文件, 必须是一个存在的目录 base_path=/home/yuqing/fastdfs
-
storage 配置文件
# 当前存储节点对应的主机属于哪一个组 group_name=group1 # 当前存储节点和所应该的主机进行IP地址的绑定, 如果不写, 有fastdfs自动绑定 bind_addr= # 存储节点绑定的端口 port=23000 # 存储节点写log日志的路径 base_path=/home/yuqing/fastdfs # 存储节点提供的存储文件的路径个数 store_path_count=2 # 具体的存储路径 store_path0=/home/yuqing/fastdfs store_path1=/home/yuqing/fastdfs1 # 追踪器的地址信息 tracker_server=192.168.247.135:22122 tracker_server=192.168.247.136:22122
-
客户端配置文件
# 客户端写log日志的目录 # 该路径必须存在 # 当前的用户对于该路径中的文件有读写权限 # 当前用户robin # 指定的路径属于root base_path=/home/yuqing/fastdfs # 要连接的追踪器的地址信息 tracker_server=192.168.247.135:22122 tracker_server=192.168.247.136:22122
- 安装fastDFS安装包
3.4 fastDFS的启动
-
第一个启动追踪器 - 守护进程
# 启动程序在 /usr/bin/fdfs_* # 启动 fdfs_trackerd 追踪器的配置文件(/etc/fdfs/tracker.conf) # 关闭 fdfs_trackerd 追踪器的配置文件(/etc/fdfs/tracker.conf) stop # 重启 fdfs_trackerd 追踪器的配置文件(/etc/fdfs/tracker.conf) restart
-
第二个启动存储节点 - 守护进程
# 启动 fdfs_storaged 存储节点的配置文件(/etc/fdfs/storage.conf) # 关闭 fdfs_storaged 存储节点的配置文件(/etc/fdfs/storage.conf) stop # 重启 fdfs_storaged 存储节点的配置文件(/etc/fdfs/storage.conf) restart
-
最后启动客户端 - 普通进程
# 上传 fdfs_upload_file 客户端的配置文件(/etc/fdfs/client.conf) 要上传的文件 # 得到的结果字符串: group1/M00/00/00/wKj3h1vC-PuAJ09iAAAHT1YnUNE31352.c # 下载 fdfs_download_file 客户端的配置文件(/etc/fdfs/client.conf) 上传成功之后得到的字符串(fileID)
-
fastDFS状态检测
-
命令
fdfs_monitor /etc/fdfs/client.conf
-
https://blog.csdn.net/u014723529/article/details/46048411
# FDFS_STORAGE_STATUS:INIT :初始化,尚未得到同步已有数据的源服务器 # FDFS_STORAGE_STATUS:WAIT_SYNC :等待同步,已得到同步已有数据的源服务器 # FDFS_STORAGE_STATUS:SYNCING :同步中 # FDFS_STORAGE_STATUS:DELETED :已删除,该服务器从本组中摘除 # FDFS_STORAGE_STATUS:OFFLINE :离线 # FDFS_STORAGE_STATUS:ONLINE :在线,尚不能提供服务 # FDFS_STORAGE_STATUS:ACTIVE :在线,可以提供服务
-
3.5 对file_id的解释
- group1
- 文件上传到了存储节点的哪一个组
- 如果有多个组这个组名可变的
- M00 - 虚拟目录
- 和存储节点的配置项有映射
- store_path0=/home/yuqing/fastdfs/data -> M00
store_path1=/home/yuqing/fastdfs1/data -> M01
- store_path0=/home/yuqing/fastdfs/data -> M00
- 和存储节点的配置项有映射
- 00/00
- 实际的路径
- 可变的
- wKhS_VlrEfOAdIZyAAAJTOwCGr43848.md
- 文件名包含的信息
- 采用Base64编码
-
包含的字段包括
-
源storage server Ip 地址
-
文件创建时间
-
文件大小
-
文件CRC32效验码
- 循环冗余校验
-
随机数
-
-
4. 上传下载代码实现
-
使用多进程方式实现
-
exec函数族函数
- execl
- execlp
-
父进程
-
子进程 -> 执行
execlp(“fdfs_upload_file” , “xx”, arg, NULL), 有结果输出, 输出到终端
- 不让它写到终端 -> 重定向dup2(old, new)
- old-> 标准输出
- new -> 管道的写端
- 文件描述符
- 数据块读到内存 -> 子进程
- 数据最终要给到父进程
- pipe -> 读端, 写端
- 在子进程创建之前创建就行了
- 不让它写到终端 -> 重定向dup2(old, new)
-
父进程
- 读管道 -> 内存
- 内存数据写数据库
-
-
-
使用fastDFS API实现
5. 源码安装 - 回顾
安装流程:
- 以下文件, 里边有安装步骤
- readme
- readme.md
- INSTALL
- 找 可执行文件 configure
- 执行这个可执行文件
- 检测安装环境
- 生成 makefile
- 执行这个可执行文件
- 执行make命令
- 编译源代码
- 生成了动态库
- 静态库
- 可执行程序
- 编译源代码
- 安装 make install (需要管理员权限)
- 将第三步生成的动态库/动态库/可执行程序拷贝到对应的系统目录
6. 复习
-
fastDFS
-
是什么?
- 分布式文件系统
-
干什么?
- 提供文件上传
- 提供文件下载
-
怎么使用?
-
根据主机的角色 -> 修改对应的配置文件
-
启动各个角色
fdfs_trackerd /etc/fdfs/tracker.conf fdfs_storaged /etc/fdfs/storage.conf
客户端编写
-
-
- 操作步骤
1. 创建管道 - pipe
2. 创建子进程
3. 子进程干什么?
- 写管道, 关闭读端
- 将标准输出 -> 管道的写端
- 重定向
- 执行execl命令, 调用另外的进程fdfs_upload_file
- 子进程退出
4. 父进程?
- 读管道, 关闭写端
- 释放子进程资源 - pcb
- wait()/ waitpid()
2、redis
1. 数据库类型
1.1 基本概念
- 关系型数据库 - sql
- 操作数据必须要使用sql语句
- 数据存储在磁盘
- 存储的数据量大
- 举例:
- mysql
- oracle
- sqlite - 文件数据库
- sql server
- 非关系数据库 - nosql
- 操作不使用sql语句
- 命令
- 数据默认存储在内存
- 速度快, 效率高
- 存储的数据量小
- 不需要数据库表
- 以键值对的方式存储的
- 操作不使用sql语句
1.2 关系/非关系型数据库搭配使用
RDBMS: Relational Database Management System
- 所有的数据默认存储在关系型数据库中
- 客户端访问服务器, 有一些数据, 服务器需要频繁的查询数据
- 服务器首先将数据从关系型数据库中读出 -> 第一次
- 将数据写入到redis中
- 客户端第二次包含以后访问服务器
- 服务器从redis中直接读数据
2. Redis
- 知道redis是什么?
- 非关系型数据库 也可以叫 内存数据库
- 能干什么?
- 存储访问频率高的数据
- 共享内存
- 服务器端 -> redis
- 怎么使用?
- 常用的操作命令
- 各种数据类型 -> 会查
- redis的配置文件
- redis的数据持久化
- 写程序的时候如何对redis进行操作
- 客户端 -> 服务器
2.1 基本知识点
-
安装包下载
- 英文官方: https://redis.io/
- 中文官方: http://redis.cn/
-
Redis安装
- 解压缩 tar zxvf redis-4.0.8.tar.gz
- make
- make install
-
redis中的两个角色
# 服务器 - 启动 redis-server # 默认启动 redis-server confFileName # 根据配置文件的设置启动 # 客户端 redis-cli # 默认连接本地, 绑定了6379默认端口的服务器 redis-cli -p 端口号 redis-cli -h IP地址 -p 端口 # 连接远程主机的指定端口的redis # 通过客户端关闭服务器 shutdown # 客户端的测试命令 ping [MSG]
-
redis中数据的组织格式
- 键值对
- key: 必须是字符串 - “hello”
- value: 可选的
- String类型
- List类型
- Set类型
- SortedSet类型
- Hash类型
- 键值对
-
redis中常用数据类型
- String类型
- 字符串
- List类型
- 存储多个string字符串的
- Set类型
- 集合
- stl集合
- 默认是排序的, 元素不重复
- redis集合
- 元素不重复, 数据是无序的
- stl集合
- 集合
- SortedSet类型
- 排序集合, 集合中的每个元素分为两部分
- [分数, 成员] -> [66, ‘‘tom’’]
- 排序集合, 集合中的每个元素分为两部分
- Hash类型
- 跟map数据组织方式一样: key:value
- Qt -> QHash, QMap
- Map -> 红黑树
- hash -> 数组
- a[index] = xx
- 跟map数据组织方式一样: key:value
- String类型
2.2 redis常用命令
-
String类型
key -> string value -> string # 设置一个键值对->string:string SET key value # 通过key得到value GET key # 同时设置一个或多个 key-value 对 MSET key value [key value ...] # 同时查看过个key MGET key [key ...] # 如果 key 已经存在并且是一个字符串, APPEND 命令将 value 追加到 key 原来的值的末尾 # key: hello, value: world, append: 12345 APPEND key value # 返回 key 所储存的字符串值的长度 STRLEN key # 将 key 中储存的数字值减一。 # 前提, value必须是数字字符串 -"12345" DECR key
-
List类型 - 存储多个字符串
key -> string value -> list # 将一个或多个值 value 插入到列表 key 的表头 LPUSH key value [value ...] # 将一个或多个值 value 插入到列表 key 的表尾 (最右边)。 RPUSH key value [value ...] # list中删除元素 LPOP key # 删除最左侧元素 RPOP key # 删除最右侧元素 # 遍历 LRANGE key start stop start: 起始位置, 0 stop: 结束位置, -1 # 通过下标得到对应位置的字符串 LINDEX key index # list中字符串的个数 LLEN key
-
Set类型
key -> string value -> set类型 ("string", "string1") # 添加元素 # 将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略 SADD key member [member ...] # 遍历 SMEMBERS key # 差集 SDIFF key [key ...] # 交集 SINTER key [key ...] # 并集 SUNION key [key ...]
-
SortedSet 类型
key -> string value -> sorted ([socre, member], [socre, member], ...) # 添加元素 ZADD key score member [[score member] [score member] ...] # 遍历 ZRANGE key start stop [WITHSCORES] # -> 升序集合 ZREVRANGE key start stop [WITHSCORES] # -> 降序集合 # 指定分数区间内元素的个数 ZCOUNT key min max
-
Hash类型
key ->string value -> hash ([key:value], [key:value], [key:value], ...) # 添加数据 HSET key field value # 取数据 HGET key field # 批量插入键值对 HMSET key field value [field value ...] # 批量取数据 HMGET key field [field ...] # 删除键值对 HDEL key field [field ...]
-
Key 相关的命令
# 删除键值对 DEL key [key ...] # 查看key值 KEYS pattern 查找所有符合给定模式 pattern 的 key 。 KEYS * 匹配数据库中所有 key 。 KEYS h?llo 匹配 hello , hallo 和 hxllo 等。 KEYS h*llo 匹配 hllo 和 heeeeello 等。 KEYS h[ae]llo 匹配 hello 和 hallo ,但不匹配 hillo # 给key设置生存时长 EXPIRE key seconds # 取消生存时长 PERSIST key # key对应的valued类型 TYPE key
2.3 redis配置文件
配置文件是给redis服务器使用 的
-
配置文件位置
- 从源码安装目录中找 -> redis.conf
-
配置文件配置项
# redis服务器绑定谁之后, 谁就能访问redis服务器 # 任何客户端都能访问服务器, 需要注释该选项 bind 127.0.0.1 192.168.1.100 # 保护模式, 如果要远程客户端访问服务器, 该模式要关闭 protected-mode yes # reids服务器启动时候绑定的端口, 默认为6379 port 6379 # 超时时长, 0位关闭该选项, >0则开启 timeout 0 # 服务器启动之后不是守护进程 daemonize no # 如果服务器是守护进程, 就会生成一个pid文件 # ./ -> reids服务器启动时候对应的目录 pidfile ./redis.pid # 日志级别 loglevel notice # 如果服务器是守护进程, 才会写日志文件 logfile "" -> 这是没写 logfile ./redis.log # redis中数据库的个数 databases 16 - 切换 select dbID [dbID == 0 ~ 16-1]
2.4 redis数据持久化
持久化: 数据从内存到磁盘的过程
持久化的两种方式:
- rdb方式
- 这是一种默认的持久化方式, 默认打开
- 磁盘的持久化文件xxx.rdb
- 将内存数据以二进制的方式直接写入磁盘文件
- 文件比较小, 恢复时间短, 效率高
- 以用户设定的频率 -> 容易丢失数据
- 数据完整性相对较低
- aof方式
- 默认是关闭的
- 磁盘的持久化文件xxx.aof
- 直接将生成数据的命令写入磁盘文件
- 文件比较大, 恢复时间长, 效率低
- 以某种频率 -> 1sec
- 数据完整性高
# rdb的同步频率, 任意一个满足都可以
save 900 1
save 300 10
save 60 10000
# rdb文件的名字
dbfilename dump.rdb
# 生成的持久化文件保存的那个目录下, rdb和aof
dir ./
# 是不是要打开aof模式
appendonly no
-> 打开: yes
# 设置aof文件的名字
appendfilename "appendonly.aof"
# aof更新的频率
# appendfsync always
appendfsync everysec
# appendfsync no
-
aof和rdb能不能同时打开?
- 可以
-
aof和rdb能不能同时关闭?
-
可以
-
rdb如何关闭?
save ""
-
-
两种模式同时开启, 如果要进行数据恢复, 如何选择?
- 效率上考虑: rdb模式
- 数据的完整性: aof模式
3 hiredis的使用
-
hiredis的安装
- 下载地址: http://redis.cn/clients.html#c
- 安装
- unzip hiredis-master
- make
- make install
-
hiredis API接口的使用
-
连接数据库
// 连接数据库 redisContext *redisConnect(const char *ip, int port); redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv);
-
执行redis命令函数
// 执行redis命令 返回void*指针,可能是任何类型的指针 void *redisCommand(redisContext *c, const char *format, ...); // redisCommand 函数实际的返回值类型 typedef struct redisReply { /* 命令执行结果的返回类型 */ int type; /* 存储执行结果返回为整数 */ long long integer; /* str变量的字符串值长度 */ size_t len; /* 存储命令执行结果返回是字符串, 或者错误信息 */ char *str; /* 返回结果是数组, 代表数据的大小 */ size_t elements; /* 存储执行结果返回是数组*/ struct redisReply **element; } redisReply; redisReply a[100]; element[i]->str
-
redis实例
myredis.c
编译 gcc myredis.c
sudo find / -name “hiredis.c”
gcc /*.c -I/usr/local/include/hiredis
gcc *.c -I/usr/local/include/hiredis -lhirredis
编译后出现a.out
执行 ./a.out
| 状态表示 | 含义 |
| ------------------------ | ------------------------------------------------------------ |
| REDIS_REPLY_STRING==1 | 返回值是字符串,字符串储存在redis->str当中,字符串长度为redi |
| REDIS_REPLY_ARRAY== 2 | 返回值是数组,数组大小存在redis->elements里面,数组值存储在redis->element[i]里面。数组里面存储的是指向redisReply的指针,数组里面的返回值可以通过redis->element[i]->str来访问,数组的结果里全是type==REDIS_REPLY_STRING的redisReply对象指针。 |
| REDIS_REPLY_INTEGER == 3 | 返回整数long long,从integer字段获取值 |
| REDIS_REPLY_NIL==4 | 返回值为空表示执行结果为空 |
| REDIS_REPLY_STATUS ==5 | 返回命令执行的状态,比如set foo bar 返回的状态为OK,存储在str当中 reply->str == "OK" 。 |
| REDIS_REPLY_ERROR ==6 | 命令执行错误,错误信息存放在 reply->str当中。 |
-
释放资源
// 释放资源 void freeReplyObject(void *reply); void redisFree(redisContext *c);
复习
redis
-
是什么?
- 非关系型数据库 - nosql
- 数据存储在内存里边
- 非关系型数据库 - nosql
-
能干什么?
- 提高程序效率
- 程序中频繁访问的数据, 可以存储到redis中
-
我们需要干什么?
-
会安装
-
掌握启动redis服务器和客户端的启动命令
# 服务器 redis-server (配置文件名) # 客户端 redis-cli (-h redis服务器IP -p 端口)
-
redis中支持的数据类型 - value
-
键值对方式存储数据
- key - 字符串
- value
- 字符串 - string
- 列表 - list
- 集合 - set
- 排序集合 - sortedSet
- 哈希 - hash
-
关于服务器使用的配置文件的修改
-
redis中持久化
- rdb
- aof
-
-
-
能够在程序中操作redis服务器
- 需要使用以下函数接口
- 官方地址 - > 客户端 -> 选择语言
3、Nginx
1. 一些基本概念
1.1 Nginx初步认识
-
Nginx介绍
-
engine x
-
俄罗斯
-
开源的框架
-
c语言
-
Tengine - 淘宝基于nginx修改的
-
-
Nginx能干什么?
- 作为web服务器
- 解析http协议
- 反向代理服务器
- 了解反向代理的概念
- 邮件服务器
- 解析邮件相关的协议: pop3/smtp/imap
- 作为web服务器
-
Nginx的优势?
-
更快
- 高峰期(数以万计的并发时)nginx可以比其它web服务器更快的响应请求
-
高扩展
- 低耦合设计的模块组成,丰富的第三方模块支持
-
高可靠
- 经过大批网站检验
- www.sina.com.cn
- www.xunlei.com
- www.163.com
- 每个worker进程相对独立, 出错之后可以快速开启新的worker
- worker进程的个数是可以控制的
- 在后台干活的进程
- 经过大批网站检验
-
低内存消耗
- 一般情况下,10000个非活跃的HTTP Keep-Alive连接在nginx中仅消耗 2.5M内存
-
单机支持10万以上的并发连接
- 取决于内存,10万远未封顶
-
热部署
- master和worker的分离设计,可实现7x24小时不间断服务的前提下升级nginx可执行文件
-
最自由的BSD许可协议
- BSD许可协议允许用户免费使用nginx, 修改nginx源码,然后再发布
- 淘宝: tengine
- BSD许可协议允许用户免费使用nginx, 修改nginx源码,然后再发布
-
1.2 正向/反向代理
一般的正向代理,比如翻墙,任何可以连接到该代理服务器的软件,就可以通过代理访问任何的其他服务器,然后把数据返回给客户端,这里代理服务器只对客户端负责。而反向代理的话,如果他反向代理了两个服务,那么之后客户端访问这两个服务器的时候,该代理服务器才会给它代理,也就是说,这里的代理服务器只对该代理服务器所代理的服务器负责。这也就是为啥要起这样的名字。总结:一个对客户端负责,一个对所代理的服务器负责。一个正,一个反。
-
正向代理
正向代理是位于客户端和原始服务器之间的服务器,为了能够从原始服务器获取请求的内容,客户端需要将请求发送给代理服务器,然后再由代理服务器将请求转发给原始服务器,原始服务器接受到代理服务器的请求并处理,然后将处理好的数据转发给代理服务器,之后再由代理服务器转发发给客户端,完成整个请求过程。
正向代理的典型用途就是为在防火墙内的局域网客户端提供访问Internet的途径, 比如:
- 学校的局域网
- 单位局域网访问外部资源
正向代理服务器是为用户服务的
-
反向代理
反向代理方式是指代理原始服务器来接受来自Internet的链接请求,然后将请求转发给内部网络上的原始服务器,并将从原始服务器上得到的结果转发给Internet上请求数据的客户端。那么顾名思义,反向代理就是位于Internet和原始服务器之间的服务器,对于客户端来说就表现为一台服务器,客户端所发送的请求都是直接发送给反向代理服务器,然后由反向代理服务器统一调配。
- 客户端给服务器发送请求, 连接服务器, 用户不知道服务器地址, 只有反向代理服务器的地址是公开的
- 请求直接发给反向代理服务器
- 反向代理服务器将请求转发给后边的web服务器
- web服务器 N 台
- 反向代理服务器转发请求会轮询进行
- web服务器收到请求进行处理, 得到结果
- web服务器将处理结果发送给反向代理服务器
- 反向代理服务器将拿到的结果转发给客户端
1.3 域名和IP
- 什么是域名?
- www.baidu.com
- jd.com
- taobao.com
- 什么是IP地址?
- 点分十进制的字符串
- 11.22.34.45
- 点分十进制的字符串
- 域名和IP地址的关系?
- 域名绑定IP
- 一个域名只能绑定一个IP
- 一个IP地址被多个域名绑定
- 域名绑定IP
2. Nginx 安装和配置
2.1 安装
-
下载
- 官方地址: http://nginx.org/
- Nginx相关依赖:
- OpenSSL: http://www.openssl.org/
- 密码库
- 使用https进行通信的时候使用
- ZLib下载: http://www.zlib.net/
- 数据压缩
- 安装:
- ./configure
- make
- sudo make install
- PCRE下载: http://www.pcre.org/
- 解析正则表达式
- 安装
- tar jxvf …tar.bz2
- ./configure
- make
- sudo make install
- OpenSSL: http://www.openssl.org/
-
安装
-
nginx的安装
# nginx工作时候需要依赖三个库 # 三个参数=这三个库对应的源码安装目录 # 根据自己的电脑的库安装包的位置进行指定 tar zxvf nginx.tar.gz ./configure --with-openssl=../openssl-1.0.1t --with-pcre=../pcre-8.40 --with-zlib=../zlib-1.2.11 make sudo make install
-
3. Nginx 相关的指令
-
Nginx的默认安装目录
/usr/local/nginx conf -> 存储配置文件的目录 html -> 默认的存储网站(服务器)静态资源的目录 [图片, html, js, css] logs -> 存储log日志 sbin -> 启动nginx的可执行程序
-
Nginx可执行程序的路径
/usr/local/nginx/sbin/nginx # 快速启动的方式 # 1. 将/usr/local/nginx/sbin/添加到环境变量PATH中 # 2. /usr/local/nginx/sbin/nginx创建软连接, 放到PATH对应的路径中, 比如: /usr/bin # 建立软链接 将nginx命令添加到系统中 ln -s /usr/local/nginx/sbin/nginx /usr/bin/nginx
-
启动Nginx - 需要管理器权限
# 假设软连接已经创建完毕 sudo nginx # 启动
-
关闭Nginx
# 第一种, 马上关闭 sudo nginx -s stop # 第二种, 等nginx作为当前操作之后关闭 sudo nginx -s quit
-
重新加载Nginx
sudo nginx -s reload # 修改了nginx的配置文件之后, 需要执行该命令
-
测试是否安装成功
- 知道nginx对应的主机的IP地址 - > 192.168.1.100
- 在浏览器中访问该IP地址
- 看到一个welcom nginx的欢迎界面
2.2 配置
-
Nginx配置文件的位置
/usr/local/nginx/conf/nginx.conf
-
Nginx配置文件的组织格式
- http -> 模块, http相关的通信设置
- server模块 -> 每个server对应的是一台web服务器
- location 模块
- 处理的是客户端的请求
- location 模块
- server模块 -> 每个server对应的是一台web服务器
- mail -> 模块, 处理邮件相关的动作
-
常用配置项介绍
user nobody; # 启动之后的worker进程属于谁 - 错误提示: nginx操作xxx文件时候失败, 原因: Permission denied - 将nobody -> root worker_processes 1; # 设置worker进程的个数, 最大 == cpu的核数 (推荐) error_log logs/error.log; # 错误日志, /usr/local/nginx pid logs/nginx.pid; # pid文件, 里边是nginx的进程ID # nginx的事件处理 events { use epoll; # 多路IO转接模型使用epoll worker_connections 1024; // 每个工作的进程的最大连接数 } http->server -> 每个server模块可以看做一台web服务器 server{ listen 80; # web服务器监听的端口, http协议的默认端口 server_name localhost; # 对应一个域名, 客户端通过该域名访问服务器 charset utf8; # 字符串编码 location { // 模块, 处理客户端的请求 } # 客户端 (浏览器), 请求: http://192.168.10.100:80/login.html # 服务器处理客户端的请求 服务器要处理的指令如何从url中提取? - 去掉协议: http - 去掉IP/域名+端口: 192.168.10.100:80 - 最后如果是文件名, 去掉该名字: login.html - 剩下的: / 服务器要处理的location指令: location / { 处理动作 }
3. Nginx的使用
3.1 部署静态网页
-
静态网页存储目录
-
默认的存储目录:
/usr/local/nginx/html
-
自己创建新的目录:
应该在 /usr/local/nginx/ mkdir /usr/local/nginx/mydir
-
-
练习
在Nginx服务器上进行网页部署, 实现如下访问:
在/usr/local/nginx/创建新的目录, yundisk用来存储静态网页
-
访问地址: http://114.213.213.11/login.html
-
login.html放到什么位置?
/ -> 服务器的资源根目录, /usr/local/nginx/yundisk login.htm-> 放到yundisk中
-
服务器要处理的动作
# 对应这个请求服务器要添加一个location location 指令(/) { # 找一个静态网页 root yundisk; # 相对于/usr/local/nginx/来找 # 客户端的请求是一个目录, nginx需要找一默认显示的网页 index index.html index.htm; } # 配置之后重启nginx sudo nginx -s reload
-
-
访问地址: http://114.213.213.11/hello/reg.html
-
hello是什么?
- 目录
-
reg.html放到哪儿?
- hello目录中
-
如何添加location
location /hello/ { root yundisk; index xx.html; }
-
-
访问地址: http://114.213.213.11/upload/ 浏览器显示upload.html
-
直接访问一个目录, 得到一默认网页
-
upload是一个目录, uplaod.html应该在upload目录中
location /upload/ { root yundisk; index upload.html; }
-
-
-
3.2 反向代理和负载均衡
反向代理和负载均衡是两码事儿
准备工作:
- 需要客户端 - 1个
- Window中的浏览器作为客户端
- 反向代理服务器 -> 1个
- window作为反向代理服务器
- web服务器 -> 2个
- ubuntu - robin: 192.168.247.135
- ubuntu - luffy : 192.168.26.250
- 反向代理设置
找window上对应的nginx的配置文件
- conf/nginx.conf
# 代理几台服务器就需要几个server模块
# 客户端访问的url: http://192.168.1.100/login.html
server {
listen 80; # 客户端访问反向代理服务器, 代理服务器监听的端口
server_name ubuntu.com; # 客户端访问反向代理服务器, 需要一个域名
location / {
# 反向代理服务器转发指令, http:// 固定
proxy_pass http://robin.test.com;
}
}
# 添加一个代理模块
upstream robin.test.com
{
server 192.168.247.135:80;
}
# luffy
server {
listen 80; # 客户端访问反向代理服务器, 代理服务器监听的端口
server_name hello.com; # 客户端访问反向代理服务器, 需要一个域名
location / {
# 反向代理服务器转发指令, http:// 固定
proxy_pass http://luffy.test.com;
}
}
# 添加一个代理模块
upstream luffy.test.com
{
server 192.168.26.250:80;
}
}
- 负载均衡设置
server {
listen 80; # 客户端访问反向代理服务器, 代理服务器监听的端口
server_name localhost; # 客户端访问反向代理服务器, 需要一个域名
location / {
# 反向代理服务器转发指令, http:// 固定的头
proxy_pass http://linux.com;
}
location /hello/ {
# 反向代理服务器转发指令, http:// 固定的头
proxy_pass http://linux.com;
}
location /upload/ {
# 反向代理服务器转发指令, http:// 固定的头
proxy_pass http://linux.com;
}
}
# 添加一个代理模块
upstream linux.com
{
server 192.168.247.135:80 weight=1;
server 192.168.26.250:80 weight=3;
}
## =====================================
web服务器需要做什么?
# 192.168.247.135
location /
{
root xxx;
index xxx;
}
location /hello/
{
root xx;
index xxx;
}
location /upload/
{
root xxx;
index xx;
}
# 192.168.26.250
location /
{
root xxx;
index xxx;
}
location /hello/
{
root xx;
index xxx;
}
location /upload/
{
root xxx;
index xx;
}
课外知识导读
1. URL和URI
-
概念:
-
URL(Uniform Resource Locator): 统一资源定位符
-
表示资源位置的字符串
- 基本格式: “协议://IP地址/路径和文件名”
- ftp://ftp.is.co.za/rfc/rfc1808.txt
- http://www.ietf.org/rfc/rfc2396.txt
- telnet://192.0.2.16:80/
- 基本格式: “协议://IP地址/路径和文件名”
-
URN(Uniform Resource Name): 统一资源名称
- P2P下载中使用的磁力链接
-
URI(Uniform Resource Identifier): 统一资源标识符
- 是一个紧凑的字符串用来标示抽象或物理资源, URL是URI的一种
- 让URI能成为URL的当然就是那个“访问机制”,“网络位置”。e.g.
http://
orftp://
。- files.hp.com
- tel:+1-816-555-1212
- ftp://ftp.is.co.za/rfc/rfc1808.txt (also a URL)
- http://www.ietf.org/rfc/rfc2396.txt (also a URL)
- telnet://192.0.2.16:80/ (also a URL)
-
-
经验式理解:
从包含关系上说: URI包含URL
字符串长度上说: URL包含URI
UIRI可以没有协议, 没有地址(IP/域名)
URL | 红色字体部分+绿色字体部分 |
---|---|
URI | 绿色字体部分 |
2. DNS解析过程
-
DNS解析的过程
- 在浏览器中输入www.magedu.com域名,操作系统会先检查自己本地的hosts文件是否有这个网址映射关系,如果有,就先调用这个IP地址映射,完成域名解析。
- 如果hosts里没有这个域名的映射,则查找本地DNS解析器缓存,是否有这个网址映射关系,如果有,直接返回,完成域名解析。
- Windows和Linux系统都会在本地缓存dns解析的记录,提高速度。
- 如果hosts与本地DNS解析器缓存都没有相应的网址映射关系,首先会找
TCP/IP
参数中设置的首选DNS服务器,在此我们叫它本地DNS服务器,此服务器收到查询时,如果要查询的域名,包含在本地配置区域资源中,则返回解析结果给客户机,完成域名解析,此解析具有权威性。 - 如果要查询的域名,不由本地DNS服务器区域解析,但该DNS服务器已缓存了此网址映射关系,则调用这个IP地址映射,完成域名解析,此解析不具有权威性。
- 如果本地DNS服务器本地区域文件与缓存解析都失效,则根据本地DNS服务器的设置(没有设置转发器)进行查询,如果未用转发模式,本地DNS就把请求发至13台根DNS,根DNS服务器收到请求后会判断这个域名(.com)是谁来授权管理,并会返回一个负责该顶级域名服务器的一个IP。本地DNS服务器收到IP信息后,将会联系负责 .com域的这台服务器。这台负责 .com域的服务器收到请求后,如果自己无法解析,它就会找一个管理 .com域的下一级DNS服务器地址(magedu.com)给本地DNS服务器。当本地DNS服务器收到这个地址后,就会找magedu.com域服务器,重复上面的动作进行查询,直至找到www.magedu.com主机。
- 如果用的是转发模式(设置转发器),此DNS服务器就会把请求转发至上一级ISP DNS服务器,由上一级服务器进行解析,上一级服务器如果不能解析,或找根DNS或把转请求转至上上级,以此循环。不管是本地DNS服务器用是是转发,还是根提示,最后都是把结果返回给本地DNS服务器,由此DNS服务器再返回给客户机。
-
域名解析服务器
-
Pod DNS+:
- 首选:119.29.29.29
- 备选:182.254.116.116
-
114DNS:
- 首选:114.114.114.114
- 备选:114.114.114.115
-
阿里 AliDNS:
-
首选:223.5.5.5
-
备选:223.6.6.6
-
-
-
hosts文件
# 存储的是域名和IP的对应关系 -windows目录: "C:\Windows\System32\drivers\etc\hosts"
复习
Nginx
-
是什么?
- 开源的框架
- 库: 一套API
- 框架: (可以)有一套API, 有一套事件处理机制
- 开源的框架
-
能干什么?
- web服务器
- http协议
- 反向代理
- 实现web服务器的负载均衡
- 邮件服务器
- pop3
- web服务器
-
怎么干事儿?
-
web服务器
# 部署静态网页 1. 制作出来, 并且部署到对应的资源目录中 2. 根据客户端的请求, 在服务器端添加对应的 location处理指令 - nginx.conf 3. 重新加载nginx.conf配置文件 客户端请求的url: http://xxxx.com/hello/login.html - 去掉协议: http - 去掉域名/IP: - 去掉端口 - 去掉尾部的文件名
-
反向代理服务器
1. 找到反向代理服务器 的配置文件: nginx.conf 2. 找模块 http -> server server{ listen: 80; # 客户端访问反向代理服务器的时候使用的端口 server_name: localhost; # 域名, 客户端访问反向代理服务器时候, 使用的地址 # 配置如何转发, 根据客户端的请求的url找到对应的转发指令 location / { # 设置转发地址 proxy_pass http://test.com; } location /login { # 设置转发地址 proxy_pass http://test.com; } } # 设置代理 upstream test.com { # web服务器的地址信息 server 192.168.1.100:80; server 192.168.1.101:80; } # 192.168.1.100 web服务器 http->server server{ location / { # 设置转发地址 root xxx; } location /login { # 设置转发地址 xxxx; } } # 192.168.1.101 web服务器 http->server server{ location / { # 设置转发地址 root xxx; } location /login { # 设置转发地址 xxxx; } }
-
4、fastCGI
1. Nginx作为web服务器处理请求
-
静态请求
客户端访问服务器的静态网页, 不涉及任何数据的处理, 如下面的URL:
http://localhsot/login.html
-
动态请求
客户端会将数据提交给服务器
# 使用get方式提交数据得到的url http://localhost/login?user=zhang3&passwd=123456&age=12&sex=man - http: 协议 - localhost: 域名 - /login: 服务器端要处理的指令 - ? : 连接符, 后边的内容是客户端给服务器提交的数据 - & : 分隔符 动态的url如何找服务器端处理的指令? - 去掉协议 - 去掉域名/IP - 去掉端口 - 去掉?和它后边的内容 # 如果看到的是请求行, 如何找处理指令? POST /upload/UploadAction HTTP/1.1 GET /?username=tom&phone=123&email=hello%40qq.com&date=2018-01-01&sex=male&class=3&rule=on HTTP/1.1 1. 找请求行的第二部分 - 如果是post, 处理指令就是请求行的第二部分 - 如果是get, 处理指令就是请求行的第二部分, ? 以前的内容
2. http协议复习
-
请求消息(Request) - 客户端(浏览器)发送给服务器的数据格式
四部分: 请求行, 请求头, 空行, 请求数据
- 请求行: 说明请求类型, 要访问的资源, 以及使用的http版本
- 请求头: 说明服务器要使用的附加信息
- 空行: 空行是必须要有的, 即使没有请求数据
- 请求数据: 也叫主体, 可以添加任意的其他数据
-
Get方式提交数据
第一行: 请求行
第2-9行: 请求头(键值对)
第10行: 空行
get方式提交数据, 没有第四部分, 提交的数据在请求行的第二部分, 提交的数据会全部显示在地址栏中
GET /?username=tom&phone=123&email=hello%40qq.com&date=2018-01-01&sex=male&class=3&rule=on HTTP/1.1 Host: 192.168.26.52:6789 Connection: keep-alive Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: zh,zh-CN;q=0.9,en;q=0.8
-
Post方式提交数据
第一行: 请求行
第2 -12行: 请求头 (键值对)
第13行: 空行
第14行: 提交的数据
POST / HTTP/1.1 Host: 192.168.26.52:6789 Connection: keep-alive Content-Length: 84 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: null Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: zh,zh-CN;q=0.9,en;q=0.8 username=tom&phone=123&email=hello%40qq.com&date=2018-01-01&sex=male&class=3&rule=on
-
响应消息(Response) -> 服务器给客户端发送的数据
- 四部分: 状态行, 消息报头, 空行, 响应正文
- 状态行: 包括http协议版本号, 状态码, 状态信息
- 消息报头: 说明客户端要使用的一些附加信息
- 空行: 空行是必须要有的
- 响应正文: 服务器返回给客户端的文本信息
第一行:状态行
第2 -11行: 响应头(消息报头)
第12行: 空行
第13-18行: 服务器给客户端回复的数据
HTTP/1.1 200 Ok Server: micro_httpd Date: Fri, 18 Jul 2014 14:34:26 GMT /* 告诉浏览器发送的数据是什么类型 */ Content-Type: text/plain; charset=iso-8859-1 (必选项) /* 发送的数据的长度 */ Content-Length: 32 Location:url Content-Language: zh-CN Last-Modified: Fri, 18 Jul 2014 08:36:36 GMT Connection: close #include <stdio.h> int main(void) { printf("hello world!\n"); return 0; }
- 四部分: 状态行, 消息报头, 空行, 响应正文
-
http状态码
状态代码有三位数字组成,第一个数字定义了响应的类别,共分五种类别:
- 1xx:指示信息–表示请求已接收,继续处理
- 2xx:成功–表示请求已被成功接收、理解、接受
- 3xx:重定向–要完成请求必须进行更进一步的操作
- 4xx:客户端错误–请求有语法错误或请求无法实现
- 5xx:服务器端错误–服务器未能实现合法的请求
3. fastCGI
3.0 spawn-fcgi源码讲述
3.1 CGI
通用网关接口(Common Gateway Interface/CGI)描述了客户端和服务器程序之间传输数据的一种标准,可以让一个客户端,从网页浏览器向执行在网络服务器上的程序请求数据。CGI 独立于任何语言的,CGI 程序可以用任何脚本语言或者是完全独立编程语言实现,只要这个语言可以在这个系统上运行。
http://localhost/login?user=zhang3&passwd=123456&age=12&sex=man
- 用户通过浏览器访问服务器, 发送了一个请求, 请求的url如上
- 服务器接收数据, 对接收的数据进行解析
- nginx对于一些登录数据不知道如何处理, nginx将数据发送给了cgi程序
- 服务器端会创建一个cgi进程
- CGI进程执行
- 加载配置, 如果有需求加载配置文件获取数据
- 连接其他服务器: 比如数据库
- 逻辑处理:
- 得到结果, 将结果发送给服务器
- 退出
- 服务器将cgi处理结果发送给客户端
在服务器端CGI进程会被频繁的创建销毁
- 服务器开销大, 效率低
3.2 fastCGI
快速通用网关接口(Fast Common Gateway Interface/FastCGI)是通用网关接口(CGI)的改进,描述了客户端和服务器程序之间传输数据的一种标准。FastCGI致力于减少Web服务器与CGI程式之间互动的开销,从而使服务器可以同时处理更多的Web请求。与为每个请求创建一个新的进程不同,FastCGI使用持续的进程来处理一连串的请求。这些进程由FastCGI进程管理器管理,而不是web服务器。
fastCGI与CGI的区别:
CGI 就是所谓的短生存期应用程序,FastCGI 就是所谓的长生存期应用程序。FastCGI像是一个常驻(long-live)型的CGI,它可以一直执行着,不会每次都要花费时间去fork一次
http://localhost/login?user=zhang3&passwd=123456&age=12&sex=man
- 用户通过浏览器访问服务器, 发送了一个请求, 请求的url如上
- 服务器接收数据, 对接收的数据进行解析
- nginx对于一些登录数据不知道如何处理, nginx将数据发送给了fastcgi程序
- 通过本地套接字
- 网络通信的方式
- fastCGI程序如何启动
- 不是有web服务器直接启动
- 通过一个fastCGI进程管理器启动
- fastcgi启动
- 加载配置 - 可选
- 连接服务器 - 数据库
- 循环
- 服务器有请求 -> 处理
- 将处理结果发送给服务器
- 本地套接字
- 网络通信
- 没有请求 -> 阻塞
- 服务器将fastCGI的处理结果发送给客户端
3.3 fastCGI和spawn-fcgi安装
-
安装fastCGI
./configure make - fcgio.cpp:50:14: error: 'EOF' was not declared in this scope - 没有包含对应的头文件: - [root@localhost fcgi-2.4.1-SNAP-0910052249]# find / -name fcgio.cpp - /root/fcgi-2.4.1-SNAP-0910052249/libfcgi/fcgio.cpp - 修改fcgio.h文件 条件#include<stdio.h> - vim /root/fcgi-2.4.1-SNAP-0910052249/libfcgi/fcgio.cpp - stdio.h - c - cstdio -> c++ sudo make install
-
安装spawn-fcgi
-
安装
./configure make sudo make install
3.4 nginx && fastcgi
nginx 不能像apache那样直接执行外部可执行程序,但nginx可以作为代理服务器,将请求转发给后端服务器,这也是nginx的主要作用之一。其中nginx就支持FastCGI代理,接收客户端的请求,然后将请求转发给后端fastcgi进程。下面介绍如何使用C/C++编写cgi/fastcgi,并部署到nginx中。
通过前面的介绍知道,fastcgi进程由FastCGI进程管理器管理,而不是nginx。这样就需要一个FastCGI管理,管理我们编写fastcgi程序。我们使用spawn-fcgi作为FastCGI进程管理器。
spawn-fcgi是一个通用的FastCGI进程管理器,简单小巧,原先是属于lighttpd的一部分,后来由于使用比较广泛,所以就迁移出来作为独立项目了。spawn-fcgi使用pre-fork 模型,功能主要是打开监听端口,绑定地址,然后fork-and-exec创建我们编写的fastcgi应用程序进程,退出完成工作。fastcgi应用程序初始化,然后进入死循环侦听socket的连接请求。
http://localhost/login?user=zhang3&passwd=123456&age=12&sex=man
- 客户端访问, 发送请求
- nginx web服务器, 无法处理用户提交的数据
- spawn-fcgi - 通信过程中的服务器角色
- 被动接收数据
- 在spawn-fcgi启动的时候给其绑定IP和端口
- fastCGI程序
- 程序猿写的 -> login.c -> 可执行程序( login )
- 使用 spawn-fcgi 进程管理器启动 login 程序, 得到一进程
-
nginx的数据转发 - 需要修改nginx的配置文件 nginx.conf
通过请求的url http://localhost/login?user=zhang3&passwd=123456&age=12&sex=man 转换为一个指令: - 去掉协议 - 去掉域名/IP + 端口 - 如果尾部有文件名 去掉 - 去掉 ? + 后边的字符串 - 剩下的就是服务器要处理的指令: /login location /login { # 转发这个数据, fastCGI进程 fastcgi_pass 地址信息:端口; # fastcgi.conf 和nginx.conf在同一级目录: /usr/local/nginx/conf # 这个文件中定义了一些http通信的时候用到环境变量, nginx赋值的 include fastcgi.conf; } 地址信息: - localhost - 127.0.0.1 - 192.168.1.100 端口: 找一个空闲的没有被占用的端口即可
-
spawn-fcgi如何启动
# 前提条件: 程序猿的fastCGI程序已经编写完毕 -> 可执行文件 login spawn-fcgi -a IP地址 -p 端口 -f fastcgi可执行程序 - IP地址: 应该和nginx的 fastcgi_pass 配置项对应 - nginx: localhost -> IP: 127.0.0.1 - nginx: 127.0.0.1 -> IP: 127.0.0.1 - nginx: 192.168.1.100 -> IP: 192.168.1.100 - 端口: 应该和nginx的 fastcgi_pass 中的端口一致
-
fastCGI程序怎么写
// http://localhost/login?user=zhang3&passwd=123456&age=12&sex=man // 要包含的头文件 #include "fcgi_config.h" // 可选 #include "fcgi_stdio.h" // 必须的, 编译的时候找不到这个头文件, find->path , gcc -I // 编写代码的流程 int main() { // FCGI_Accept()是一个阻塞函数, nginx给fastcgi程序发送数据的时候解除阻塞 while (FCGI_Accept() >= 0) { // 1. 接收数据 // 1.1 get方式提交数据 - 数据在请求行的第二部分 // user=zhang3&passwd=123456&age=12&sex=man char *text = getenv("QUERY_STRING"); // 1.2 post方式提交数据 char *contentLength = getenv("CONTENT_LENGTH"); // 根据长度大小判断是否需要循环 // 2. 按照业务流程进行处理 // 3. 将处理结果发送给nginx // 数据回发的时候, 需要告诉nginx处理结果的格式 - 假设是html格式 printf("Content-type: text/html\r\n"); printf("<html>处理结果</html>"); } }
demo.html 网页的上传路径要更改!!!!
url : "/Myupload", // 上传文件的路径
4.fastCGI程序的启动
- 1 echo.c文件
将/home/misterlanbing/shareFile/05_FastCGI/fcgi-2.4.1-SNAP-0910052249/examples下的echo.c文件复制到工作文件夹 - 2 gcc echo.c -o app -lfcgi 进行编译生成 app程序
- 3 ldd app 查看加载程序的动态库
如果有动态库没有找到( not found)
sudo find / -name “libfcgi.so” 搜索相应动态库位置
vi /etc/ld.so.conf 编辑配置文件
将刚刚查找的 /usr/local/lib 添加进去
sudo ldconfig重新加载配置文件
ldd app 动态库添加成功
- spawn-fcgi -a 127.0.0.1 -p 10000 -f ./app 启动spawn-fcgi
spawn-fcgi -a IP地址 -p 端口 -f fastcgi可执行程序
- IP地址: 应该和nginx的fastcgi_pass配置项对应
- nginx: localhost -> IP: 127.0.0.1
- nginx: 127.0.0.1 -> IP: 127.0.0.1
- nginx: 192.168.1.100 -> IP: 192.168.1.100
- 端口:
应该和nginx的fastcgi_pass中的端口一致
复习
-
fastCGI
-
是什么?
- 运行在服务器端的代码, 帮助服务器处理客户端提交的动态请求
-
干什么
- 帮助服务器处理客户端提交的动态请求
-
怎么用?
-
nginx如何转发数据
# 分析出客户端请求对应的指令 -- /test location /test { # 转发出去 fastcgi_pass 地址:端口; include fastcgi.conf; }
-
fastcgi如何接收数据
# 启动, 通过spawn-fcgi启动 spawn-fcgi -a IP -p port -f ./fcgi # 编写fastCGI程序的时候 - 接收数据: 调用读终端的函数就是接收数据 - 发送数据: 调用写终端的函数就是发送数据
-
fastcgi如何处理数据
// 编写登录的fastCgI程序 int main() { while(FCGI_Accept() >= 0) { // 1. 接收登录信息 -> 环境变量中 // post -> 读数据块的长度 CONTENT-LENGTH // get -> 从请求行的第二部分读 QUEERY_STRING // 2. 处理数据 // 3. 回发结果 -> 格式假设是json printf("Content-type: application/json"); printf("{\"status\":\"OK\"}") } }
-
-
5、nginx+fastDFS
1. 文件上传下载流程
1 上传
2 下载
3. 优化
优化思路:
直接让客户端连接fastDFS的存储节点, 实现文件下载
举例, 访问一个url直接下载:
http://192.168.247.147/group1/M00/00/00/wKj3k1tMBKuARhwBAAvea_OGt2M471.jpg
客户端发送请求使用的协议: http
fastDFS能不能解析http协议
nginx能解析http协议
- 在nginx中安装fastDFS的插件
客户端怎么知道文件就存储在对应的那个存储节点上?
- 上传的时候将fileID和存储节点IP地址都进行存储
2. Nginx和fastDFS的整合
- 在存储节点上安装Nginx, 将软件安装包拷贝到fastDFS存储节点对应的主机上
- 找fastDFS的存储节点
2. 在存储节点对应的主机上安装Nginx, 安装的时候需要一并将插件装上
- (余庆提供插件的代码 + nginx的源代码 ) * 交叉编译 = Nginx
-
在存储节点对应的主机上安装Nginx和fastdfs-nginx插件, 使其变为作为直接相应请求的web服务器
- fastdfs-nginx-module_v1.16.tar.gz 解压缩 # 1. 进入nginx的源码安装目录 # 2. 检测环境, 生成makefile ./configure --add-module=fastdfs-nginx插件的源码目录/src #有openssl依赖 要加上 #./configure --add-module=/home/misterlanbing/shareFile/06_fastdfs_nginx_module/fastdfs-nginx-module/src --with-openssl=../openssl-1.0.1t --with-pcre=../pcre-8.40 --with-zlib=../zlib-1.2.11 make sudo make install
make过程中的错误:
# 1. fatal error: fdfs_define.h: No such file or directory # 2. fatal error: common_define.h: No such file or directory default: build clean: rm -rf Makefile objs build: $(MAKE) -f objs/Makefile install: $(MAKE) -f objs/Makefile install modules: $(MAKE) -f objs/Makefile modules upgrade: /usr/local/nginx/sbin/nginx -t kill -USR2 `cat /usr/local/nginx/logs/nginx.pid` sleep 1 test -f /usr/local/nginx/logs/nginx.pid.oldbin kill -QUIT `cat /usr/local/nginx/logs/nginx.pid.oldbin` # 解决方案 - 修改objs/Makefile ALL_INCS = -I src/core \ -I src/event \ -I src/event/modules \ -I src/os/unix \ -I /usr/local/include/fastdfs \ -I /usr/local/include/fastcommon/ \ -I objs \ -I src/http \ -I src/http/modules\ -I /usr/include/fastdfs/
-
安装成功, 启动Nginx, 发现没有 worker进程
robin@OS:/usr/local/nginx/sbin$ ps aux|grep nginx root 65111 0.0 0.0 39200 696 ?Ss 10:32 0:00 nginx: master process ./nginx robin 65114 0.0 0.0 16272 928 pts/9 S+ 10:32 0:00 grep --color=auto nginx
查找找nginx的logs目录下的error.log日志
# ERROR - file: shared_func.c, line: 968, file /etc/fdfs/mod_fastdfs.conf not exist # 从哪儿找 -> fastDFS插件目录中找 robin@OS:~/package/nginx/fastdfs-nginx-module/src$ tree . ├── common.c ├── common.h ├── config ├── mod_fastdfs.conf -> cp /etc/fdfs └── ngx_http_fastdfs_module.c 0 directories, 5 files
需要修改mod_fdfs.conf文件, 参数当前存储节点的storage.conf进行修改
# 存储log日志的目录 base_path=/home/robin/fastdfs/storage # 连接tracker地址信息 tracker_server=192.168.247.135:22122 # 存储节点绑定的端口 storage_server_port=23000 # 当前存储节点所属的组 group_name=group1 # 客户端下载文件的时候, 这个下载的url中是不是包含组的名字 // 上传的fileID: group1/M00/00/00/wKj3h1vJRPeAA9KEAAAIZMjR0rI076.cpp // 完整的url: http://192.168.1.100/group1/M00/00/00/wKj3h1vJRPeAA9KEAAAIZMjR0rI076.cpp url_have_group_name = true # 存储节点上存储路径的个数 store_path_count=1 # 存储路径的详细信息 store_path0=/home/robin/fastdfs/storage //只有一个组 下面配置这个组的信息 group_count = 1 #group settings for group #1 #since v1.14 #when support multi-group, uncomment following section [group1] group_name=group1 storage_server_port=23000 store_path_count=1 store_path0=/home/zbc/Yunpan/fastdfs/storage #store_path1=/home/yuqing/fastdfs1
-
sudo nginx 重写启动Nginx, 还是没有worker进程, 查看nginx的logs目录下的error.log日志
# ERROR - file: ini_file_reader.c, line: 631, include file "http.conf" not exists, line: "#include http.conf" 从 /etc/fdfs 下找的时候不存在 - 从fastDFS源码安装目录找/conf - sudo cp http.conf /etc/fdfs # ERROR - file: shared_func.c, line: 968, file /etc/fdfs/mime.types not exist - 从nginx的源码安装包中找/conf - sudo cp mime.types /etc/fdfs
-
通过浏览器请求服务器下载文件: 404 Not Found
http://192.168.1.100/group1/M00/00/00/wKj3h1vJRPeAA9KEAAAIZMjR0rI076.jpg # 错误信息 open() "/usr/local/nginx/zyFile2/group1/M00/00/00/wKj3h1vJSOqAM6RHAAvqH_kipG8229.jpg" failed (2: No such file or directory), client: 192.168.247.1, server: localhost, request: "GET /group1/M00/00/00/wKj3h1vJSOqAM6RHAAvqH_kipG8229.jpg HTTP/1.1", host: "192.168.247.135" 服务器在查找资源时候, 找的位置不对, 需要给服务器指定一个正确的位置, 如何指定? - 资源在哪? 在存储节点的存储目录中 store_path0 - 如何告诉服务器资源在这? 在服务器端添加location处理 locatioin /group1/M00/00/00/wKj3h1vJSOqAM6RHAAvqH_kipG8229.jpg location /group1/M00/00/00/ location /group1/M00/ location /group1/M00/ { # 告诉服务器资源的位置 root /home/robin/fastdfs/storage/data; ngx_fastdfs_module; }
3. 数据库表
mysql 数据库登录: mysql -u root -p
密码设置为root
3.1 数据库操作
-
创建一个名称为cloud_disk的数据库
CREATE DATABASE cloud_disk;
-
删除数据库cloud_disk
drop database cloud_disk;
-
使用数据库 cloud_disk
use cloud_disk;
3.2 数据库建表
-
用户信息表 – user
字段 解释 id 用户序号,自动递增,主键 name 用户名字 nickname 用户昵称 phone 手机号码 email 邮箱 password 密码 createtime 时间 CREATE TABLE user ( id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, name VARCHAR (128) NOT NULL, nickname VARCHAR (128) NOT NULL, password VARCHAR (128) NOT NULL, phone VARCHAR (15) NOT NULL, createtime VARCHAR (128), email VARCHAR (100), CONSTRAINT uq_nickname UNIQUE (nickname), CONSTRAINT uq_name UNIQUE (NAME) );
-
文件信息表 - file_info
字段 解释 md5 文件md5, 识别文件的唯一表示(身份证号) file_id 文件id-/group1/M00/00/00/xxx.png url 文件url 192.168.1.2:80/group1/M00/00/00/xxx.png - 下载的时候使用 size 文件大小, 以字节为单位 type 文件类型: png, zip, mp4…… fileName 文件名 count 文件引用计数, 默认为1 每增加一个用户拥有此文件,此计数器+1 CREATE TABLE file_info( md5 VARCHAR (200) NOT NULL, file_id VARCHAR (128), url VARCHAR (128), id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, size INT, type VARCHAR (128), filename VARCHAR (128), count INT );
-
用户文件列表 - user_file_list
字段 解释 user 文件所属用户 md5 文件md5 createtime 文件创建时间 filename 文件名字 shared_status 共享状态, 0为没有共享, 1为共享 pv 文件下载量,默认值为0,下载一次加1 CREATE TABLE user_file_list ( user VARCHAR (128) NOT NULL, md5 VARCHAR (200) NOT NULL, createtime VARCHAR (128), filename VARCHAR (128), shared_status INT, pv INT );
-
用户文件数量表 - user_file_count
字段 解释 user 文件所属用户 count 拥有文件的数量 CREATE TABLE user_file_count ( user VARCHAR (128) NOT NULL PRIMARY KEY, count INT );
-
共享文件列表 - share_file_list
字段 解释 user 文件所属用户 md5 文件md5 createtime 文件共享时间 filename 文件名字 pv 文件下载量,默认值为1,下载一次加1 CREATE TABLE share_file_list ( user VARCHAR (128) NOT NULL, md5 VARCHAR (200) NOT NULL, createtime VARCHAR (128), filename VARCHAR (128), pv INT );
数据库操作
1、启动数据库
4、cloud-disk
4.1cloud-disk安装
tar -xzvf cloud-disk.tar.gz
6、登录注册
1. 登录和注册协议
1.1 注册协议
-
客户端
# URL http://192.168.1.100:80/reg # post数据格式 { userName:xxxx, nickName:xxx, firstPwd:xxx, phone:xxx, email:xxx }
-
服务器端 - Nginx
-
服务器端的配置
location /reg { # 转发数据 fastcgi_pass localhost:10000; include fastcgi.conf; }
-
编写fastcgi程序
int main() { while(FCGI_Accept() >= 0) { // 1. 根据content-length得到post数据块的长度 // 2. 根据长度将post数据块读到内存 // 3. 解析json对象, 得到用户名, 密码, 昵称, 邮箱, 手机号 // 4. 连接数据库 - mysql, oracle // 5. 查询, 看有没有用户名, 昵称冲突 -> {"code":"003"} // 6. 有冲突 - 注册失败, 通知客户端 // 7. 没有冲突 - 用户数据插入到数据库中 // 8. 成功-> 通知客户端 -> {"code":"002"} // 9. 通知客户端回传的字符串的格式 printf("content-type: application/json\r\n"); printf("{\"code\":\"002\"}"); } }
-
服务器回复的数据格式:
成功 {“code”:“002”} 该用户已存在 {“code”:“003”} 失败 {“code”:“004”}
-
1.2 登录协议
-
客户端
#URL http://127.0.0.1:80/login # post数据格式 { user:xxxx, pwd:xxx }
-
服务器端
-
Nginx服务器配置
location /login { # 转发数据 fastcgi_pass localhost:10001; include fastcgi.conf; }
-
服务器回复数据格式
// 成功 { "code": "000", "token": "xxx" } // 失败 { "code": "001", "token": "faild" }
-
2. 单例模式
-
单例模式的优点:
- 在内存中只有一个对象, 节省内存空间
- 避免频繁的创建销毁对象,可以提高性能
- 避免对共享资源的多重占用
- 可以全局访问
(工厂模式就是频繁的创建对象)
-
单例模式的适用场景:
-
需要频繁实例化然后销毁的对象
-
创建对象耗时过多或者耗资源过多,但又经常用到的对象
struct More { int number; ...(100) }
-
有状态的工具类对象
-
频繁访问数据库或文件的对象
-
要求只有一个对象的场景
-
-
如何保证单例对象只有一个?
// 在类外部不允许进行new操作 class Test { public: // 1. 默认构造 // 2. 默认析构 // 3. 默认的拷贝构造 } // 1. 构造函数私有化 // 2. 拷贝构造私有化
-
单例模式实现方式?
-
懒汉模式 - 单例对象在使用的时候被创建出来, 线程安全问题需要考虑
class Test { public: static Test* getInstance() { if(m_test == NULL) { m_test = new Test(); } return m_test; } private: Test(); Test(const Test& t); // 静态变量使用之前必须初始化 static Test* m_test; } Test* Test::m_test = NULL; // 初始化
-
饿汉模式 - 单例对象在使用之前被创建出来
class Test { public: static Test* getInstance() { return m_test; } private: Test(); Test(const Test& t); // 静态变量使用之前必须初始化 static Test* m_test; } Test* Test::m_test = new Test(); // 初始化
-
-
QRegExp类
QRegExp::QRegExp(); QRegExp::QRegExp(const QString &pattern, Qt::CaseSensitivity cs = Qt::CaseSensitive, PatternSyntax syntax = RegExp) - pattern: 正则表达式, 该对象继续数据校验的规则 bool QRegExp::exactMatch(const QString &str) const - str: 被校验的字符串 - 返回值: 匹配成功: true, 失败:false // 重新给正则对象指定匹配规则 void QRegExp::setPattern(const QString &pattern) - pattern: 正则表达式
-
鼠标拖动窗口移动, 左上角坐标求解方法:
在鼠标按下还没有移动的时候求差值
差值 = 鼠标当前位置 - 屏幕左上角的点
鼠标移动过程中
屏幕左上角的点 = 鼠标当前位置 - 差值
-
QSS参考资料
- <https://blog.csdn.net/liang19890820/article/details/51691212
-
通过样式函数给控件设置样式
void setStyleSheet(const QString &styleSheet) - 参数styleSheet: 样式字符串, css格式 - 在QT中参照帮助文档也可以
- sourceInsight
// 刷新窗口
// 什么时候被回调?
// 1. 窗口第一次现实的时候
// 2. 窗口被覆盖, 又重新显示
// 3. 最大化, 最小化
// 4. 手动重绘 - > 调用一个api : [slot] void QWidget::update()
// 函数内部写的绘图动作 -> QPainter
[virtual protected] void QWidget::paintEvent(QPaintEvent *event);
- QPainter(QPaintDevice *device) -> 参数应该this
// 这个点是窗口左上角坐标
void move(int x, int y);
void move(const QPoint &);
Qt中使用正则表达式进行数据校验:
// 使用的类: QRegExp
// 1. 构造对象
QRegExp::QRegExp();
QRegExp::QRegExp(const QString &pattern, Qt::CaseSensitivity cs = Qt::CaseSensitive, PatternSyntax syntax = RegExp);
- pattern: 正则表达式
// 2. 如何使用正则对象进行数据校验
bool QRegExp::exactMatch(const QString &str) const;
- 参数str: 要校验的字符串
- 返回值: 匹配成功: true, 失败: false
// 3. 给正则对象指定匹配规则或者更换匹配规则
void QRegExp::setPattern(const QString &pattern);
- 参数pattern: 新的正则表达式
Qt中处理json
// QJsonDocument
// 1. 将字符串-> json对象/数组; 2. json对象,数组 -> 格式化为字符串
// QJsonObject -> 处理json对象的 {}
// QJsonArray -> 处理json数组 []
// QJsonValue -> 包装数据的, 字符串, 布尔, 整形, 浮点型, json对象, json数组
- 内存中的json数据 -> 写磁盘
- 磁盘中的json字符串 -> 内存
7、样式
1. 单例模式
-
单例模式实现方式?
-
懒汉模式 - 单例对象在使用的时候被创建出来, 线程安全问题需要考虑
class Test { public: static Test* getInstance(); private: Test(); Test(const Test& t); // 静态变量使用之前必须初始化 static Test* m_test; } Test* Test::m_test = NULL; // 初始化 // 第一种实现方式 Test::Test* getInstance() { if(m_test == NULL) { m_test = new Test(); } return m_test; } // 弊端: 有线程安全问题, 会创建多个对象, 每个线程创建一个 // 解决方案: 线程同步, 加锁 // 第2种实现方式 // 假设在c++中有一个mutex对象, lock, unlock Test::Test* getInstance() { mutex.lock(); // 加锁 if(m_test == NULL) { m_test = new Test(); } mutex.unlock(); // 解锁 return m_test; } //例如第一次来三个线程,每一个都需要堵塞在锁上排队,等待上一个解锁,效率提高 // 弊端: 效率很低, 每个线程得到单例对象是, 线性执行的 // 第3种实现方式 // 假设在c++中有一个mutex对象, lock, unlock Test::Test* getInstance() { if(m_test == NULL) { mutex.lock(); // 加锁 if(m_test == NULL) { m_test = new Test(); } mutex.unlock(); // 解锁 } return m_test; } //例如第一次来三个线程,每一个都需要堵塞在锁上排队,等待上一个解锁,第二次来了三个线程,就会同时并行执行 // 弊端: 第一次获取单例对象的时候, 线程是线性执行的, 第二次以后是并行的 // 第四种 : 要求编译器支持c++11 class Test { public: static Test* getInstance(); private: Test(); Test(const Test& t); } Test::Test* getInstance() { static Test test;//静态局部变量 生存周期与主体程序一样 //第一次调用时创建,之后再调用直接返回地址 return &test; }
-
饿汉模式 - 单例对象在使用之前被创建出来
class Test { public: static Test* getInstance() { return &m_test; } private: Test(); Test(const Test& t); // 静态变量使用之前必须初始化 // static Test* m_test; static Test m_test; } // Test* Test::m_test = new Test(); // 初始化 Test Test::m_test;
-
-
如何在单例类中存储数据?
// 实现了一个单例模式的类 // 存储用户名/密码/服务器的iP/端口 class Test { public: static Test* getInstance() { return &m_test; } // 设置数据 void setUserName(QString name) { // 多线程-> 加锁 m_user = name; // 解锁 } // 获取数据 QString getUserName() { return m_user; } private: Test(); Test(const Test& t); // 静态变量使用之前必须初始化 // static Test* m_test; static Test m_test; // 定义变量 -> 属于唯一的单例对象 QString m_user; QString m_passwd; QString m_ip; QString m_port; QString m_token; } // Test* Test::m_test = new Test(); // 初始化 Test Test::m_test;
在客户端登录的时候, 服务器回复给客户端的数据
// 成功 { "code": "000", "token": "xxx" } // 失败 { "code": "001", "token": "faild" } token -> 客户端成功连接了服务器, 服务器针对于客户端的个人信息生成了一个唯一的身份标识 - 可以按照每个人的身份证号理解 - 服务器将这个token发送给客户端 - 客户端token的使用和保存: - 使用: 登录成功之后, 向服务器在发送任意请求都需要携带该token值 - 保存方式: 放到单例对象中 - 服务器端的使用和保存: - 使用: 接收客户端发送的token, 和服务器端保存的token进行认证 - 认证成功: 合法客户端, 失败: 客户端非法 - 保存: 服务器需要保存所有客户端的token - 数据库中 - 配置文件 -> 效率低 - redis中 -> 效率最高 (客户端信息+随机数)*des*md5*base64
1. QSS样式表
1.1 选择器类型
选择器 | 示例 | 说明 |
---|---|---|
通用选择器 | * | 匹配所有部件 匹配当前窗口所有的子窗口 |
类型选择器 | QWidget | 匹配QWidget及其子类窗口的实例 |
类选择器 | .QPushButton | 匹配QPushButton的实例,但不包含子类。相当于*[class~=”QPushButton”]。 |
ID选择器 | QPushButton#okButton | 匹配所有objectName为okButton的QPushButton实例。 |
后代选择器 | QDialog QPushButton | 匹配属于QDialog后代(孩子,孙子等)的QPushButton所有实例。 |
子选择器 | QDialog >QPushButton | 匹配属于QDialog直接子类的QPushButton所有实例。 |
1.2 QSS的使用步骤
// QSS是一个文件, 样式表文件(CSS文件)
// - Qt样式表支持css2.0, 1.0 所有的语法, css3.0部分样式在qt中不支持
// 如何使用
/*
1. 根据介绍的选择器对所有的控件样式设置, 写入qss文件中
2. 在程序中读样式表文件, 得到一个字符串 -> 样式字符串
3. 将读出的样式设置给QT的应用程序对象
4. 在qt中有一个全局的应用程序指针qApp
5. qApp->setStyleSheet("样式字符串");
6. QFile读磁盘文件, 磁盘文件的编码格式必须是utf8
*/
1.3 登录窗口设置
/* 登录窗口设置背景图片 */
/* 登录窗口所有控件设置字体, 字体大小 */
/* 设置登录/注册/服务器设置窗口标题字体, 字体大小 */
/* 设置logo显示的图片 */
/* 设置窗口标题字体, 字体大小, 加粗 */
/* 没有账号马上注册按钮: 字体颜色和添加下划线 */
/* 登录/注册/OK按钮: 字体颜色, 宽度, 高度, 字体大小, 显示图片 */
/* 标题栏按钮: normal, hover, press三种状态切换 */
/* 按钮的默认状态 */
QPushButton#loginBtn
{
color: white;
width: 200;
height: 50;
font-size: 30px;
border-image: url(:/images/balckButton.png); /* 默认显示的图片 */
}
/* 按钮的悬停状态 */
QPushButton#loginBtn:hover
{
border-image: url(:/images/balckButton1.png); /* 默认显示的图片 */
}
/* 按钮的按下状态 不是css中的标准状态, qt独有的 */
QPushButton#loginBtn:pressed
{
border-image: url(:/images/balckButton2.png); /* 默认显示的图片 */
}
2. 客户端post方式上传数据
HTTP 请求分为四个部分:状态行、请求头、空行消息、主体
协议规定 POST
提交的数据必须放在消息主体 (entity-body)中,但协议并没有规定数据必须使用什么编码方式。
开发者完全可以自己决定消息主体的格式数据发送出去,还要服务端解析成功才有意义,服务端通常是根据请求头(headers)中的 Content-Type
字段来获知请求中的消息主体是用何种方式偏码,再对主体进行解析。
2.1 常用的四种方式
-
application/x-www-form-urlencoded
# 请求行 POST http://www.example.com HTTP/1.1 # 请求头 Content-Type: application/x-www-form-urlencoded;charset=utf-8 # 空行 # 请求数据(向服务器提交的数据) title=test&user=kevin&passwd=32222
-
application/json
POST http://www.example.com HTTP/1.1 Content-Type: application/json;charset=utf-8 {"title":"test","sub":[1,2,3]}
-
text/xml
POST http://www.example.com HTTP/1.1 Content-Type: text/xml <?xml version="1.0" encoding="utf8"?> <methodcall> <methodname color="red">examples.getStateName</methodname> <params> <value><i4>41</i4></value> </params> </methodcall>
-
multipart/form-data
tool.oschina.net
POST http://www.example.com HTTP/1.1 Content-Type: multipart/form-data # 发送的数据 ------WebKitFormBoundaryPpL3BfPQ4cHShsBz \r\n Content-Disposition: form-data; name="file"; filename="qw.png"; md5="xxxxxxxxxx" Content-Type: image/png\r\n; \r\n .............文件内容................ .............文件内容................ ------WebKitFormBoundaryPpL3BfPQ4cHShsBz Content-Disposition: form-data; name="file"; filename="qw.png"; md5="xxxxxxxxxx" Content-Type: image/png\r\n; \r\n .............文件内容................ .............文件内容................ ------WebKitFormBoundaryPpL3BfPQ4cHShsBz--
3. 上传协议
文件上传的一般步骤:
- 尝试秒传 -> 文件并没上传
- 给服务器发送的不是文件内容, 是文件的哈希值
- 在服务器端收到哈希值, 查询数据库
- 查到了 -> 秒传成功
- 没查到 -> 秒传失败, 需要进行一个真正的上传操作
- 进行真正的上传
- 需要的时间长
- 上传有文件内容, 文件的哈希值
- 文件内容 -> 分布式文件系统
- 哈希值 -> 数据库
-
秒传
-
客户端
# url http://127.0.0.1:80/md5 # post数据格式 { user:xxxx, token:xxxx, md5:xxx, fileName: xxx }
-
服务器
location /md5 { # 转发数据 fastcgi_pass localhost:10002; include fastcgi.conf; }
文件已存在(秒传成功): {“code”:“005”} 秒传成功: {“code”:“006”} 秒传失败: {“code”:“007”}
fastCGI程序编写
int main() { while(FCGI_Accept() >= 0) { // 1. 得到post数据的长度 char* length = getenv("content-length"); int len = atoi(length); // 2. 根据len将数据读到内存中, json对象字符串 // 3. 解析json对象, user,md5, token, fileName // 4. token认证 , 查询redis/数据库 // -- 成功: 继续后续操作, 失败, 返回, 给客户端一个结果 // 5. 打开数据库, 并查询md5是否存在 // -- 存在 {"code":"006"} // -- 不存在 {"code":"007"} } }
-
-
上传
-
客户端
# url http://127.0.0.1:80/upload # post数据格式 ------WebKitFormBoundary88asdgewtgewx\r\n Content-Disposition: form-data; user="mike"; filename="xxx.jpg"; md5="xxxx"; size=10240 Content-Type: image/jpg 真正的文件内容 ------WebKitFormBoundary88asdgewtgewx--
-
Qt中如何组织上述post数据块
// 组织数据块 - > QHttpPart QHttpPart::QHttpPart(); // 设置数据头 void QHttpPart::setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value); - header: - QNetworkRequest::ContentDispositionHeader - QNetworkRequest::ContentTypeHeader - value: "form-data; 自定义的数据, 格式 xxx=xxx, 中间以;间隔" // 适合传递少量的数据 void QHttpPart::setBody(const QByteArray &body); - body: 传递的数据串 // 传递大文件 void QHttpPart::setBodyDevice(QIODevice *device); - 使用参数device, 打开一个磁盘文件 // QHttpMulitPart QHttpMultiPart::QHttpMultiPart(ContentType contentType, QObject *parent = Q_NULLPTR); - 参数contentType: QHttpMultiPart::FormDataType // 调用该函数会自动添加分界线 -> 使用频率高的函数 void QHttpMultiPart::append(const QHttpPart &httpPart); // 查看添加的分界线的值 QByteArray QHttpMultiPart::boundary() const; // 自己设置分界线, 一般不需要自己设置 void QHttpMultiPart::setBoundary(const QByteArray &boundary); // 使用post方式发送数据 QNetworkReply *QNetworkAccessManager::post(const QNetworkRequest &request, QHttpMultiPart *multiPart);
-
服务器
location /upload { # 转发数据 fastcgi_pass localhost:10003; include fastcgi.conf; }
// fastCGI程序 int main() { // 1. 获取数据长度 1024000 // 2. 循环读post数据内容 }
成功 {“code”:“008”} 失败 {“code”:“009”} -
服务器端fastCGI 部分 代码
// 取出 Content-Disposition 中的键值对的值, 并得到文件内容, 并将内容写入文件 int recv_save_file(char *user, char *filename, char *md5, long *p_size) { int ret = 0; char *file_buf = NULL; char *begin = NULL; char *p, *q, *k; char content_text[512] = {0}; //文件头部信息 char boundary[512] = {0}; //分界线信息 //==========> 开辟存放文件的 内存 <=========== file_buf = (char *)malloc(4096); if (file_buf == NULL) { return -1; } //从标准输入(web服务器)读取内容 int len = fread(file_buf, 1, 4096, stdin); if(len == 0) { ret = -1; free(file_buf); return ret; } //===========> 开始处理前端发送过来的post数据格式 <============ begin = file_buf; //内存起点 p = begin; /* ------WebKitFormBoundary88asdgewtgewx\r\n Content-Disposition: form-data; user="mike"; filename="xxx.jpg"; md5="xxxx"; size=10240\r\n Content-Type: application/octet-stream\r\n ------WebKitFormBoundary88asdgewtgewx-- */ //get boundary 得到分界线, ------WebKitFormBoundary88asdgewtgewx p = strstr(begin, "\r\n"); if (p == NULL) { ret = -1; free(file_buf); return ret; } //拷贝分界线 strncpy(boundary, begin, p-begin); boundary[p-begin] = '\0'; //字符串结束符 p += 2; //\r\n //已经处理了p-begin的长度 len -= (p-begin); //get content text head begin = p; //Content-Disposition: form-data; user="mike"; filename="xxx.jpg"; md5="xxxx"; size=10240\r\n p = strstr(begin, "\r\n"); if(p == NULL) { ret = -1; free(file_buf); return ret; } strncpy(content_text, begin, p-begin); content_text[p-begin] = '\0'; p += 2;//\r\n len -= (p-begin); //========================================获取文件上传者 //Content-Disposition: form-data; user="mike"; filename="xxx.jpg"; md5="xxxx"; size=10240\r\n q = begin; q = strstr(begin, "user="); q += strlen("user="); q++; //跳过第一个" k = strchr(q, '"'); strncpy(user, q, k-q); //拷贝用户名 user[k-q] = '\0'; //========================================获取文件名字 //"; filename="xxx.jpg"; md5="xxxx"; size=10240\r\n begin = k; q = begin; q = strstr(begin, "filename="); q += strlen("filename="); q++; //跳过第一个" k = strchr(q, '"'); strncpy(filename, q, k-q); //拷贝文件名 filename[k-q] = '\0'; //========================================获取文件MD5码 //"; md5="xxxx"; size=10240\r\n begin = k; q = begin; q = strstr(begin, "md5="); q += strlen("md5="); q++; //跳过第一个" k = strchr(q, '"'); strncpy(md5, q, k-q); //拷贝文件名 md5[k-q] = '\0'; //========================================获取文件大小 //"; size=10240\r\n begin = k; q = begin; q = strstr(begin, "size="); q += strlen("size="); k = strstr(q, "\r\n"); char tmp[256] = {0}; strncpy(tmp, q, k-q); //内容 tmp[k-q] = '\0'; *p_size = strtol(tmp, NULL, 10); //字符串转long begin = p; p = strstr(begin, "\r\n"); p += 2; //\r\n len -= (p-begin); //下面才是文件的真正内容 /* ------WebKitFormBoundary88asdgewtgewx\r\n Content-Disposition: form-data; user="mike"; filename="xxx.jpg"; md5="xxxx"; size=10240\r\n Content-Type: application/octet-stream\r\n 真正的文件内容\r\n ------WebKitFormBoundary88asdgewtgewx-- */ // begin指向正文首地址 begin = p; // 将文件内容抠出来 // 文件内容写如本地磁盘文件 free(file_buf); return ret; }
-
使用fastCGI管理器启动fastCGI程序
spawn-fcgi -a IP地址 -p 端口 -f ./fastcgi程序 - 提示启动失败 - ldd fastCGI程序
-
4. Http上传下载进度
// QNetworkReply - 信号
void QNetworkReply::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
- bytesReceived: 已经接收的字节数
- bytesTotal: 要接收的总字节数
void QNetworkReply::uploadProgress(qint64 bytesSent, qint64 bytesTotal)
- bytesSent: 已经发送的字节数
- bytesTotal: 要发送的总字节数
5. 上传大文件Nginx设置
-
413 错误
服务器提示:413 Request Entity Too Large 的解决方法
-
原因: 上传文件太大, 请求实体太长了
-
解决方案:
- 在配置文件nginx.conf中添加: client_max_body_size 10M
- 10M: 用户指定的大小
- 在配置文件nginx.conf中添加: client_max_body_size 10M
-
-
设置的位置:
在http{ }中设置:client_max_body_size 20m;
-
所有的server中的所有的location都起作用
-
在server{ }中设置:client_max_body_size 20m;
- 对当前server的所有的location生效
-
在location{ }中设置:client_max_body_size 20m;
- 只对当前location生效
-
-
三者的区别是:
-
http{} 中控制着所有nginx收到的 http 请求。
-
报文大小限制设置在server{}中,控制该server收到的请求报文大小
-
如果配置在location中,则报文大小限制,只对匹配了location 路由规则的请求生效。
-
6. Qt中的哈希运算
-
哈希算法 - QCryptographicHash
// 构造哈希对象 QCryptographicHash(Algorithm method); // 添加数据 // c格式添加数据 void QCryptographicHash::addData(const char *data, int length); // qt中的常用方法 void QCryptographicHash::addData(const QByteArray &data); // 适合操作大文件 bool QCryptographicHash::addData(QIODevice *device); // QFile - 使用device打开一文件, 在addData进行文件的读操作 // 计算结果 QByteArray QCryptographicHash::result() const; // 一般适合,哈希值都是使用16进制格式的数字串来表示 QByteArray QByteArray::toHex() const; [static] QByteArray QCryptographicHash::hash(const QByteArray &data, Algorithm method); - 参数data: 要运算的数据 - 参数method: 使用的哈希算法 - 返回值: 得到的哈希值
8. 程序中的相关协议
1. 注册
-
客户端
// url http://127.0.0.1:80/reg // post数据格式 { userName:xxxx, nickName:xxx, firstPwd:xxx, phone:xxx, email:xxx }
-
服务器
成功 {“code”:“002”} 该用户已存在 {“code”:“003”} 失败 {“code”:“004”}
2. 登录
-
客户端
// url http://127.0.0.1:80/login // post数据 { user:xxxx, pwd:xxx }
-
服务器
§ 成功: { "code": "000", "token": "xxx" } § 失败: { "code": "001", "token": "faild" } § token验证 ® token验证成功:{"token":"110"} ® token验证失败:{"token":"111"}
3. 秒传
-
客户端
# url http://127.0.0.1:80/md5 # post数据格式 { user:xxxx, token:xxxx, md5:xxx, fileName: xxx }
-
服务器
locationi /md5 { fastcgi_pass 地址:端口; include fastcgi.conf; }
文件已存在(秒传成功): {“code”:“005”} 秒传成功: {“code”:“006”} 秒传失败: {“code”:“007”}
4. 上传
-
客户端
# url http://127.0.0.1:80/upload # post数据格式 ------WebKitFormBoundary88asdgewtgewx\r\n Content-Disposition: form-data; user="mike"; filename="xxx.jpg"; md5="xxxx"; size=10240 Content-Type: text/plain 真正的文件内容 - 2G ------WebKitFormBoundary88asdgewtgewx--
-
客户端上传多个文件
// 1. 上传多个文件, 文件是一个一个处理的, 处理第一个的时候其余的文件信息应该保存起来 /* 1. 需要一块内存, stl, 队列queue, vector/list(首选) 2. 将数据存储到队列, 先进先出 操作: 1. 往队列中放 2. 从队列中取 3. 将元素从队列中删除 4. 判断其是否为空 5. 清空队列 */ class uploadTask { public: void push(FileInfo info); FileInfo getItem(); void delItem(QString md5); void delItem(FileInfo info); bool isEmpty(); void cleanQueue(); private: uploadTask(); uploadTask(const uploadTask &t); queue<FileInfo> myqueue; } // 任务队列中每一个任务的类型 struct FileInfo { QString filename; long fileSize; QString user; // 文件所有者 QStirng md5; }
-
服务器
// fastcgi程序 int main() { while(FCGI_Accept() >= 0) { // 1. 读一次数据 - buf, 保证能够将分界线和两个头都装进去 char buf[4096]; // len == 实际读出的字节数 int len = fread(buf, 1, 4096, stdin); // 2. 跳过第一行的分界线 len = len - 第一行的长度 // 3. 将第二行中的user, filename, md5, size的值读出 -> 内存 len = len - 第二行的长度; // 4. 跳过第3行 len = len-第三行的长度 // 5. 跳过空行 len = len-空行的长度; // 6. 现在得到的位置的就是传输的真正数据的正文开始 // 7. 循环的将剩余的内容读出, 有必要就写磁盘 // 8. 读完了, 将最后的分界线剔除 // 9. 以上8步处理完毕, 文件内容就被扣出来了 } }
成功 {“code”:“008”} 失败 {“code”:“009”}
5. 获取用户文件个数
-
客户端
url http://127.0.0.1:80/myfiles?cmd=count post数据 { “user”: “kevin”, “token”: “xxxx” } -
服务器:
{ "num":"270", "code":"110" // token验证 } § token验证 ® token验证成功:{"token":"110"} ® token验证失败:{"token":"111"}
6. 从服务器获取文件信息
-
客户端
获取用户文件信息 http://127.0.0.1:80/myfiles&cmd=normal 按下载量升序 http://127.0.0.1:80/myfiles?cmd=pvasc 按下载量降序 http://127.0.0.1:80/myfiles?cmd=pvdesc post数据 { “user”: “yoyo” , “token”: “xxxx”, “start”: 0, “count”: 10 } -
服务器
成功 文件列表json 失败 {“code”: “015”} { "files": [ { "user": "yoyo", "md5": "e8ea6031b779ac26c319ddf949ad9d8d", "time": "2017-02-26 21:35:25", "filename": "test.mp4", "share_status": 0, "pv": 0, "url": "http://192.168.31.109:80/group1/M00/00/00/wKgfbViy2Z2AJ-FTAaM3As-g3Z0782.mp4", "size": 27473666, "type": "mp4" }, { "user": "yoyo", "md5": "e8ea6031b779ac26c319ddf949ad9d8d", "time": "2017-02-26 21:35:25", "filename": "test.mp4", "share_status": 0, "pv": 0, "url": "http://192.168.31.109:80/group1/M00/00/00/wKgfbViy2Z2AJ-FTAaM3As-g3Z0782.mp4", "size": 27473666, "type": "mp4" } ] }
7. 下载文件之后, 下载量pv字段的处理
-
客户端
请求的url: http://127.0.0.1:80/dealfile?cmd=pv 通过值请求更新数据库中该文件对应的下载量字段值
/* post数据块 */ { "user": "yoyo", "token": "xxx", "md5": "xxx", "filename": "xxx" }
-
服务器
成功: {"code":"016"} 失败: {"code":"017"}
8. 文件分享
-
客户端
请求的url http://127.0.0.1:80/dealfile?cmd=share
/* post数据格式 */ { "user": "yoyo", "token": "xxxx", "md5": "xxx", "filename": "xxx" }
-
服务器
成功 {"code":"010"} 失败 {"code":"011"} 别人已经分享此文件 {"code": "012"}
9. 文件删除
-
客户端
请求的url http://127.0.0.1:80/dealfile?cmd=del
/* post数据格式 */ { "user": "yoyo", "token": "xxxx", "md5": "xxx", "filename": "xxx" }
-
服务器
成功 {"code":"013"} 失败 {"code":"014"}
10. 获取共享文件数目
-
客户端
get请求对应的url http://127.0.0.1:80/sharefiles&cmd=count
-
服务器
# 服务器返回一个整形数
11. 共享文件信息
-
客户端
请求的url, 获取共享文件信息 http://127.0.0.1:80/sharefiles&cmd=normal
/* post数据格式 */ { "start": 0, "count": 10 }
-
服务器
# 数据格式参考获取文件列表
12. 取消分享文件
-
客户端
请求的url: http://127.0.0.1:80/dealsharefile?cmd=cancel
/* post数据块格式 */ { "user": "yoyo", "md5": "xxx", "filename": "xxx" }
-
服务器
成功 {"code":"018"} 失败 {"code":"019"}
13. 转存文件
- 客户端
http 请求的url: http://127.0.0.1:80/dealsharefile?cmd=save
```json
/* post数据块格式 */
{
"user": "yoyo",
"md5": "xxx",
"filename": "xxx"
}
```
- 服务器
```json
成功 {"code":"020"}
文件已存在 {"code":"021"}
失败 {"code":"022"}
```
9、总结
1. Location语法
-
语法规则
location [=|~|~*|^~] /uri/ { … } 正则表达式中的特殊字符: - . () {} [] * + ?
-
Location优先级说明
- 在nginx的location和配置中location的顺序没有太大关系。
- 与location表达式的类型有关。
- 相同类型的表达式,字符串长的会优先匹配。
-
location表达式类型
-
~ 表示执行一个正则匹配,区分大小写
-
~* 表示执行一个正则匹配,不区分大小写
-
^~ 表示普通字符匹配。使用前缀匹配。如果匹配成功,则不再匹配其他location。
-
= 进行普通字符精确匹配。也就是完全匹配。
-
-
匹配模式及优先级顺序(高 -> 低):
location = /uri =开头表示精确匹配,只有完全匹配上才能生效。 一旦匹配成功,则不再查找其他匹配项。 location ^~ /uri ^~ 开头对URL路径进行前缀匹配,并且在正则之前。 一旦匹配成功,则不再查找其他匹配项。 需要考虑先后顺序, 如: http://localhost/helloworld/test/a.html location ~ pattern location ~* pattern ~ 开头表示区分大小写的正则匹配。 ~*开头表示不区分大小写的正则匹配。 location /uri 不带任何修饰符,也表示前缀匹配,但是在正则匹配之后。 location / 通用匹配,任何未匹配到其它location的请求都会匹配到,相当于switch中的default。 客户端: http://localhost/helloworld/test/a.html 客户端: http://localhost/helloworld/test/ /helloworld/test/a.html /helloworld/test/ location / { } location /helloworld/ { } location /helloworld/test/ { } location =/helloworld/test/ { root xxx; } http://localhost/helloworld/test/a.html location ^~ /helloworld/test/ { } location ^~ /login/ { } http://localhost/helloworld/test/a.JPG location ~* \.[jpg|png] { } http://192.168.1.100/login/hello/world/login.html /login/hello/world/login.html location / { } location /login/ { } location /login/hello/ { } location /login/hello/world/ { } location ~ /group[1-9]/M0[0-9] { }
-
练习题
匹配示例:
- / -> configuration
- /index.html -> configuration
- /documents/document.html -> configuration C
- /documents/
- /
- /images/1.gif -> configuration D
- /images/
- /
- /documents/1.jpg -> configuration E
2. 项目总结
- 客户端
- Qt
- 了解了Qt中http通信
- Qt
- nginx反向代理服务器
- 为web服务器服务
- web服务器需要集群
- web服务器 - nginx
- 处理静态请求 - > 访问服务器文件
- 动态请求 -> 客户端给服务器提交的数据
- 借助fastCGI进行处理
- 讲的是单线程处理方式 - API
- 也可以多线程处理 -> 另外的API
- 使用spawn-fcgi启动
- 借助fastCGI进行处理
- mysql
- 关系型数据库 - 服务器端
- 存储什么?
- 项目中所有用到的数据
- redis
- 非关系型数据库 - 服务器端使用
- 数据默认在内存, 不需要sql语句, 不需要数据库表
- 键值对存储, 操作数据使用的是命令
- 和关系型数据库配合使用
- 存储服务器端经常访问的数据
- fastDFS
- 分布式文件系统
- 追踪器, 存储节点, 客户端
- 存储节点的集群
- 横向扩容 -> 增加容量
- 添加新组, 将新主机放到该组中
- 纵向扩容 -> 备份
- 将主机放到已经存在的组中
- 存储用户上传的所有的文件
- 给用户提供下载服务器
- 横向扩容 -> 增加容量
3. 项目提炼
- 做的是文件服务器
- 电商网站
- 旅游网站
- 租房
- 装修公司
- 医院
- 短视频网站
- 需要什么?
- 首先需要的是fastDFS
- 配置环境
- 扩容
- 操作fastDFS - 客户端
- web
- 桌面终端 - Qt
- 数据库操作
- mysql
- oralce
- 有一个web服务器 - Nginx
- 静态资源部署
- 动态请求 - 编写fastCGI程序
- 注册
- 登录
- 上传
- 下载
- 秒传
- 文件列表的获取
- redis
- 存储服务器访问比较频繁的数据
- 首先需要的是fastDFS
4. 存储节点反向代理
http://192.168.31.109:80/group1/M00/00/00/wKgfbViy2Z2AJ-FTAaM3Asg3Z0782.mp4"
http://192.168.31.109:80/group2/M00/00/00/wKgfbViy2Z2AJ-FTAaM3Asg3Z0782.mp4"
上图的反向代理服务器代理的是每个存储节点上部署的Nginx
- 每个存储节点上的Nginx的职责: 解析用户的http请求, 帮助用户快速下载文件
客户端上传了一个文件, 被存储到了fastDFS上, 得到一个文件ID
/group1/M00/00/00/wKgfbViy2Z2AJ-FTAaM3Asg3Z0782.mp4"
因为存储节点有若干个, 所有下载的时候不知道对应的存储节点的访问地址
给存储节点上的nginx web服务器添加反向代理服务器之后, 下载的访问地址:
- 只需要知道nginx反向代理服务器的地址就可以了: 192.168.31.109
- 访问的url:
http://192.168.31.109/group1/M00/00/00/wKgfbViy2Z2AJ-FTAaM3Asg3Z0782.mp4
客户端的请求发送给了nginx反向代理服务器
- 反向代理服务器不处理请求, 只转发, 转发给存储节点上的nginx服务器
反向代理服务器的配置 - nginx.conf
- 找出处理指令: 去掉协议, iP/域名, 末尾文件名, ?和后边的字符串
- /group1/M00/00/00/ - 完整的处理指令
- 添加location
server{
location /group1/M00
{
# 数据转发, 设置转发地址
proxy_pass http://test.com;
}
location /group2/M00
{
# 数据转发, 设置转发地址
proxy_pass http://test1.com;
}
}
upstream test.com
{
# fastDFS存储节点的地址, 因为存储节点上安装了nginx, 安装的nginx作为web服务器的角色
server 192.168.31.100;
server 192.168.31.101;
server 192.168.31.102;
}
upstream test1.com
{
# fastDFS存储节点的地址, 因为存储节点上安装了nginx, 安装的nginx作为web服务器的角色
server 192.168.32.100;
server 192.168.33.101;
server 192.168.34.102;
}
# ===================================
存储节点上的web服务器的配置
存储节点1
location /group1/M00
{
# 请求处理
root 请求的资源的根目录; // 存储节点的store_path0对应的路径+data
ngx_fastdfs_module;
}
location /group1/M01
{
# 请求处理
root 请求的资源的根目录;
ngx_fastdfs_module;
}
存储节点2
location /group2/M00
{
# 请求处理
root 请求的资源的根目录;
ngx_fastdfs_module;
}
location /group2/M01
{
# 请求处理
root 请求的资源的根目录;
ngx_fastdfs_module;
}
存储节点3
location /group3/M00
{
# 请求处理
root 请求的资源的根目录;
ngx_fastdfs_module;
}
location /group3/M01
{
# 请求处理
root 请求的资源的根目录;
ngx_fastdfs_module;
}
5. https
- 在百度服务器端首先生成一个秘钥对 -> 对公钥分发
- 百度将公钥给到了CA认证机构, ca对私钥进行签名 -> 生成了证书.
- 第一步第二部只做一次
- 客户端访问百度, 百度将ca生成的证书发送给客户端
- 浏览器对收到的证书进行认证
- 如果证书没有问题 -> 使用ca的公钥将服务器的公钥从证书中取出
- 我们得到了百度的公钥
- 在浏览器端生成一个随机数, 使用得到的公钥进行加密, 发送给服务器
- 服务器端使用私钥解密, 得到了随机数, 这个随机数就是对称加密的秘钥
- 现在秘钥分发已经完成, 后边的通信使用的的对称加密的方式
对称加密
- 加解密秘钥是同一个
非对称加密
- 公钥, 私钥
- rsa -> 公钥私钥都是两个数字
- ecc -> 椭圆曲线, 两个点
- 公钥加密, 私钥解密
- 数据传输的时候使用
- 私钥加密, 公钥解密
- 数字签名
哈希函数
- md5/ sha1/sha2
- 得到散列值, 散列值是定长的
消息认证码
- 生成消息认证码: (将原始数据+共享秘钥) * 进行哈希运算 = 散列值
- 验证消息认证码:
- (接收的原始数据 + 共享秘钥) * 哈希运算 = 新的散列值
- 新散列值和旧散列值进行比较, 看是不是相同
- 作用:
- 验证数据的一致性型
- 弊端:
- 两端共享秘钥必须相同, 共享秘钥分发困难
数字签名 -> 目的告诉所有人这个数据的所有者是xxx, xxx就是拿私钥的人
生成一个非对称加密的密钥对
- 公钥
- 私钥
生成签名:
- 对原始数据进行哈希运算 -> 散列值
- 使用非对称加密的私钥, 对散列值进行签名(私钥加密) -> 密文
- 得到的密文就是数字签名
签名的校验:
- 校验这会收到签名者发送的数据
- 原始数据
- 数字签名
- 对接收的数据进行哈希运算 -> 散列值
- 使用非对称加密的公钥, 对数字签名进行解密 -> 明文 == 签名者生成的散列值
- 校验者的散列值 和 签名者的散列值进行比较
- 相同 -> 校验成功了, 数据属于签名的人
- 失败 -> 数据不属于签名的人
弊端:
接收公钥的人没有办法校验公钥的所有者
证书
- 由一个受信赖的机构 (CA) 对某人的公钥进行数字签名
- CA有一个密钥对
- 使用ca的私钥对某个人的公钥进行加密 -> 证书
- 这个人 的公钥
- 这个人的个人信息
项目细节注意
打开虚拟机centOS 黑屏:
crtl+shift+f2 命令行登陆 root root后
输入startx进入图形界面