文件的上传和下载

1 基本概念

1.1客户端
b/s:必须使用http协议
c/s:协议可以随意选择
1.2 服务器
Nginx:

  • 能处理静态请求 html,jpg
  • 动态请求无法处理
  • 服务器集群之后,每台服务器上部署的内容必须相同

fastCGI:帮助服务器处理动态请求
1.3 反向代理服务器:

  • 客户端并不能直接访问web服务器,直接访问到的是反向代理服务器
  • 客户端请求发送给反向代理服务器,反向代理将客户端请求转发给web服务器

1.4 关系型数据库:存储文件属性信息,用户的属性信息
1.5 redis非关系型数据库:提高程序效率,存储是服务器经常要从关系型数据库中读取的数据
1.6 FASTDFS分布式文件系统:存储文件内容,供用户下载
1.7 分布式文件系统:文件系统的全部,不在同一台主机上,而是在很多台主机上,多个分散的文件系统组合在一起,形成一个完整的文件系统

  • 需要有网络
  • 多台主机:不需要在同一地点
  • 需要管理者
  • 编写应用层的管理程序

2 FastDFS

是用c语言编写的一款开源的分布式文件系统,为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,注重高可用、高性能等指标,可以很容易搭建一套高性能的文件服务器集群,提供文件上传、下载等服务。
2.1 fastDFS框架的三个角色

  • 追踪器:管理者 管理存储节点 – 守护进程

  • 存储节点:存储节点是有多个的 – 守护进程

  • 客户端:不是守护进程,是程序员编写的程序,文件的上传和下载
    在这里插入图片描述
    在这里插入图片描述

  • 追踪器:最先启动追踪器

  • 存储节点:第二个启动的角色,存储节点启动之后会单独开一个线程:汇报当前存储节点的容量和剩余容量、汇报数据的同步情况、汇报数据被下载的次数

  • 客户端:最后启动,

    • 上传,链接追踪器,询问存储节点的信息
      • 哪个存储节点有足够的容量
      • 追踪器查询,得到结果
      • 追踪器将查到的存储节点的IP和端口发送到客户端
      • 通过得到IP和端口连接存储节点
      • 将文件内容发送给存储节点
    • 下载,连接追踪器,询问存储节点的信息
      • 要下载的文件在哪个存储节点
      • 追踪器查询,得到结果
      • 追踪器将查到的存储节点的IP和端口发送给客户端
      • 通过得到IP和端口连接存储节点
      • 下载文件

2.2 追踪器集群
为什么集群:避免单点故障
多个tracker如何工作:轮询工作
如何实现集群:修改配置文件

集群方式:横向扩容和纵向扩容

  • 横向扩容:增加容量,添加一台新的主机,即新的分组,主机之间不需要通信
  • 纵向扩容:数据备份,假设当前有两个组group1和group2,将新的主机放到现有的组中,每个组主机的数量从1->N ,这n台主机就是相互备份的关系,同一个组主机之间需要通信,每个组的容量等于容量最小的主机。

fastDFS配置文件:

tracker配置文件:tracker.conf
# 将追踪器和部署的主机的IP地址进程绑定, 也可以不指定
# 如果不指定, 会自动绑定当前主机IP, 如果是云服务器建议不要写
bind_addr=本地IP
# 追踪器监听的端口
port=22122
# 追踪器存储日志信息的目录, xxx.pid文件, 必须是一个存在的目录
base_path=/home/xxx/fastdfs

storage配置文件:storage.conf
# 当前存储节点对应的主机属于哪一个组
group_name=group1
# 当前存储节点和所应该的主机进行IP地址的绑定, 如果不写, 有fastdfs自动绑定
bind_addr=
# 存储节点绑定的端口
port=23000
# 存储节点写log日志的路径
base_path=/home/xxx/fastdfs
# 存储节点提供的存储文件的路径个数
store_path_count=2
# 具体的存储路径
store_path0=/home/xxx/fastdfs
store_path1=/home/xxx/fastdfs1
# 追踪器的地址信息
tracker_server=IP:22122
tracker_server=IP:22122

客户端配置文件:
# 客户端写log日志的目录
# 该路径必须存在
# 当前的用户对于该路径中的文件有读写权限
# 当前用户robin
# 指定的路径属于root
base_path=/home/xxx/fastdfs
# 要连接的追踪器的地址信息
tracker_server=IP:22122
tracker_server=IP:22122

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/stroga.conf)
# 关闭
fdfs_storaged 存储节点的配置文件(/etc/fdfs/stroga.conf) stop
# 重启
fdfs_storaged 存储节点的配置文件(/etc/fdfs/stroga.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
# 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 :在线,可以提供服务
int upload_file1(const char *confFile,const char* myFile,char* fileID)
{
	
	char group_name[FDFS_GROUP_NAME_MAX_LEN + 1];
	ConnectionInfo *pTrackerServer;
	int result;
	int store_path_index;
	ConnectionInfo storageServer;
	

	if ((result=fdfs_client_init(confFile)) != 0)
	{
		return result;
	}

	pTrackerServer = tracker_get_connection();
	if (pTrackerServer == NULL)
	{
		fdfs_client_destroy();
		return errno != 0 ? errno : ECONNREFUSED;
	}

	*group_name = '\0';

	if ((result=tracker_query_storage_store(pTrackerServer, \
	                &storageServer, group_name, &store_path_index)) != 0)
	{
		fdfs_client_destroy();
		fprintf(stderr, "tracker_query_storage fail, " \
			"error no: %d, error info: %s\n", \
			result, STRERROR(result));
		return result;
	}

	result = storage_upload_by_filename1(pTrackerServer, \
			&storageServer, store_path_index, \
			myFile, NULL, \
			NULL, 0, group_name, fileID);
	if (result == 0)
	{
		printf("%s\n", fileID);
	}
	else
	{
		fprintf(stderr, "upload file fail, " \
			"error no: %d, error info: %s\n", \
			result, STRERROR(result));
	}

	tracker_disconnect_server_ex(pTrackerServer, true);
	fdfs_client_destroy();

	return result;
}

//使用多进程的方式来实现
int upload_file2(const char* confFile,const char* uploadFile,char* fileID,int size){
	//创建管道
	int fd[2];
	int ret = pipe(fd);
	if(ret ==-1){
		perror("pipe error");
		return -1;
	}

	//创建子进程
	pid_t pid = fork();
	if(pid==0){ //子进程
		//标准输出重定向到管道的写端
		dup2(fd[1],STDOUT_FILENO);
		//关闭读端
		close(fd[0]);

		//执行命令
		execlp("fdfs_upload_file","xxx",confFile,uploadFile,NULL);
		perror("execlp error");
	}else{
		close(fd[1]);

		read(fd[0],fileID,size);
		wait(NULL);
	}

}

3 数据库类型

3.1 基本概念
关系型数据库sql

  1. 操作数据库必须使用sql语句
  2. 数据存储在磁盘
  3. 存储的数据量大
  4. 例如:MySQL,oracle,sqlite,sql server

非关系型数据库:nosql

  1. 操作不使用sql语句
  2. 数据默认存储在内存,速度快,效率高,存储的数据量小
  3. 不需要数据库表,以键值对的方式存储

关系型和非关系型搭配使用:
在这里插入图片描述
所有数据默认存储在关系型数据库中,客户端访问服务器,有一些数据,服务器需要频繁的查询数据,所以服务器首先将数据从关系型数据库中读出,将数据写入到redis中,客户端以后在访问服务器,服务器就从redis中直接读取数据。

redis的两个角色:

# 服务器 - 启动
redis-server # 默认启动
redis-server confFileName # 根据配置文件的设置启动
# 客户端
redis-cli # 默认连接本地, 绑定了6379默认端口的服务器
redis-cli -p 端口号
redis-cli -h IP地址 -p 端口 # 连接远程主机的指定端口的redis
# 通过客户端关闭服务器
shutdown
# 客户端的测试命令
ping [MSG]

redis中数据的组织格式:
键值对key:value
key:必须是字符串
value是可选的:String类型,List类型,Set类型,SortedSet类型,Hash类型

常用数据类型:
String类型:字符串
List类型:存储多个String字符串
Set类型:
	集合:stl集合,默认是排序的,元素不重复
	redis集合:元素不重复,数据无序
SortedSet类型:排序集合,集合中的每个元素分为两部分[分数,成员]->[66,“tom”]
Hash类型:跟map数据组织方式一样:key:value

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中的field是否存在,存在返回1,不存在返回0
	HEXISTS key field
	#返回哈希表key中所有域的值
	HVALS key

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
	#以秒为单位,返回给定key的剩余生存时间
	TTL key

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中数据库的个数
database 16
	-切换 select dbID [dbID == 0~16-1]

redis数据持久化:数据从内存到磁盘的过程
持久化的两种方式:
rdb方式:

  • 默认的持久化方式,默认打开
  • 磁盘的持久化文件xxx.rdb
  • 将内存数据以二进制的方式直接写入磁盘文件
  • 文件比较小,恢复时间短,效率高
  • 以用户设定的频率->容易丢失数据
  • 数据完整性相对较低

aof方式

  • 默认是关闭的
  • 磁盘的持久化文件xxx.aof
  • 直接将生成数据的命令写入磁盘文件
  • 文件比较大,恢复时间长,效率低
  • 以某种频率->1sec
  • 数据完整性高
# rdb的同步频率,任意一个满足都可以
save 999 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

rdb如何关闭 save ""
两种模式同时开启,如果进行恢复,如何选择?
效率上考虑:rdb模式
数据的完整性:aof模式

hiredis的使用:
API的使用:

// 连接数据库
redisContext *redisConnect(const char *ip, int port);
redisContext *redisConnectWithTimeout(const char *ip,
		int port, const struct timeval tv);

执行redis命令:
// 执行redis命令
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

释放资源:
void freeReplyObject(void *reply);
void redisFree(redisContext *c);

hiredis向内存中写数据:

#include <stdio.h>
#include <hiredis.h>
int main(){

    //1.连接redis服务器
    redisContext* c = redisConnect("127.0.0.1",6379);
    if(c->err != 0){
        return -1;
    }
    void *prt = redisCommand(c,"hmset user userName zhangsan passwd 123456 age 23 sex man");
    redisReply* ply = (redisReply*)prt;
    if(ply->type == 5){
        printf("状态:%s\n",ply->str);
    }
    freeReplyObject(ply);

    //从数据库中取数据
    prt=redisCommand(c,"hgetall user");
    ply = (redisReply*)prt;
    if(ply->type == 2){
        for(int i=0;i<ply->elements;i+=2){
            printf("key:%s,value:%s\n",ply->element[i]->str,ply->element[i+1]->str);
        }
    }
    freeReplyObject(ply);
    redisFree(c);
    return 0;
}

命令:gcc -o myredis myredis.c -I/usr/local/include/hiredis/ -lhiredis
若报错找不到.so文件,则sudo

4 Nginx

作为web服务器,解析http协议;反向代理服务器;邮件服务器
4.1 正向代理:为用户服务
正向代理是位与客户端和原始服务器之间的服务器,为了能够从原始服务器获取请求的内容,客户端需要将请求发送给代理服务器,然后再由代理服务器将请求转发给原始服务器,原始服务器接受到代理服务器的请求并处理,然后将处理好的数据转发给代理服务器,之后再由代理服务器转发给客户端,完成整个请求过程。

4.2反向代理
反向代理方式是指代理原始服务器来接受来自Internet的链接请求,然后将请求转发给内部网络上的原始服务器,并将从原始服务器上得到的结果转发给Internet上请求数据的客户端。所以,反向代理就是位于Internet和原始服务器之间的服务器,对于客户端来说就表现为一台服务器,客户端所发送的请求都是直接发送给反向代理服务器,然后由反向代理服务器统一调配。
在这里插入图片描述

  1. 客户端给服务器发送请求,连接服务器,用户不知道服务器地址,只有反向代理服务器的地址是公开的
  2. 请求直接发给反向代理服务器
  3. 反向代理服务器将请求转发给后边的web服务器:web服务器N台,反向代理服务器转发请求会轮询进行
  4. web服务器收到请求进行处理,得到结果
  5. web服务器将处理结果发送给反向代理服务器
  6. 反向代理服务器将拿到的结果转发给客户端

4.3 域名和IP
什么是域名:www.baidu.com,www.jd.com
什么是IP地址:点分十进制的字符串:11.22.33.44
域名和IP地址的关系:域名绑定IP,一个域名只能绑定一个IP,一个IP地址被多个域名绑定

4.4 Nginx的安装和配置

下载:
官方地址: 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/
		解析正则表达式
			安装
				./configure
				make
				sudo make install

安装:
# nginx工作时候需要依赖三个库
# 三个参数=这三个库对应的源码安装目录
# 根据自己的电脑的库安装包的位置进行指定
./configure --with-openssl=../openssl-1.0.1t --with-pcre=../pcre-8.40 --withzlib=../zlib-1.2.11
make
sudo make install

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
	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地址 ->xxx.xxx.xx.xxx
	在浏览器中访问该IP地址
		看到一个welcome nginx的欢迎界面

配置:

1.Nginx配置文件的位置:/usr/local/nginx/conf/nginx.conf

2.Nginx配置文件的组织格式:
	http -> 模块, http相关的通信设置
		server模块 -> 每个server对应的是一台web服务器
			location 模块
				处理的是客户端的请求
	mail -> 模块, 处理邮件相关的动作

3.常用配置介绍:
	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 /
		{
		处理动作
		}

4.5 Nginx的使用:
部署静态网页:

静态网页存储目录:/usr/local/nginx/html
自己创建新的目录:mkdir /usr/local/nginx/mydir

访问地址:http://xxx.xxx.xx.xxx/login.html
	login.html放在自己创建的目录中
服务器要处理的动作:
	# 对应这个请求服务器要添加一个location
	location 指令(/)
	{
		# 找一个静态网页
		root yundisk; # 相对于/usr/local/nginx/来找
		# 客户端的请求是一个目录, nginx需要找一默认显示的网页
		index index.html index.htm;
	}
	# 配置之后重启nginx
	sudo nginx -s reload

访问地址:http://xxx.xxx.xx.xxx/hello/reg.html
	hello是一个目录
	reg.html放在自己创建的目录中
	添加location:
	location /hello/
	{
		root hello;
		index xx.html;
	}

访问地址:http://xxx.xxx.xx.xxx/upload/  浏览器显示upload.html
	直接访问一个目录,得到一个默认网页
		upload是一个目录,upload.html应该在upload目录中
		location /upload/
		{
			root yundisk;
			index upload.html;
		}

反向代理和负载均衡:
在这里插入图片描述
准备工作:

  1. 需要客户端 1个:window中的浏览器作为客户端
  2. 反向代理服务器 ->1个:window作为反向代理服务器
  3. web服务器 ->2个:Ubuntu robin:xxx Ubuntu luffy:xxx
    在这里插入图片描述
找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
{
	weight设置权重,让每个服务器处理的服务数量不同
	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;
}	

5 Nginx作为web服务器处理请求

静态请求:客户端访问服务器的静态网页,不涉及任何数据的处理
动态请求:客户端会将数据提交给服务器

# 使用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, 处理指令就是请求行的第二部分, ? 以前的内容

http协议:
请求消息:客户端发送给服务器的数据格式

四部分: 请求行, 请求头, 空行, 请求数据
	请求行: 说明请求类型, 要访问的资源, 以及使用的http版本
	请求头: 说明服务器要使用的附加信息
	空行: 空行是必须要有的, 即使没有请求数据
	请求数据: 也叫主体, 可以添加任意的其他数据

get方式提交:
	第一行: 请求行
	第2-9: 请求头(键值对)10: 空行
	get方式提交数据, 没有第四部分, 提交的数据在请求行的第二部分, 提交的数据会全部显示在地址栏中
	
post方式提交数据:
	第一行: 请求行
	第2 -12: 请求头 (键值对)13: 空行
	第14: 提交的数据
		

响应消息:服务器给客户端发送的数据

四部分: 状态行, 消息报头, 空行, 响应正文
	状态行: 包括http协议版本号, 状态码, 状态信息
	消息报头: 说明客户端要使用的一些附加信息
	空行: 空行是必须要有的
	响应正文: 服务器返回给客户端的文本信息
第一行:状态行
第2 -11: 响应头(消息报头)12: 空行
第13-18: 服务器给客户端回复的数据

fastCGI:
CGI:通用网关接口:描述了客户端和服务器程序之间传输数据的一种标准

  1. 用户通过浏览器访问服务器,发送了一个请求
  2. 服务器接收数据,对接收的数据进行解析
  3. nginx对于一些登录数据不知道如何处理,nginx将数据发送给cgi程序,服务器会创建一个cgi进程
  4. CGI进程执行:加载配置,连接其他服务器,逻辑处理,得到结果,将结果发送给服务器,退出
  5. 服务器将CGI处理结果发送给客户端
    在这里插入图片描述

fastCGI致力于减少web服务器与CGI进程之间的互动开销,从而使服务器可以同时处理更多的web请求。
在这里插入图片描述
nginx和fastCGI:
在这里插入图片描述

  1. 客户端访问,发送请求
  2. nginx web服务器,无法处理用户提交的数据
  3. spawn-fcgi通信过程中的服务器角色,被动接收数据,在spawn-fcgi启动的时候给其绑定IP和端口
  4. fastCGI程序,程序员写可执行程序,使用spawn-fcgi进程管理器启动login程序得到一进程。

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>");
	}
}

http协议:
1.请求消息-客户端(浏览器)发送给服务器的数据格式。
四部分:请求行,请求头,空行,请求数据

  • 请求行:说明请求类型,要访问的资源,以及使用的http版本
  • 请求头:说明服务器要使用的附加信息
  • 空行:空行是必须要有的,即使没有请求数据
  • 请求数据:也叫主体,可以添加任意的其他数据

get方式提交数据:

  • 第一行:请求头
  • 第2-9行:请求头(键值对)
  • 第10行:空行

get方式提交数据没有第四部分,提交的数据在请求行的第二部分,提交的数据会全部显示在地址栏中
post方式提交数据:

  • 第一行:请求行
  • 第2-12行:请求头(键值对)
  • 第13行:空行
  • 第14行:提交的数据
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请求:
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

2.响应消息:服务器给客户端发送的数据
四部分:状态行,消息报头,空行,响应正文

  • 状态行:包括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;
}

3.http状态码
1xx:指示信息–表示请求已接收,继续处理
2xx:成功–表示请求已被成功接收、理解、接受
3xx:重定向–要完成请求必须进行更进一步的操作
4xx:客户端错误–请求有语法错误或请求无法实现
5xx:服务器端错误–服务器未能实现合法的请求

文件上传下载流程

上传流程:
在这里插入图片描述
下载流程:
在这里插入图片描述
优化下载:让客户端连接fastdfs的存储节点,实现文件下载
在这里插入图片描述
1.客户端发送请求使用的协议http

  • fastDFS不能解析http协议
  • nginx能解析http协议:nginx中安装fastDFS的插件

上传的时候将fileID和存储节点IP地址都进行存储就可以知道文件存储在对应的那个存储节点上

登录和注册协议:
客户端:

# 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\"}");
	}
}

登陆协议:

客户端:
#URL
http://127.0.0.1:80/login
# post数据格式
{
	user:xxxx,
	pwd:xxx
}

服务端:
location /login
{
	# 转发数据
	fastcgi_pass localhost:10001;
	include fastcgi.conf;
}

单例模式:

单例模式的优点:

  • 在内存中只有一个对象,节省内存空间
  • 避免频繁的创建销毁对象,可以提高性能
  • 避免对共享资源的多重占用
  • 可以全局访问

适用场景:

  • 需要频繁实例化然后销毁的对象
  • 创建对象耗时过多或者耗资源过多,但又经常用到的对象
  • 有状态的工具类对象
  • 频繁访问数据库或文件的对象
  • 要求只有一个对象的场景

如何保证单例对象只有一个:

// 在类外部不允许进行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();
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;
}
Test* Test::m_test = new 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;

QT中处理json数据:
内存中的json数据 -> 写磁盘
在这里插入图片描述
磁盘中的json字符串 -> 内存
在这里插入图片描述
给服务器发送数据:

void Login::on_regButton_clicked()
{
    //1.从控件中取出用户输入的数据
    QString userName = ui->reg_userName->text();
    QString nickName = ui->reg_NickName->text();
    QString passwd = ui->reg_passwd->text();
    QString email = ui->reg_mail->text();
    QString phone = ui->reg_phone->text();
    QString confirmPwd = ui->reg_confirmPwd->text();
    //2.数据校验
    QRegExp regexp;
    QString User = "^[a-zA-Z0-9_@#-\\*]\\{3,16\\}$";
    regexp.setPattern(User);
    if(!regexp.exactMatch(userName)){
            QMessageBox::warning(this,"ERROR","用户名格式不正确");
            return;
    }

    // 3. 用户信息发送给服务器
    QNetworkAccessManager* pManager = new QNetworkAccessManager(this);
    QNetworkRequest request;
    QString url = QString("http://%1:%2/reg").arg(ui->ip->text()).arg(ui->ip->text());
    request.setUrl(url);
    request.setHeader(QNetworkRequest::ContentTypeHeader,"application/json");
    QJsonObject obj;
    /*
        {
            userName:xxxx,
            nickName:xxx,
            firstPwd:xxx,
            phone:xxx,
            email:xxx
        }
    */
    //将用户提交的数据拼接成json对象字符串
    obj.insert("userName",userName);
    obj.insert("nickName",nickName);
    obj.insert("passwd",passwd);
    obj.insert("phone",phone);
    obj.insert("email",email);

    QJsonDocument doc(obj);
    QByteArray json = doc.toJson();
    QNetworkReply* reply = pManager->post(request,json);
    // 4. 接收服务器发送的响应数据
    connect(reply,&QNetworkReply::readyRead,this,[=](){
        // 5. 对服务器响应进行分析处理, 成功or失败
        // 5.1 接收数据
        QByteArray all = reply->readAll();
        // 5.2 需要知道服务器往回发送的字符串的格式
        QJsonDocument doc = QJsonDocument::fromJson(all);
        QJsonObject myobj = doc.object();
        QString status = myobj.value("code").toString();
        if("002"==status){
            //成功
        }
        else if("003"==status){
            //用户已经存在
        }else{
            //失败
        }
    });

QSS样式表

选择器类别:

  • 通用选择器(*):匹配所有部件匹配当前窗口所有的子窗口
  • 类型选择器(QWidget):匹配QWidget及其子类窗口的实例
  • 类选择器(.QPushButton):匹配QPushButton的实例,但不包含子类
  • ID选择器(QPushButton#okButton):匹配所有objectName为okButton的QPushButton实例
  • 后代选择器(QDialogQPushButton):匹配属于QDialog后代(孩子,孙子等)的QPushButton所有实例
  • 子选择器(QDialog>QPushButton):匹配属于QDialog直接子类的QPushButton所有实例

QSS的使用步骤:

  1. 根据介绍的选择器对所有的控件样式设置,写入qss文件中
  2. 在程序中读样式表文件,得到一个字符串 -> 样式字符串
  3. 将读出的样式设置给qt的应用程序
  4. 在qt中有一个全局的应用程序指针qApp
  5. qApp->setStyleSheet(“样式字符串”);
  6. QFile读磁盘文件,磁盘文件的编码格式必须是utf-8
//设置样式表
QFile file("mytest.qss");
bool bl = file.open(QFile::ReadOnly);
if(!bl){
    qDebug()<<"文件未打开";
}
QByteArray all = file.readAll();
file.close();
qApp->setStyleSheet(all);

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);
void MainWindow::on_uploadBtn_clicked()
{
    //1.创建networkmanager对象
    QNetworkAccessManager* pManager = new QNetworkAccessManager(this);
    //2.发送数据 post
    QNetworkRequest request;
    request.setUrl(QUrl("http://10.102.17.151/uploadFile"));
    request.setHeader(QNetworkRequest::ContentTypeHeader,"multipart/form-data");

    //post数据块
    QFileInfo info(ui->filePath->text());
    QHttpPart part;
    QString disp = QString("form-data; user=\"%1\"; filename=\"%2\"; md5=\"%3\";size=%4")
            .arg(LoginInstance::getInstance()->getName()).arg(info.fileName()).arg(getMd5(ui->filePath->text())).arg(info.size());
    QString sufix = info.suffix();
    part.setHeader(QNetworkRequest::ContentDispositionHeader,disp);
    QFile *file = new QFile(ui->filePath->text());
    file->open(QFile::ReadOnly);
    part.setBodyDevice(file);
    //传输的数据库的格式

    part.setHeader(QNetworkRequest::ContentTypeHeader,"xxxx");
    QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType,this);
    multiPart->append(part);

    ui->recordMsg->append("开始发送数据....");
    QNetworkReply* reply = pManager->post(request,multiPart/*QHttpMultiPart *multiPart*/);
    connect(reply,&QNetworkReply::readyRead,this,[=](){
        //3.接收数据
        QByteArray all = reply->readAll();
        //4.格式解析 -> 纯文本
        //5.判断是否上传成功
        ui->recordMsg->append("服务器响应数据:"+all);

        //释放内存  deleteLater 会自己把自己析构掉,自己释放自己
        multiPart->deleteLater();
        file->close();
        file->deleteLater();
        reply->deleteLater();
    });
    //进度条
    connect(reply,&QNetworkReply::uploadProgress,this,[=](qint64 bytesSent,qint64 bytesTotal){
        //更新进度条
        qDebug()<<"当前进度:"<<bytesSent<<",总数:"<<bytesTotal;
        if(bytesSent>0){
            ui->progressBar->setValue(bytesSent * 100 / bytesTotal);
        }
    });
}

fastcgi程序

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
       \r\n
       真正的文件内容\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';   //字符串结束符
    //LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC,"boundary: [%s]\n", boundary);

    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 += 4; //\r\n\r\n
    // len就是正文的起始位置到数组结束的长度
    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
       真正的文件内容\r\n
       ------WebKitFormBoundary88asdgewtgewx
    */
    begin = p;
    // 将数组中剩余的数据(文件的内容), 写入到磁盘中
    int fd = open(filename, O_CREAT | O_WRONLY, 0664);
    write(fd, begin, len);
    // 1. 文件已经读完了
    // 2. 文件还没读完
    if(*p_size > len)
    {
        // 读文件 -> 接收post数据
        // fread , read  , 返回值>0== 实际读出的字节数; ==0, 读完了; -1, error
        while( (len = fread(file_buf, 1, 4096, stdin)) > 0 )
        {
            // 读出的数据写文件
            write(fd, file_buf, len);
        }
    }
    // 3. 将写入到文件中的分界线删除
    ftruncate(fd, *p_size);
    close(fd);

    free(file_buf);
    return ret;
}

int main(){
    while(FCGI_Accept()>=0){
        //处理流程
        //1.读一次数据,buf,保证能够将分界线和两个头都装进去
        char user[24];
        char fileName[32];
        char md5[64];
        long size;
        recv_save_file(user,fileName,md5,&size);
        //filename对应的文件上传到fastdfs
        //上传得到的文件ID需要写数据库
        //给客户端发送响应数据
        printf("Content-type:text/plain\r\n\r\n");
        printf("用户名:%s\n",user);
        printf("文件名:%s,md5:%s,size:%ld\n",fileName, md5, size);
        printf("客户端处理数据完毕,恭喜。。");
    }
}

启动命令:spawn-fcgi -a IP地址 -p 端口 -f ./fastcgi程序

QT中的哈希算法:

//构造哈希对象
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: 使用的哈希算法
	- 返回值: 得到的哈希值

QString MainWindow::getMd5(QString path){
    QCryptographicHash hash(QCryptographicHash::Md5);
    //1.添加数据
    QFile file(path);
    file.open(QFile::ReadOnly);
    hash.addData(&file);
    //2.数据运算 -> 结果
    QByteArray res = hash.result().toHex();
    //3.return result
    return res;
}

总结

在这里插入图片描述

1.客户端
 	QT:了解Qt中http通信
2.nginx反向代理服务器
	为web服务器服务
	web服务器需要集群
3.web服务器 - nginx
	处理静态请求 -> 访问服务器文件
	处理动态请求 -> 客户端给服务器提交的数据
		借助fastCGI进行处理
			单线程或多线程的处理,使用spawn-fcgi启动
4.mysql
	关系型数据库-服务器端
	存储什么?项目中所有用到的数据
5.redis
	非关系型数据库 - 服务器端使用
	数据默认存在内存,不需要sql语句,不需要数据库表
	键值对存储,操作数据使用的是命令
	和关系型数据库配合使用
	存储服务器端经常访问的数据
6.fastDFS
	分布式文件系统
	追踪器,存储节点,客户端
	存储节点的集群
		横向扩容 -> 增加容量
			添加新组,将新主机放到该组中
		纵向扩容 -> 备份
			将主机放到已经存在的组中
		存储用户上传的所有文件
		给用户提高下载服务器
		

存储节点反向代理:
在这里插入图片描述

上图的反向代理服务器代理的是每个存储节点上部署的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

http:
在这里插入图片描述

  1. 在百度服务器端首先生成一个密钥对 -> 对公钥分发
  2. 百度将公钥给到了CA认证机构,ca对私钥进行签名->生成了证书
  3. 第一部第二部只做一次
  4. 客户端访问百度,将百度ca生成的证书发送给客户端
  5. 浏览器对收到的证书进行认证
  6. 如果证书没有问题 -> 使用ca的公钥将服务器的公钥从证书中取出
  7. 得到了百度的公钥
  8. 浏览器端生成一个随机数,使用得到的公钥进行加密,发送给服务器
  9. 服务器端使用私钥解密,得到了随机数,这个随机数就是对称加密的密钥
  10. 现在密钥分发已经完成,后边的通信使用对称加密的方式
1.对称加密:加解密密钥是同一个
2.非对称加密
	公钥,私钥
		rsa:公钥私钥都是两个数字
		ecc:椭圆曲线,两个点
	公钥加密,私钥解密
		数据传输的时候使用
	私钥加密,公钥解密
		数字签名
3.哈希函数
	md5/sha1/sha2
	得到散列值,散列值是定长的
4.消息认证码
	生成消息认证码(将原始数据+共享密钥)*进行哈希运算=散列值
	验证消息认证码:
		(接收的原始数据+共享密钥)*哈希运算=新的散列值
		新散列值和旧散列值进行比较,看是不是相同
	作用:验证数据的一致性
	弊端:两端共享密钥必须相同,共享密钥分发困难
5.数字签名 -> 目的告诉所有人这个数据的所有者是xxx,就是拿私钥的人
	生成一个非对称加密的密钥对
		公钥,私钥
	生成签名:
		对原始数据进行哈希运算 -> 散列值
		使用非对称加密的私钥,对散列值进行签名(私钥加密) -> 密文
		得到的密文就是数字签名
	签名的校验:
		校验会受到签名者发送的数据
			原始数据
			数字签名
		对接受的数据进行哈希运算 -> 散列值
		使用非对称加密的公钥,对数字签名进行解密->明文==签名者生成的散列值
		校验者的散列值和签名者的散列值进行比较
			相同->校验成功,数据属于签名的人
			失败->数据不属于签名的人
	弊端:
		接收公钥的人没有办法校验公钥的所有者
		
6.证书
	有一个受信赖的机构CA对某人的公钥进行数字签名
		ca有一个密钥对
			使用ca的私钥对某个人的公钥进行加密->证书
				

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值