本篇文章不會(huì)涉及到很多復(fù)雜的概念,也沒(méi)有寫(xiě)很難讀懂的模板函數(shù),代碼簡(jiǎn)單可讀,本篇文章送給每一個(gè)想自己用C++寫(xiě)一個(gè)http服務(wù)器的小伙伴!高手們、大佬們當(dāng)然可以不用看的啦!
正文
怎么寫(xiě)一個(gè)簡(jiǎn)單的http服務(wù)器阿?很簡(jiǎn)單,只需要返回最基本的3個(gè)東西即可。
-
狀態(tài)碼
-
發(fā)送文件的長(zhǎng)度
-
發(fā)送文件的類(lèi)型
?
狀態(tài)碼如200(找到請(qǐng)求文件)、404(未找到請(qǐng)求文件),我實(shí)現(xiàn)的也比較簡(jiǎn)單,就實(shí)現(xiàn)了這兩個(gè)狀態(tài)碼。
文件長(zhǎng)度比如客戶(hù)請(qǐng)求的是index.html頁(yè)面,瀏覽器如何知道收到的這個(gè)文件什么時(shí)候結(jié)束呢?就靠是文件的長(zhǎng)度
文件類(lèi)型?html的類(lèi)型是html格式,css的是css格式,圖片有圖片的格式,zip有zip格式文件格式對(duì)應(yīng)的文件類(lèi)型表(https://tool.oschina.net/commons)
返回給客戶(hù)端三個(gè)這種東西加上請(qǐng)求的文件即可(存在請(qǐng)求文件的情況下)可以了
Http工作流程
在這個(gè)部分大概介紹一下大概的http的工作流程??蛻?hù)端通過(guò)網(wǎng)址訪問(wèn)到你的網(wǎng)站(一定要記住客戶(hù)端是主動(dòng)請(qǐng)求連接的,服務(wù)端是被動(dòng)連接的),實(shí)則就是通過(guò)ip+port訪問(wèn)的,只不過(guò)http的默認(rèn)端口號(hào)是80。比如你還沒(méi)有域名、云服務(wù)器這些東西,那如何在本地測(cè)試呢?就是在瀏覽框輸入ip:port,例如127.0.0.1:9996,按下回車(chē)就可以在本地訪問(wèn)自己的http服務(wù)器了。當(dāng)然了http要給客戶(hù)(請(qǐng)求者)一個(gè)首頁(yè),當(dāng)客戶(hù)沒(méi)有指定網(wǎng)頁(yè),單純的打出域名或者127.0.0.1:9996,就給他一個(gè)默認(rèn)的首頁(yè),這也是我們要實(shí)現(xiàn)的事情。客戶(hù)寫(xiě)了請(qǐng)求文件,我們來(lái)判斷是否存在,存在就返回狀態(tài)碼200和請(qǐng)求文件的內(nèi)容。不存在就直接返回404。那我們?nèi)绾闻袛喟?,拿最?jiǎn)單的GET為例子吧,其他也大同小異,有興趣的同學(xué)可以自行研究其他的。我們利用正則表達(dá)式來(lái)解析客戶(hù)發(fā)來(lái)的請(qǐng)求是GET還是POST還是其他請(qǐng)求,在解析出來(lái)要請(qǐng)求文件。再利用狀態(tài)機(jī)思想來(lái)文件是否存在,若存在在判斷文件的類(lèi)型。大概的流程就是這些。
Http.h
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
class TcpClient;
class Http{
// 文件的根目錄
const std::string path_ = "./www/dxgzg_src";
std::string filePath_;// 具體文件的絕對(duì)路徑
std::string fileType_;// 請(qǐng)求文件的類(lèi)型
std::string header_; // http頭response
int fileSize_;
int fileFd_;
struct stat fileStat_;
// POST請(qǐng)求的話將留言保存到本地文件中
bool isPostMode_ = false;
public:
Http() = default;
void addHeader(const std::string& head);
void Header(bool flag);
// 把一些頭文件的信息都加進(jìn)來(lái),只有成功的時(shí)候調(diào)用這個(gè)函數(shù),
// 并返回文件中的數(shù)據(jù)
void processHead();
// 把請(qǐng)求文件的路徑加上
void addFilePath(const std::string& requestFile);
// 獲取文件的類(lèi)型
void analyseFileType(const std::string& requestFile);
bool analyseFile(const std::string& request);
void SendFile(int clientFd,bool isRequestOk);
bool fileIsExist();
// 用戶(hù)自定義的回調(diào)函數(shù)要正確的處理異常和自己負(fù)責(zé)關(guān)閉套接字
void ReadCallback(TcpClient* t);
};
?
Http.cpp
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
- ?
// 用于test
using namespace std;
void Http::addHeader(const string& head)
{
if (!head.empty())
{
header_ += head;
header_ += " ";
// Console.WriteLine("我在這里 head!= null" + header_);
}
// 自動(dòng)加個(gè)結(jié)尾
else
{
header_ += " ";
// Console.WriteLine("我在這里 head == null" + header_);
}
}
void Http::Header(bool flag)
{
// 判斷要發(fā)送的頭部 true 表示200 false 表示404
if(flag == true)
{
header_ = "HTTP/1.1 200 OK ";
}
else
{
header_ = "HTTP/1.1 404 NOTFOUND Content-Length:0 ";
}
}
void Http::processHead()
{
string ContentType = "Content-Type:";
if (fileType_ == "html")
{
ContentType += "text/html";
}
else if(fileType_ == "js")
{
ContentType += "application/x-javascript";
}
else if(fileType_ == "css")
{
ContentType += "text/css";
}
else if(fileType_=="jpg" || fileType_== "png")
{
ContentType += "image/" + fileType_;
}
else if (fileType_== "zip" || fileType_ == "tar")
{
ContentType += "application/" + fileType_;
}
addHeader(ContentType);
// 代完善,要打開(kāi)文件 filePath_是請(qǐng)求文件的路徑
fileSize_= fileStat_.st_size;
string ContentLength = "Content-Length:" + to_string(fileSize_);
addHeader(ContentLength);
// 最后加了一個(gè)結(jié)尾
addHeader("");
// Console.WriteLine("process fileContent_:" + );
}
void Http::addFilePath(const string& requestFile)
{
filePath_ += requestFile;
}
void Http::analyseFileType(const string& requestFile)
{
for (int i = 0; i < requestFile.size(); ++i)
{
if (requestFile[i] == '.')
{
// 獲取請(qǐng)求文件以什么結(jié)尾的
fileType_ = requestFile.substr(i + 1);
}
}
}
bool Http::fileIsExist(){
fileFd_ = ::open(filePath_.c_str(),O_CLOEXEC | O_RDWR);
if (fileFd_ < 0)
{ // 說(shuō)明為找到請(qǐng)求的文件
return false;
}
return true;
}
bool Http::analyseFile(const string& request)
{
// 調(diào)用header的
// 在[]的^是以什么什么開(kāi)頭,放在[]里面的是非的意思
string pattern = "^([A-Z]+) ([A-Za-z./1-9-]*)";
regex reg(pattern);
smatch mas;
regex_search(request,mas,reg);
// 因?yàn)橄聵?biāo)0是代表匹配的整體
if(mas.size() < 3){
LOG_INFO("不是正常請(qǐng)求");
// 啥都不是直接返回false
return false;
}
string requestMode = mas[1];
if(requestMode == "POST"){
isPostMode_ = true;
cout << "POST請(qǐng)求?。。。。? << endl;
}
// 請(qǐng)求的具體文件
string requestFile = mas[2];
// 先獲取請(qǐng)求的文件
bool flag;
if (requestFile == "/")
{ // 如果是/的話就給默認(rèn)值
filePath_.clear(); // 先清個(gè)零
filePath_ = path_;
filePath_ += "/run.html";
// 文件的類(lèi)型也要給人家加上
fileType_ = "html";
}
else
{
filePath_.clear(); // 先清個(gè)零
filePath_ = path_;
addFilePath(requestFile);
// 利用open函數(shù)
}
flag = fileIsExist();
// 未找到文件的話
if(!flag){
LOG_INFO("未找到客戶(hù)要的文件");
cout << filePath_ << endl;
return false;
}
::fstat(fileFd_,&fileStat_);
// 如果文件不存在的話也就不需要解析類(lèi)型
analyseFileType(requestFile);
return true;
}
void Http::SendFile(int clientFd,bool isRequestOk)
{
long len = 0;
// 頭部一定是有的。
while(len < header_.size()){
len += ::send(clientFd,header_.c_str(),header_.size(),0);
cout << "len header" << header_ <<endl;
}
// 發(fā)完了頭,在發(fā)請(qǐng)求文件的信息。如果是404這里是沒(méi)有的
if (isRequestOk == true)
{
len = 0;
int num = 0;
int tmpLen = 0;// 連續(xù)好幾次沒(méi)變的話就加一個(gè)num
while (len < fileSize_)
{
// 發(fā)送的文件個(gè)數(shù)已經(jīng)寫(xiě)入在len當(dāng)中了
::sendfile(clientFd,fileFd_,(off_t*)&len,fileStat_.st_size- len);
cout << "len sendfile" <<"len:" << len << "fileSize" << fileSize_ <<endl;
if(len <= 0 ){
break;
}
if(tmpLen == len){
++num;
if(num > 10){
break;
}
}
tmpLen = len;
}
}
}
void Http::ReadCallback(TcpClient* t){
cout << "ReadCallback" << endl;
int sockFd = t->getFd();
char buff[1024];
int r = ::recv(sockFd,buff,sizeof(buff),0);
if (r == 0)
{
t->CloseCallback();
return;
}
buff[r] = '';
string str = buff;
cout << str << endl;
// 未找到文件直接回應(yīng)404.
bool flag = analyseFile(str);
Header(flag);
if(!flag){
SendFile(sockFd,false);
// t->CloseCallback();
return ;
}
// 這個(gè)修改頭文件的,先調(diào)用這個(gè)
processHead();
//這是文件找到了發(fā)送的
SendFile(sockFd,true);
if(isPostMode_){
int fd = ::open("./postLog/message.txt",O_RDWR);
if(fd < 0){
LOG_ERROR("未找到文件");
}
else{
// 文件偏移到末尾
::lseek(fd,0,SEEK_END);
::write(fd,str.c_str(),str.size());
close(fd);
}
isPostMode_ = true;
}
// 關(guān)閉文件套接字
close(fileFd_);
// 發(fā)完就關(guān)閉連接,主要是為了多去幾個(gè)線程還能跑的快一些
//t->CloseCallback();
}
不考慮高并發(fā)的情況,設(shè)計(jì)一個(gè)同步阻塞的epoll即可,看完http必備的三要素已經(jīng)能夠?qū)懗鲆粋€(gè)服務(wù)器了,我的底層socket采用的是自己封裝的網(wǎng)絡(luò)庫(kù),Reactor模型,one loop per thread的代碼文件比較多,所以就沒(méi)有放上來(lái),但只要把狀態(tài)碼、文件類(lèi)型(那一大段if)、文件的長(zhǎng)度這三個(gè)實(shí)現(xiàn)了就可以搭建一個(gè)簡(jiǎn)易的http服務(wù)器了。可以利用sendfile零拷貝來(lái)發(fā)送文件
在補(bǔ)充一點(diǎn)就是,http協(xié)議是 結(jié)尾。最后還有一個(gè) ,就比如404,HTTP/1.1 404 NOTFOUND Content-Length:0 ,最后面再跟一個(gè) ,結(jié)束一段跟一個(gè)。
評(píng)論
查看更多