基于Http的share服务器

学了Tcp、Http协议,线程池、epoll多路转接这些知识之后,决定将它们串起来,写一个共享资源服务器,当然由于能力有限,目前仅限于目录浏览、下载及上传功能。下面对整个流程作一简要总结:首先是搭建tcp服务器,实现服务器和客户端的连接,将epoll添加,epoll开始监听,获取到就绪的列表,并判断这个列表中的套接字是不是监听套接字,如果是的话,接收一下,如果不是的话,就组织一个任务抛进线程池中,线程中有个任务类和线程池类,任务类主要是组织任务,线程池类主要是线程池入口及任务入队。客户端发送请求时,在线程池中传入处理函数,对请求进行解析,解析的话就是Http中的事了,对请求进行解析,解析完毕后组织响应,发送出去。

接下来是具体每个类的实现细节,代码就不贴了,有需要的留言哦。。。。。
Server类
首先得有个Start()函数,这个函数的作用是让服务器跑起来
bool Start()
这里边主要首先是各种初始化,包括套接字初始化,线程池的初始化以及epoll的初始化。
初始化完毕之后要将epoll添加进去,开始监听,epoll要的是一个列表,然后就开始获取就绪的列表,但是在获取之前先判断这个套接字是不是监听套接字,如果是监听套接字,直接将它接收一下,,并且把客户端的这个套接字设置成非阻塞。如果不是监听套接字,就要组织一个任务,把任务抛进线程池里边,并且还要把这个套接字从epoll中移走。完了之后关闭套接字。
static void ThreadHandler(int sockfd)
接下来要定义这个回调函数,就是在线程池中自己传入的这个函数,首先是从客户端接收请求数据,进行请求解析,如果响应状态码不等于200,说明解析失败,直接调用错误处理的函数,响应错误即可,如果解析成功,就要进行具体的http处理,并将结果填充到rsp中,并将处理结果响应给客户端,完了之后,关闭套接字。
static bool HttpProcess(HttpRequest &req,HttpResponse &rsp)
接下来是具体的http处理
这里传入的路径应该是包含www的实际路径
首先要判断看客户端请求的这个文件是否存在,如果不存在直接返回404,客户端错误,如果这个文件存在的话,分两种情况,一种是如果请求方法是“GET”并且请求字符串大小不为0,或者是“POST”请求的话,就是文件上传请求,就要用CGI外部处理。否则的话就是一个基本的文件下载或是浏览目录请求。那么怎么判断是一个目录列表请求还是文件下载请求,在boost库中有一个is_directory(realpath)这个函数就是专门判断是不是目录的,然后就要调用ListShow函数浏览目录,当然还得用SetHeader()来把头部组织好。
如果是普通文件下载请求,文件下载时有个断点续传,就是在断网的情况下,能接着下载。
首先要在请求头部中找有没有“Range”字段,Range表示的是断点续传时客户端所要求的续传数据范围。如果都找到了请求头部的结尾,说明就不用断点续传了哈,直接就下载完了,这时候就组织一些头部信息,比如Accept-Range,告诉浏览器,我支持断点续传,还有Etag,标识文件的唯一性,看现在还支持断点续传不。否则的话就是需要断点续传了,range是一个范围,文件的续传起始位置的偏移量-文件结束位置的偏移量
RangeDownLoad(std::string &path,std::string &range,std::string body)
断点续传的下载,这个字段格式是:Range:bytes=start-end
找“bytes=”
按照“-”找起始的字节数,与结束的字节数
如果end不存在的话,那么长度就是文件大小-1,然后就是读文件,读到body里头去。
这里还要注意的是偏移量及跳转。
static bool CGIProcess(HttpRequest &req,HttpResponse &rsp)
这部分主要实现的功能是,首先创建管道是为了通信,之后创建子进程,如果当前是子进程,则管道重定向,并且关闭掉不用一端的pipe_in,用于父进程读子进程写,所以要关闭读段, pipe_out用于父进程写(子进程读,关闭写端),然后将这两个各自重定向,之后传递请求信息,将正文写入到子进程中去,写了之后,等待子进程响应,拿到响应之后放到body当中,当响应完毕,没有写端写入了,则直接退出循环,设置响应状态码。
static bool DownLoad(std::string &path,std::string &body)
文件下载,在boost库里面的filesystem里有个file_size(path),给出路径,可以得到文件大小,然后调用read函数,从body[0]的位置开始读,一直读文件大小个就可以了,最后再关闭文件。
static bool ListShow(std::string &path,std::string &body)
这部分主要是将目录节点组织成html,添加到body当中

TcpSocket类
int GetFd():
用于外部类可以获取到这个文件描述符
void SetFd():
设置文件描述符
void SetNonBlock():
将套接字设置为非阻塞
bool SocketInit(int port):
创建套接字,绑定地址信息,但是这里要注意一个问题,服务器如果重启,我们并不希望 服务 器因为端口被占用而导致服务器起不来,再次使用的话,分配的地址肯定就不同了,所以这里用个setsockopt(_sockfd,SOL_SOCKET,SO_REUSEADDR,(void*)&opt,sizeof(int))
,SO_REUSEADDR 就是设置地址重用选项,开启它就是把opt传进去,表示开启地址重用
bool Accept(TcpSocket &sock):
获取新连接,还要在设置一下fd,因为相当于一个socket工作 完了,然后到下一个。再次设置为非阻塞。

bool RecvPeek(std::string &buf):
这个函数就是象征性的 看一下,看看接收到的有没有头部,并不是真正的取出数据
recv()函数,最后一个参数如果是MSG_PEEK就是象征性的接收。
bol Recv(std::string &buf,int len);
这才是真正的接收头部,当前我们是要获取指定长度的数据,但是如果我们没有获取到指定长度的数据怎么办?当然是会一直接收了,直到获取完,所以这里要用到一个while循环,当实际接收的长度小于指定数据长度,就会一直接收,如果缓冲区中没有数据,就不用接收了,怎么判断缓冲区中有没有数据,用errnoEAGAIN
bool Send(const std::string& buf)
发送数据,如果实际接收的数据小于buf的大小的话,就会一直接收
最后再关闭一下就可以了。
Epoll类
bool Init():
初始化epoll类,创建epoll句柄
bool Add(TcpSOcket &sock)
初始化完了之后,就要添加监控了:epoll_ctl()
bool Del(TcpSocket &sock)
,监控的过程中,如果描述符出错了就要移除监控,还是epoll_ctl(),不过参数变一下
bool Wait(std::vector& list,int timeout=3000)
添加完就要开始监控,定义一个epoll_enent evs结构体,将就绪的事件放在evs中,得到就绪的事件个数nfds,如果nfds<0表示监控出错,如果nfds
0,表示等待超时,否则的话,就把就绪的fd放到sock当中,把它插入到就绪的列表当中

线程池类
线程池中有两个类,一个是任务类,一个是线程池类。
任务类:
void SetTask(int data,handler_t handle):组织一个任务
void TaskRun():任务运行函数,具体的处理函数
线程池类:(生产者消费者模型)
void thr_start():线程池入口函数
PoolInit():
线程池的初始化
bool TaskPush(ThreadTask &tt)
任务的入队

Http中的请求类和响应类
1.请求类:HttpRequest:
请求类中成员有:请求方法,URL(请求路径和请求字符串),请求头部以及请求正文这几个部分。
bool RecvHeader(TcpSocket &sock,std::string &header)
首先是接收头部,先试探性的接收头部,找“\r\n\r\n”,如果包含整个头部的话,则真正接收,直接获取头部,把整个头部都拷到header中去。
bool FirstLineParse(std::string &line)
接收完头部之后,首先要解析首行,首行格式为:GET //HTTP/1.1按照空格进行分割,首行是vector类型,vector的大小是3,用boost库中的split函数按照空格进行分割,如果首行vector的大小不是3的话,首行就解析错了,先按照空格进行分割,第0个就是请求方法,第1个就是URL,URL中有用的就是请求路径和查询字符串,按照“?”分割查询路径和查询字符串,问号后边的是查询字符串,查询字符串是一个个的键值对按照“=”分割为key和val
int RequestParse(TcpSocket &sock)
解析一下是个什么样的请求,第一步:接收http头部(这个头部包含首行、头部和空行)
第二步:对整个头部按照\r\n进行分割,由于头部有好多个字段,都是以换行分隔的----得到一个list,第三步:进行首行解析(list的第0个就是首行),第四步,对头部进行分割解析,头部字段都是以“: ”分割的,“: ”之前的是key,之后的是val。接下来是接收正文,在头部里头找“Content-Length”找到之后,才知道正文要接收多长

Http响应类
HttpResponse
私有成员有:响应状态码,响应头部(key=val的键值对),正文
std::string GetDesc()
获取响应状态码所对应的描述信息。
bool SetHeader(const std::string &key,const std::string &val)
设置_headers[key]=val
bool ErrorProcess(TcpSocket &sock)
错误处理的方法,直接就 return true
bool NormalProcess(TcpSocket &sock)
正确响应时,第一步:组织首行,往tmp中添加首行的各项信息(协议版本、响应状态码),加一个换行
第二步:组织头部,有时候头部信息是需要用户自己添加的,比如说这个Content-Length就是用户自己给的,所以还要完善一下,根据正文的长度给出Content-Length,然后按照“: ”的格式添加到tmp中,再通过Send函数发送出去,将正文也发送出去。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值