基于HTTP协议的共享目录

目录

一、什么是共享目录?

二、实现流程:

三、线程池创建

四、TCP建立网络通信

五、HTTP请求:

六、HTTP响应:

      列表展示、 文件下载文件上传

七、断点续传分块传输


一、什么是共享目录?

       共享目录本质上就是一个实现了一个支持多人同时访问的HTTP服务器,在这个服务器中实现了目录列表、文件下载和文件上传的功能。

二、实现流程:

 

三、线程池创建

        ●一定数量的线程加上一个任务队列构成了线程池。当链接到来时创建任务,直接将任务放进线程池中由已有线程处理任务。这样就减少了线程创建和销毁的时间,由线程处理任务也减少了资源的消耗。

class HttpTask
{
public:
	//设置任务,也就是对于这个任务类进行初始化
	void SetHttpTask(int sock, Handler handler);
	//任务处理函数
    void Run();
};

class ThreadPool
{
	//完成线程创建,互斥锁/条件变量初始化
	bool ThreadPoolInit();
	//线程安全的任务入队
    bool PushTask(HttpTask& tt);
	//线程安全的任务出队
    bool PopTask(HttpTask& tt);
	//销毁线程池
	bool ThreadPoolStop();
};

四、TCP建立网络通信

          TCP通过Socket API建立网络通讯,使用TCP是因为TCP协议有一大特征就是它通过确认应答机制,确认序号等一系列措施保证它是可靠的,我们要在服务器上进行数据文件的传输,那么就要保证其可靠性,不能丢失数据

             socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//创建

		bind(_serv_sock, (sockaddr*)&lst_addr, len);//绑定

		listen(_serv_sock, MAX_LISTEN);//监听

五、HTTP请求:

1.获取HTTP请求的数据:通过socket接收http数据

2.解析首行和头部:解析出首行的请求方法、版本号和url。

                                解析出头部中K:V键值对放到一个map中。

3.对外提供获取请求的接口

class HttpRequest
{
private:
	int _cli_sock;
	std::string _http_header;
	RequestInfo _req_info;

public:
	HttpRequest(int sock)
		: _cli_sock(sock)
	{}

	//接收http请求头
	bool RecvHttpHeader();
	//解析http请求头
	bool ParseHttpHeader();
	//向外提供解析结果
	RequestInfo& GetRequestInfo();
};

六、HTTP响应:

       响应文件请求:目录列表展示、文件下载

       响应CGI请求:上传文件

1.列表展示:

如果解析出的HTTP请求是一个文件请求,且该文件是一个目录,就进行文件列表展示。

1>浏览目录,获取该目录下所有的文件信息。

#include <dirent.h>

int scandir( const char *dir,struct dirent ***namelist,int (*filter) (const void *b),int ( 
            * compare )( const struct dirent **, const struct dirent ** ) );

//扫描dir目录下(不包括子目录)满足filter过滤模式的文件,fillter为0表示不过滤
//返回的结果是compare函数经过排序的,并保存在namelist中
//第四个参数alphasort按字母排序和versionsort按版本排序

2>组织HTTP响应头部:先组织首行再组织头部。这里要注意一个目录下可能有很多的文件,如果遍历一遍所有的文件计算出content-length响应回去,效率太低,因此这里采用分块传输(Transfer-Chuncked),每次传输body的一部分内容。

//Transfer-Encoding: chunked\r\n\r\n  分块传输

    //chunked发送数据的格式
    //假设发送hello 
    //5\r\n        :发送数据的大小
    //hel\r\n       :发送数据    
    //2\r\n
    //lo\r\n
    //0\r\n\r\n       :发送最后一个分块

3>组织html展示页面。

<html>
    <head>
        <title>Index of /</VisionHou>
        <meta charset='UTF-8'>
    </head>
    <body>
        <h1>hc's Dir/</h1>
        <hr />
        <ol>
            <li>
                <strong><a href='/./'>./</a></strong><br />
                <small>modified: Tue, 15 Feb 2019 13:48:04 GMT<br />
                directory - 4 kbytes<br />
                <br />
                </small>
            </li>
        </ol>
        <hr />
    </body>
</html>

4>发送HTTP头部。

5>发送正文。

2.文件下载:

如果解析出的HTTP请求是一个文件请求,且该文件不是目录,就进行文件下载。

1>获取文件信息

2>组织HTTP响应头部信息

3>发送响应头部:先发首行,再发送头部

4>发送文件数据。

3.文件上传:

      如果解析出的HTTP请求是一个CGI请求,就进行CGI响应即文件上传。文件上传成功,就在该目录下创建一个了文件,然后将请求中的body放进文件中。
 1>创建管道:由于父子进程双向通信,所以创建两个管道,一个传数据,一个获取结果。

2>创建子进程:fork()。

3>设置子进程环境变量:HTTP请求头以K:V形式存在,使用环境变量传递头信息。

4>程序替换:子进程程序替换后对管道的文件描述符会发生改变,因此程序替换前必须进行文件描述符的复制。

     ●CGI程序:通过父进程获取的正文数据提取文件数据并将其写入文件。

     ●头部中的Content-Type中有一个boundary分隔符,用于对正文部分进行解析,将正文数据分割。

POST /upload HTTP/1.1
Host: 192.168.129.129:9999
Connection: keep-alive
Content-Length: 351
Cache-Control: max-age=0
Origin: http://192.168.129.129:9999
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryQx2dIaIMSojqLEYy
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://192.168.129.129:9999/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
PHYS PATH/home/houcong/Documents/ShareDir/www/upload
content:[------WebKitFormBoundaryQx2dIaIMSojqLEYy
Content-Disposition: form-data; name="FileUpload"; filename="hello.txt"
Content-Type: text/plain

hello world!
------WebKitFormBoundaryQx2dIaIMSojqLEYy
Content-Disposition: form-data; name="FileUpload"; filename=""
Content-Type: application/octet-stream


------WebKitFormBoundaryQx2dIaIMSojqLEYy--
]

处理正文数据,实现文件上传:

		//----boundary 
		//first_boundary:   ------boundary 
		//middle_boundary:  \r\n------boundary\r\n 
		//last_boundary:    \r\n------boundary--

1>获取content-type中的boundary

2>从正文起始位置匹配first_boundary,获取上传文件名称,打开文件。

3>循环从剩下正文匹配middle_boundary,将该位置之前数据存储到文件中。

4>遇到last_boundary,将该位置之前数据存储到文件中,文件上传结束。

七、断点续传分块传输

      文件在下载或上传时,有可能遇到网络故障而暂停下载/上传,那么下次下载/上传时我们希望从上次暂停的地方继续,而没有必要重新再来,这样可以节省时间提高效率。

1.Range/Content-Range:

   1>Range:客户端发请求时用的是Range,制定第一个字节和最后一个字节的位置

   //格式 Range:(unit=first byte pos)-[last byte pos]
    Range: bytes=0-100 表示第 0-100 字节范围的内容 
    Range: bytes=-100 表示最后 100 字节的内容 
    Range: bytes=100- 表示从第 100 字节开始到文件结束部分的内容 
    Range: bytes=0-0,-1 表示第一个和最后一个字节 
    Range: bytes=300-500,501-999 多个范围

   2>Content-Range:用于服务器的响应头中,在发出Range请求后,服务器会在 Content-Range返回当前接收的范围和总大小。

//格式Content-Range: bytes (unit first byte pos) - [last byte pos]/[all length]
      Content-Range: bytes 0-100/1000

   注意:使用断点续传的状态码是206,不是200

HTTP/1.1 200 Ok//一般情况
HTTP/1.1 206 Partial Content//使用断点续传

2.Last-Modified/Etag/If-Range:

     有一种情况,客户端发起续传请求时,服务器端对应文件已经被改变,直接续传就会出错,通过 Last-Modified和 ETag 标识该文件是唯一的。

  1>Last-Modified:

     If-Modified-Since :由客户端向服务器发送的HTTP 头信息,记录最后修改时间。

     Last-Modified:由服务器向客户端发送的HTTP 头信息,记录最后修改时间。

     客户端通过 If-Modified-Since 将先前服务器端发过来的 Last-Modified 最后修改时间戳发送回去,让服务器端判断客户端的页面是否是最新的:

          ●如果不是最新的,则返回新的内容;

          ●否则返回 304 告诉客户端页面是最新的,客户端就可以直接从本地加载页面,不用再次下载。
   2>Etag :

    ●一般是一串长的数字串 “589-4d648041f6c76” ,标识文件的唯一性。

    ●Etag 由服务器端生成,客户端通过 If-Range 来验证资源是否修改。

    ●如果请求报文中的 Etag没有发生变化,则应答报文的状态码为 206。发生了变化,应答报文的状态码为 200。

   3>If-Range:

    ●判断文件是否发生改变,如果未改变,服务器发送客户端剩余接收的部分,否则发送整个文件。If-Range用 Etag 或者 Last-Modified作为返回值。

    ●必须和Range配套使用。

//格式:If-Range: Etag | HTTP-Date
       If-Range: “627-4d648041f6b80” 
       If-Range: Fri, 22 Feb 2013 03:45:02 GMT

 

●下载和上传后的文件可以通过计算MD5值判断文件是否正确:

//两个值计算出来一样表示文件上传成功
//Linux:
  md5sum filename
//Windows:
  certuitl-hashfile filename MD5

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值