项目(5)(部署web页面实现文件上传操作、post提交数据的四种格式、上传文件流程梳理、安装插件fastdfs-nginx-module、安装storage服务器)

部署web页面实现文件上传操作

 

1、将zyFile2拷贝到ngixn安装目录

/usr/local/nginx/

zyFile2获取链接:https://pan.baidu.com/s/16CIznNCMihlhpCHAh6q12Q

2、修改nginx.conf文件

zyFile2中是成形的网页,直接拿来使用。设置静态首页为demo.html,该网页提交表单有一个upload事件,将该事件与fcgi进行绑定。

配置好nginx之后,nginx.conf为(删掉了无用的注释部分)

worker_processes  1;

events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;

    keepalive_timeout  65;

    server {
        listen       80;
        server_name  localhost;

        #部署静态网页
        location / {
            root   zyFile2;
            index  demo.html;
        }

		#upload事件绑定端口为8002
		location /upload{
			fastcgi_pass localhost:8002;
			include fastcgi.conf;
		}
        
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
   
    }
}

3、编写一个fastcgi程序

将上传的文件的内容 - 提交到fastdfs存储节点

  • 将上传文件内容保存到web服务器
  • 调用fastdfs api将文件上传到fastdfs存储节点上
  • 得到一文件ID
  • 将文件和对应的fastdfs返回的文件ID存储到mysql数据库
  • 将上传之后的文件从web服务器删除

用于上传文件的程序源代码(模仿echo.c,调研fcgi的api):

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

#include "fcgi_config.h"
#include "fcgi_stdio.h"

#include "fdfs_api.h"
#include "deal_mysql.h"
#include "make_log.h"


//full_data字符串首地址,full_data_len字符串长度,substr是要匹配的字符串
char* memstr(char* full_data, int full_data_len, char* substr){
    if (full_data == NULL || full_data_len <= 0 || substr == NULL)
        return NULL;

    if (*substr == '\0')  return NULL;

    int sublen = strlen(substr);
    char* cur = full_data;
    int last_possible = full_data_len - sublen + 1;
    for (int i = 0; i < last_possible; i++) {
        if (*cur == *substr) {
            if (memcmp(cur, substr, sublen) == 0) {
                // found
                return cur;
            }
        }
        cur++;
    }
    return NULL;
}

//提取http请求里面的数据(就是两个分界线中间的部分)
int get_file_content(char* begin, char* end, int len, char* filename){
    char *p = NULL;
    // 取出并保存分界线字符串
    char boundary[256] = {0};
    // 从大串里面匹配子串“\r\n”。使得p 指向该行的\r字符
    p = strstr(begin, "\r\n");
    //拷贝begin到p-begin之间的字符(p指向了\r)
    strncpy(boundary, begin, p-begin);
    // p指向下一行行首
    p += 2;
    // 剩余字符串长度
    len -= (p-begin);
    printf("boundary: %s", boundary);

    // begin记录第二行行首位置
    begin = p;
    // content-disp, 保存到begin中
    p = strstr(begin, "\r\n");
    // 移动到下一行行首
    p += 2;
    // 剩余数据长度
    len -= (p-begin);
    // 将此行的filename提取出来,pt指向 f
    char* pt = strstr(begin, "filename=");
    //将pt向后移动,为了让他指向文件名
    pt += strlen("filename=");
    // 指针指向文件名的第一个字符
    pt ++;
    // 找到文件名的结尾字符
    char* q = strchr(pt, '"');
    // 保存文件名
    strncpy(filename, pt, q-pt);
    printf("<br>filename: %s<br>", filename);

    //查找本行的 content-type
    begin = p;
     // 跳过\r\n和空行(\r\n)
    p += 4;
    // 剩余数据长度
    len -= (p-begin);

    // 文件内容正式开始
    begin = p;
    // 从剩余数据块中找到结束分界线(分界线字符串存储在了boundary中)。
    p = memstr(begin, len, boundary);
	//说明当前串中不包含分界线
    if(p == NULL) {
        // 减去\r\n长度
        p = end - 2;
    }
    else {
        // 使得p指向\r\n
        p -= 2;
    }

    // 将上传文件内容写入文件中
    int fd = open(filename, O_CREAT|O_WRONLY, 0664);
    write(fd, begin, p-begin);
    close(fd);

    return 0;
}

int store_data(char* filename, char* fileid){
    // 连接数据库
    MYSQL* conn = NULL;
    //数据库名为test,用户名和密码都是root。msql_conn函数在deal_mysql.h中定义
    conn = msql_conn("root", "root", "test");
    if(conn == NULL){
        LOG("upload_file", "mysql", "数据库连接失败!");
        return -1;
    }
    // 设置数据库编码,否则,中文插入乱码
    mysql_query(conn, "set names utf8");
    // 插入数据
    char buf[1024];
    sprintf(buf, "insert into file (name, fileid) values ('%s', '%s')",
            filename, fileid);
    printf("<br> sql: %s<br>\n", buf);
    // 执行sql语句
    // 如果成功,它返回0。
    if ( mysql_query (conn, buf) != 0 ) {
        LOG("upload_file", "mysql", "数据插入失败");
        return -1;
    }
    // 关闭数据库连接
    mysql_close(conn);
    return 0;
}

int main (){
    //保证cgi程序不退出,阻塞等待请求
    while (FCGI_Accept() >= 0){
        int len = 0;
        //获取post数据的长度。环境变量CONTENT_LENGTH对应的值是一个字符串
        char *contentLength = getenv("CONTENT_LENGTH");
        printf("Content-type: text/html\r\n"
                               "\r\n");

        if (contentLength != NULL){
            //将字符串转整型值
            len = strtol(contentLength, NULL, 10);
        }

        if (len <= 0){
            printf("No data from standard input.<p>\n");
        }
        else {
            int ch;
            char filename[128] = {0};
            // 根据post数据长度, 创建对应大小的数组
            char* file_data = (char*)malloc(len);
            char *begin, *end, *p;
            // 指针初始化
            end = NULL;
            begin = p = file_data;

            for (int i = 0; i < len; i++){
                if ((ch = getchar()) < 0){
                    printf("Error: Not enough bytes received on standard input<p>\n");
                    break;
                }
                *p = ch;    // 每读一个字符,就将字符的值赋值给p
                p++;        //最后将所有的数据都存储在file_data里面
            }
            // end 指向文件结束位置
            end = p;

            // 得到文件內容
            get_file_content(begin, end, len, filename);

            // 文件上传到fastdfs
            char fileid[1024] = {0};
            //fileid是一个传出参数,用以记录文件id
            //fdfs_upload_file在fdfs_api.h中定义
            fdfs_upload_file(filename, fileid);
            printf("<br>fileid: %s\n<br>", fileid);

            // 将数据存储到数据库中
            store_data(filename, fileid);

            // 释放内存
            free(file_data);
            // 删除文件
            unlink(filename);
        }
    } /* while */

    return 0;
}

4、写一个脚本,实现启动spawn-fcgi

#!/bin/bash
#确保启动的时候能启动成功,如果之前开过该进程,则先杀死该进程
# stop
kill -9 $(ps aux | grep "./bin_cgi/upload.cgi" | grep -v grep | awk '{print $2}')

# start
spawn-fcgi -a 127.0.0.1 -p 8002 -f ./bin_cgi/upload.cgi

5、再写一个脚本,用于启动fastdfs(项目(1)中的内容)

#!/bin/bash

# 关闭tracker 和 storage服务
tracker_start(){
    ps aux | grep fdfs_trackerd | grep -v grep > /dev/null
    if [ $? -eq 0 ];then
        echo "fdfs_trackerd 已经在运行中, 无需启动..."
    else
        sudo fdfs_trackerd  /etc/fdfs/tracker.conf
        if [ $? -ne 0 ];then
            echo "tracker start failed ..."
        else
            echo "tracker start success ..."
        fi
    fi
}

storage_start(){
    ps aux | grep fdfs_storaged | grep -v grep > /dev/null
    if [ $? -eq 0 ];then
        echo "fdfs_storaged 已经在运行中, 无需启动..."
    else
        sudo fdfs_storaged  /etc/fdfs/storage.conf
        if [ $? -ne 0 ];then
            echo "storage start failed ..."
        else
            echo "storage start success ..."
        fi
    fi
}

echo "Operation:"
echo "  start storage please input: storage"
echo "  start tracker please input: tracker"
echo "  start storage && tracker please input: all"
echo "  stop storage && tracker input: stop"

read ARG

case $ARG in
    storage)
        storage_start
        ;;
    tracker)
        tracker_start
        ;;
    all)
        storage_start
        tracker_start
        ;;
    stop)
        sudo fdfs_trackerd /etc/fdfs/tracker.conf stop
        sudo fdfs_storaged /etc/fdfs/storage.conf stop
        ;;
    *)
        echo "nothing ......"
esac

6、测试一下

图片的上传功能成功了

7、查看机器里面的fastDFS里面是否真的存储了上传的文件

进入fastDFS的storage目录

根据屏幕上上传成功的返回值

查找对应的文件

图片真的存储在了storage里面了。

post提交数据的四种格式

post提交数据,而不是get,是出于安全考虑。get提交数据会把数据的内容写在http请求里面,这样不安全。

概述

  1.  HTTP 请求分为四个部分:状态行、请求头、空行消息、主体
  2. 协议规定 POST 提交的数据必须放在消息主体(entity-body)中,但协议并没有规定数据必须使用什么编码方式。
  3. 开发者完全可以自己决定消息主体的格式
  4. 数据发送出去,还要服务端解析成功才有意义, 服务端通常是根据请求头(headers)中的 Content-Type 字段来获知请求中的消息主体是用何种方式编码,再对主体进行解析。

post提交数据的四种格式

第一种,content-Type: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&sub%5B%5D=1&sub%5B%5D=2&sub%5B%5D=3

第二种,application/json(后面是json数据)

POST http://www.example.com HTTP/1.1

Content-Type: application/json;charset=utf-8

{"title":"test","sub":[1,2,3]}

第三种,text/xml(后面是xml数据)

POST http://www.example.com HTTP/1.1

Content-Type: text/xml

<!--?xml version="1.0"?-->

<methodcall>

    <methodname>examples.getStateName</methodname>

    <params>

        <value><i4>41</i4></value>

    </params>

</methodcall>

第四种,multipart/form-data(后面跟着实际的数据格式)

------WebKitFormBoundaryPpL3BfPQ4cHShsBz 

Content-Disposition: form-data; name="file"; filename="qw.png"

Content-Type: image/png

 

 PNG

...................................................................................

...................................................................................

------WebKitFormBoundaryPpL3BfPQ4cHShsBz

Content-Disposition: form-data; name="tailor"

 

false

------WebKitFormBoundaryPpL3BfPQ4cHShsBz--

我们提交数据的方式是第四种。

可以看出,提交上来的信息被两行相同的boundary(即------WebKitFormBoundaryPpL3BfPQ4cHShsBz)包裹。因此,如果想解析post上来的信息,需要进行如下操作:

  1. 先读第一行 : 遇到分界线(boundary),说明后面就是我们要存储的内容
  2. 遇到第二个同样的分界线, 说明文件内容结束了.
//big代指整块数据,sub代指big中被包裹的数据,即我们提交的文件内容
char *p = strstr(big, sub)

注意事项:

  • boundary是一个字符串,用来切分数据
  • 在HTML协议中换行使用的是:"\r\n"
  • BODY里面的bounday比HEADER里面的前面都多了“--”

上传文件流程梳理

  1. 浏览器将图片上传给服务器
  2. nginx检测到上传操作,将操作交给fastCGI
  3. fastCGI将收到的数据进行处理,提取出要存储的信息交给fastDFS的client进程进行存储
  4. client进程询问tracker,获取到storage的ip地址和端口号,将数据存储到storage中

下载文件

1、分析用户下载文件的流程

  1. 浏览器访问web服务器
  2. web服务器调用cgi程序
  3. cgi程序需要访问tracker
  4. tracker会提供storage的ip和端口
  5. cgi程序, 去访问storage,下载数据
  6. web服务器需要再次将数据发给浏览器

2、改进下载流程

  • 通过浏览器访问web服务器
  • 拿到一个地址, 直接通过这个地址, 来访问storage上的图片

3、如何实现改进版本?

  • 有一个storage服务器
  • 在上边部署一个nginx服务器
  • 需要在nginx中添加一fastdfs模块

实现功能一通过ip地址+文件路径,让浏览器展示存储在该位置的图片

为了演示安装storage服务器而做的例子

安装插件fastdfs-nginx-module

获取链接:https://pan.baidu.com/s/1wxD3qBltUSEFevbrD9PTIw

第一步。配置nginx

1、进入nginx的安装目录

2、执行 ./configure  --add-module=fastdfs-module的src目录

出现图片内容,则说明配置成功

3、执行make,报错

搜索该文件的路径

进入makefile来将对应的路径添加。

发现makefile中默认执行build操作,而build操作执行的是objs/makefile,因此去修改该makefile

打开objs中的makefile文件,在ALL_INCS中添加对应文件路径(-I指定头文件目录)

之后还会发现缺失头文件,以此类推的继续进行修改即可

4、执行make install

第二步、添加相应的conf文件

重启nginx,发现worker进程没有被开启,查看log发现缺失文件

缺失的三个文件

  1. 拷贝fastdfs-nginx-module/src中的mod_fastdfs.conf/etc/fdfs
  2. 从fastDFS的源码安装包中的conf中 将http.conf 拷贝到 /etc/fdfs
  3. 从nginx的源码安装包中的conf中mime.types拷贝到 /etc/fdfs

第三步、修改mod_fastdfs.conf文件信息

1、修改log日志存储位置(将路径与storage的log存储路径相同)

storage.conf截图

2、修改tracker_server对应的ip

3、检查存储节点的端口

4、检查存储节点属于哪个组

5、设置url_have_group_name的值为true

因为我们希望在url中看到图片的存储路径(这是例子的需求)

6、检查当前存储节点个数和存储路径(与storage.conf要保持一致)

storage.conf的截图

7、修改group_count,以及group_count对应的信息

改完了,累死了。

测试一下,现在nginx启动时,两个进程都能开启了

第四步、修改nginx.conf

该nginx是storage上的nginx。用于方便storage服务器来获取数据。

重启nginx之后测试一下。通过路径在浏览器中展示storage中存储所图片的功能实现

./nginx -s reload

猜你喜欢

转载自blog.csdn.net/qq_29996285/article/details/87810411
今日推荐