基于HTTP的文件共享服务器

基于TCP、HTTP的文件共享服务器

服务器的基础架构

构建TCP服务器

对于文件的传输来说,我们必须要保证文件内容的准确性。所以在数据传输时我们选择保证数据可靠传输的TCP协议。

  • 首先使用socket创建套接字进行对端口号与IP地址进行绑定。
  • 然后监听来自客户端的请求。
  • 连接任务的处理
  • 对于来自客户端的连接请求,我们使用多线程对每个请求进行处理。能够保证并发性的请求处理。
  • 创建线程任务,将线程任务添加到线程池中。

考虑到线程的创建时仍需要时间并占用服务器的资源,为了能够提升请求处理的效率,使用线程池来对连接请求的处理。

线程池

线程池的本质就是提前创建多个线程,并与一个任务队列进行配合使用,即让任务出队进行处理操作。

  • 使用互斥锁是其对于任务队列的访问是线程安全的。
  • 使用条件变量保证线程之间的同步,解决线程之间的饥饿问题。
具体流程:
  1. 初始化线程池时, 创建多个线程,并对其进行分离。
  2. 当线程获从任务队列中获取任务时,发现任务对列为空,就会在条件变量下进行等待当线程获从任务队列中获取任务时,发现任务对列为空,就会在条件变量下进行等待。
  3. 当有连接到达时,将其加入任务队列,此时唤醒在条件变量下进行等待的函数,使其中的线程对任务队列中的任务进行处理。当有连接到达时,将其加入任务队列,此时唤醒在条件变量下进行等待的函数,使其中的线程对任务队列中的任务进行处理。
  4. 当线程获取到任务时,调用http任务处理函数,该线程负责与客户端进行通信。当线程获取到任务时,调用http任务处理函数,该线程负责与客户端进行通信。
  5. 当一个线程处理完一个连接请求后, 就会断开来连接, 继续任务队列中获取任务,如果任务队列为空就会在条件变量下进行等待。 直到有任务抵达,线程被唤醒。

线程池任务操作

  • 获取任务
  • 执行任务(HTTP请求接收+HTTP请求解析、HTTP响应:列表/下载/上传)
对与HTTP请求的处理

当线程获得连接任务后,执行任务处理函数,与客户端建立连接,接收来自浏览器的HTTP请求数据。

头部信息的解析

首先对于http头部进行解析,将请求行与首部字段进行分离,并分析其请求行中的请求方法、url、版本协议。并对http首部中的首部字段进行存储。

对请求进行处理

当首部信息解析完毕后,根据请求行中的URL,进行判断是否为CGI请求。
如果是不是CGI请求,就执行文件请求处理,如果是CGI请求, 就进行CGI请求的处理。

如何判断为是否为CGI请求?

  • 当请求方法为GET且查询字符串不为空
  • 请求方法为POST方法
对文件请求的处理

对文件的请求处理分为文件列表展示,与文件下载两个部分。我么你需要对两个功能分别进行不同的实现。

文件列表的展示

当通过判断url 的请求路径为目录时,就执行文件目录展示函数,组织响应的头部内容以及正文部分,使用Transfer-Econding: chunked 进行分块传输。因为文件目录中的文件数是不确定的,我们无法返回准确的Content-Length字段,使用chunked以适用不确定的正文传输长度。根据目录下的文件信息,将每一个文件的信息组织成html页面,通过chunked分块传输,在浏览器页面进行展示。在文件展示时,标明文件的大小,文件的类型等信息。

文件的下载请求

对于文件的下载请求, 我们根据头部信息中的请求路径,如果通过判断请求路径不是对于目录的请求,就执行文件下载函数。

首先获得文件的解析过的请求信息,组织文件的响应的头部信息,并在头部信息中标明文件的大小Content-Length, 并将头部信息发送,将文件的数据作为正文部分进行发送。

对CGI请求的处理

通过文件的请求行的信息, 判断请求是否为CGI请求。如果为CGI请求, 就执行文件上传函数。

  • 首先接收文件的CGI的请求信息,对文件的请求进行解析。
  • 使用子进程对于请求进行处理。
  • 如果因为上传文件的程序导致服务端出错,导致服务器程序出现问题,从而使服务器挂掉。为了防止出现这种问题就使用使用子进程对与上传请求进行处理。如果因为上传问文件而出现问题,导致子进程挂掉,并不会影响父进程的运行。

父子进程之间的数据传输问题

如果使子进程对于文件进行处理, 我们需要父进程将请求信息传递给子进程, 而子进程也需要将处理完毕的结果返回给父进程,但对于进程来说,进程在系统中是相互独立的, 并不互相干扰。所以我们就需要考虑进程间的通信问题。

使用管道作为两个进程间的通信方式。在进程通信的几个方式中,共享内存是速度最快的通信方式, 但是在这里我们不使用它,因为共享内存并不保证资源的安全性,不具有同步与互斥。 所以这里我们选择匿名管道,一方面是因为匿名管道适用与具有亲缘关系的进程间的通信,更重要的是其自带同步与互斥。所以给我们提供了很大的便利。
为了提高效率,我们使用环境变量来传递头部的信息,因为环境变量是成键值对的,而HTTP的请求的首部,也是key : value 键值对的关系, 使用环境变量,可以提高子进程处理的时间效率。
所以我们使用双向管道传递文件的数据, 使用环境变量传递请求的首部。

CGI处理流程

  1. 父进程接收到CGI请求,进入文件的上传处理函数。
  2. 首先父进程创建两个匿名管道,用与和子进程之间的请求正文的传输。首先父进程创建两个匿名管道,用与和子进程之间的请求正文的传输。
  3. 父进程通过fork 创建出子进程。父进程通过fork 创建出子进程。
  4. 子进程通过设置环境变量, 将请求的首部信息进行保存。 然后执行程序替换,执行文件上传函数。
  5. 而父进程则此时通过管道向子进程发送所接受的请求的正文部分,在子进程呢个处理完后,将处理结果发送给父进程。
  6. 子进程对于CGI的处理,通过对于从父进程获取的正文数据进行解析,从而进行处理。首先从请求的首部中获取boundary信息。 在Content-Type的首部字段中存放有boundary字段, 我们通过获得boundary的信息,对正文部分进行解析。

POST 以下http的请求是上传文件的常用方式
其Content-Type: 被指定使用multipart/form-data, 然后指定了boundary 的内容

POST http://www.example.com HTTP/1.1 
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA 

------WebKitFormBoundaryrGKCBY7qhFd3TrwA 
Content-Disposition: form-data; name="text" 

title 
------WebKitFormBoundaryrGKCBY7qhFd3TrwA 
Content-Disposition: form-data; name="file"; filename="chrome.png" 
Content-Type: image/png 

PNG ... content of chrome.png ... 
------WebKitFormBoundaryrGKCBY7qhFd3TrwA-- 

首先生成了一个 boundary 用于分割不同的字段,为了避免与正文内容重复,boundary 很长很复杂。然后 Content-Type 里指明了数据是以 mutipart/form-data 来编码,本次请求的 boundary 是什么内容。消息主体里按照字段个数又分为多个结构类似的部分,每部分都是以 --boundary 开始,紧接着内容描述信息,然后是回车,最后是字段具体内容(文本或二进制)。如果传输的是文件,还要包含文件名和文件类型信息。消息主体最后以 --boundary-- 标示结束。
所以整个问文件上传的正文部分由boundary进行分割,通过每一个Boundary传输一个文件。

对请求的正文部分进行解析
正文部分使用boundary进行分割,每一部分的boundary 都表示传输的是相互独立的数据部分
首先对每一个Boundary部分的信息进行处理
Boundary 的划分
由以上可知Boundary分为三种

第一个boundary : ----boundary前面会多加两个--
中间部分的boundary: --boundary 中间的分隔符,可能有多个
最后一个boundary: --boundary-- 在最后添加--

在每部分的boundray中, 首先是boundary分割符,紧挨着就是内容的描述信息,之后使用一个空行分隔, 空行后是正文部分,正文部分的后面又紧跟着下一个boundary。
在每个boundary后面的描述信息后,如果要上传我文件, 后面就会有文件名字段‘filename’, 我们可以通过此来获取创建的文件名。
当获取到文件名时, 就创建文件, 并将后面的正文部分写入到文件中。

项目不足

未实现断点续传:

FTP(文件传输协议的简称)(File Transfer Protocol、 FTP)客户端软件断点续传指的是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载。用户可以节省时间,提高速度。

附代码:https://github.com/JochebedJX/HttpServer

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值