來(lái)源:Jialuhu 發(fā)布時(shí)間:2019-03-28 14:28:18 閱讀量:1833
一、具體功能實(shí)現(xiàn)
GET方法請(qǐng)求解析
POST方法請(qǐng)求解析
返回請(qǐng)求資源頁(yè)面
利用GET方法實(shí)現(xiàn)加減法
利用POST方法實(shí)現(xiàn)加減法
HTTP請(qǐng)求行具體解析
400、403、404錯(cuò)誤碼返回的處理
二、什么是web服務(wù)器
web服務(wù)器就是在物理服務(wù)器基礎(chǔ)上的具有服務(wù)端功能的網(wǎng)絡(luò)連接程序,簡(jiǎn)而言之就是處理客戶(hù)端發(fā)來(lái)的各種請(qǐng)求然后根據(jù)服務(wù)器的邏輯處理返回一個(gè)結(jié)果給客戶(hù)端。在web服務(wù)器和客戶(hù)端之間的通信是基于HTTP協(xié)議進(jìn)行的。而客戶(hù)端可以是瀏覽器也可以是支持HTTP協(xié)議的APP。
那么瀏覽器應(yīng)該怎么連接上自己的web服務(wù)器呢,最簡(jiǎn)單的web服務(wù)器就是通過(guò)TCP三次握手建立連接后,服務(wù)器直接返回一個(gè)結(jié)果給瀏覽器。瀏覽器和服務(wù)器是通過(guò)TCP三路握手建立連接的。瀏覽器在通過(guò)URL(統(tǒng)一資源定位符,就是我們俗稱(chēng)的網(wǎng)絡(luò)地址)去請(qǐng)求服務(wù)器的連接,并且通過(guò)URL中的路徑請(qǐng)求服務(wù)器上的資源。舉個(gè)栗子就是這樣的:
最簡(jiǎn)單的web服務(wù)器:
#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/sendfile.h>
#include<fcntl.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<unistd.h>
#include<string.h>
const int port = 8888;
int main(int argc,char *argv[])
{
if(argc<0)
{
printf("need two canshu\n");
return 1;
}
int sock;
int connfd;
struct sockaddr_in sever_address;
bzero(&sever_address,sizeof(sever_address));
sever_address.sin_family = PF_INET;
sever_address.sin_addr.s_addr = htons(INADDR_ANY);
sever_address.sin_port = htons(8888);
sock = socket(AF_INET,SOCK_STREAM,0);
assert(sock>=0);
int ret = bind(sock, (struct sockaddr*)&sever_address,sizeof(sever_address));
assert(ret != -1);
ret = listen(sock,1);
assert(ret != -1);
while(1)
{
struct sockaddr_in client;
socklen_t client_addrlength = sizeof(client);
connfd = accept(sock, (struct sockaddr*)&client, &client_addrlength);
if(connfd<0)
{
printf("errno\n");
}
else{
char request[1024];
recv(connfd,request,1024,0);
request[strlen(request)+1]='\0';
printf("%s\n",request);
printf("successeful!\n");
char buf[520]="HTTP/1.1 200 ok\r\nconnection: close\r\n\r\n";//HTTP響應(yīng)
int s = send(connfd,buf,strlen(buf),0);//發(fā)送響應(yīng)
//printf("send=%d\n",s);
int fd = open("hello.html",O_RDONLY);//消息體
sendfile(connfd,fd,NULL,2500);//零拷貝發(fā)送消息體
close(fd);
close(connfd);
}
}
return 0;
}
最簡(jiǎn)單的html文件:
<html>
<body bgcolor="blue">
this is the html.
<hr>
<p>hello word! waste young! </p><br>
</body>
</html>
運(yùn)行web.c文件,生成執(zhí)行文件a.out,在終端執(zhí)行后,我們?cè)跒g覽器的網(wǎng)址欄中輸入:http://localhost:8888 然后確認(rèn)后,就會(huì)返回hello.html的文件頁(yè)面
這里的URL,localhost:實(shí)際就是hostname,然后8888是端口,如果在端口后面再加上比如/hello.html這樣的路徑就表示請(qǐng)求服務(wù)器上的一個(gè)hello.html,這里請(qǐng)求方法是GET,所以要求服務(wù)器返回該資源的頁(yè)面。
那么此時(shí)再來(lái)看下服務(wù)器接收到的東西,就是HTTP請(qǐng)求。
第一行就是請(qǐng)求行,請(qǐng)求行的格式是這樣的:請(qǐng)求方法+空格+URL+空格+協(xié)議版本+\r+\n 這里的請(qǐng)求方法是GET ,URL是/(在這里,URL就相當(dāng)于資源的路徑,若在網(wǎng)址欄輸入的是http://localhost:8888/hello.html的話(huà),這里瀏覽器發(fā)送過(guò)來(lái)的URL就是/hello.html),協(xié)議版本是HTTP/1.1(現(xiàn)在多數(shù)協(xié)議版本都是這個(gè))。
第二行到最后一行都是請(qǐng)求頭部,請(qǐng)求頭部的格式是這樣的: 頭部字段:+空格+數(shù)值+\r+\n 然后多個(gè)頭部子段組織起來(lái)就是請(qǐng)求頭部,在最后的頭部字段的格式中需要有兩個(gè)換行符號(hào),最后一行的格式是:頭部字段:+空格+數(shù)值+\r+\n+\r+\n 因?yàn)樵诤竺孢€要跟著請(qǐng)求數(shù)據(jù),為了區(qū)分請(qǐng)求數(shù)據(jù)和請(qǐng)求頭的結(jié)束,就多了一個(gè)換行符。
三、HTTP請(qǐng)求和響應(yīng)
(1)HTTP請(qǐng)求
簡(jiǎn)而言之就是客戶(hù)端發(fā)送給服務(wù)端的請(qǐng)求。請(qǐng)求格式上面略提到了一點(diǎn)點(diǎn),大概的格式就如下所示:
其中的細(xì)節(jié)就很多了,但是主要的是請(qǐng)求方法。其中頭部字段有很多,大家可以上網(wǎng)百度。主要實(shí)現(xiàn)的就是GET方法和POST方法,其中GET方法是請(qǐng)求資源,但是不改變服務(wù)器上資源的,POST方法的話(huà)就會(huì)請(qǐng)求更改服務(wù)器上的資源。除了這兩個(gè)方法外,還有PUT,DELETE,HEAD,TRACE等等。對(duì)應(yīng)增刪查改的就是PUT、DELETE、POST、GET。
然后URL就是要請(qǐng)求的資源路徑,協(xié)議版本為HTTP/1.1,頭部字段根據(jù)每個(gè)頭部字段名都代表著給服務(wù)器的一個(gè)信息,具體可以根據(jù)以下網(wǎng)址查看:https://blog.csdn.net/sinat_22840937/article/details/64438253
(2)HTTP響應(yīng)
HTTP響應(yīng)就是服務(wù)端返回給客戶(hù)端的響應(yīng)消息。響應(yīng)格式大概如下:
其中響應(yīng)首行格式如:HTTP/1.1+狀態(tài)響應(yīng)碼+\r\n 狀態(tài)響應(yīng)碼參考如下:https://baike.baidu.com/item/HTTP狀態(tài)碼/5053660?fr=aladdin
這里大概用的是200,400,403,404,其中頭部字段需要注意content-length,在服務(wù)器中響應(yīng)碼若沒(méi)有消息題的長(zhǎng)度,瀏覽器就只能通過(guò)關(guān)閉客戶(hù)端才可以得知消息體的長(zhǎng)度,才可以顯示出消息體的具體表現(xiàn)。而且消息體的長(zhǎng)度必須要和消息體吻合。如果服務(wù)端發(fā)送的消息體長(zhǎng)度不正確的話(huà),會(huì)導(dǎo)致超時(shí)或者瀏覽器一直顯示不了要的資源文件。詳細(xì)可以參考博客:https://www.cnblogs.com/lovelacelee/p/5385683.html
四、如何寫(xiě)出小型 web服務(wù)器
1、代碼預(yù)備知識(shí)
了解TCP三次握手和TCP四次揮手
線(xiàn)程同步機(jī)制包裝類(lèi)
線(xiàn)程池創(chuàng)建
epoll多路復(fù)用
(1)TCP三次握手
服務(wù)器需要準(zhǔn)備好接受外來(lái)連接,通過(guò)socket bind listen三個(gè)函數(shù)完成,然后我們稱(chēng)為被動(dòng)打開(kāi)。
客戶(hù)則通過(guò)connect發(fā)起主動(dòng)連接請(qǐng)求,這就導(dǎo)致客戶(hù)TCP發(fā)送一個(gè)SYN(同步)分節(jié)去告訴服務(wù)器客戶(hù)將在待建立的連接中發(fā)送的數(shù)據(jù)的初始序列號(hào),通常SYN不攜帶數(shù)據(jù),其所在IP數(shù)據(jù)只有一個(gè)IP首部,一個(gè)TCP首部以及可能有的TCP選項(xiàng)。
服務(wù)器確認(rèn)客戶(hù)的SYN后,同時(shí)自己也要發(fā)送一個(gè)SYN分節(jié),它含有服務(wù)器將在同一個(gè)連接中發(fā)送的數(shù)據(jù)的初始化列序號(hào),服務(wù)器在單個(gè)分節(jié)中發(fā)送SYN和對(duì)客戶(hù)SYN的確認(rèn)
客戶(hù)必須去確認(rèn)服務(wù)器的SYN
(2)TCP四次揮手
某一個(gè)應(yīng)用進(jìn)程首先調(diào)用close,稱(chēng)為該端執(zhí)行主動(dòng)關(guān)閉,該端的TCP會(huì)發(fā)送一個(gè)FIN分節(jié),表示數(shù)據(jù)已經(jīng)發(fā)送完畢
接到FIN的對(duì)端將執(zhí)行被動(dòng)關(guān)閉,這個(gè)FIN由TCP確認(rèn),它的接受也作為一個(gè)文件結(jié)束符傳遞給接收端應(yīng)用進(jìn)程(放在已排隊(duì)等候該應(yīng)用進(jìn)程接收的任何其他數(shù)據(jù)之后),因?yàn)?/span>FIN的接收意味著接收端應(yīng)用進(jìn)程在相應(yīng)連接上已無(wú)額外數(shù)據(jù)可以接收
一段時(shí)間后,接收到這個(gè)文件結(jié)束符的應(yīng)用進(jìn)程會(huì)調(diào)用close關(guān)閉它的套接字,這會(huì)導(dǎo)致它的TCP也要發(fā)送一個(gè)FIN
接收這個(gè)最終FIN的原發(fā)送端TCP(即執(zhí)行主動(dòng)關(guān)閉的那一端)確認(rèn)這個(gè)FIN
參考網(wǎng)站:https://www.cnblogs.com/Andya/p/7272462.html
(3)線(xiàn)程池的創(chuàng)建
我用的是半同步/半反應(yīng)堆線(xiàn)程池。該線(xiàn)程池通用性比較高,主線(xiàn)程一般往工作隊(duì)列中加入任務(wù),然后工作線(xiàn)程等待后并通過(guò)競(jìng)爭(zhēng)關(guān)系從工作隊(duì)列中取出任務(wù)并且執(zhí)行。而且應(yīng)用到服務(wù)器程序中的話(huà)要保證客戶(hù)請(qǐng)求都是無(wú)狀態(tài)的,因?yàn)橥粋€(gè)連接上的不同請(qǐng)求可能會(huì)由不同的線(xiàn)程處理。
ps:若工作隊(duì)列為空,則線(xiàn)程就處于等待狀態(tài),就需要同步機(jī)制的處理。
代碼:
#ifndef _THREADPOOL_H
#define _THREADPOOL_H
#include<iostream>
#include<list>
#include<cstdio>
#include<semaphore.h>
#include<exception>
#include<pthread.h>
#include"myhttp_coon.h"
#include"mylock.h"
using namespace std;
template<typename T>
/*線(xiàn)程池的封裝*/
class threadpool
{
private:
int max_thread;//線(xiàn)程池中的最大線(xiàn)程總數(shù)
int max_job;//工作隊(duì)列的最大總數(shù)
pthread_t *pthread_poll;//線(xiàn)程池?cái)?shù)組
std::list<T*> m_myworkqueue;//請(qǐng)求隊(duì)列
mylocker m_queuelocker;//保護(hù)請(qǐng)求隊(duì)列的互斥鎖
sem m_queuestat;//由信號(hào)量來(lái)判斷是否有任務(wù)需要處理
bool m_stop;;//是否結(jié)束線(xiàn)程
public:
threadpool();
~threadpool();
bool addjob(T* request);
private:
static void* worker(void *arg);
void run();
};
/*線(xiàn)程池的創(chuàng)建*/
template <typename T>
threadpool<T> :: threadpool()
{
max_thread = 8;
max_job = 1000;
m_stop = false;
pthread_poll = new pthread_t[max_thread];//為線(xiàn)程池開(kāi)辟空間
if(!pthread_poll)
{
throw std::exception();
}
for(int i=0; i<max_thread; i++)
{
cout << "Create the pthread:" << i << endl;
if(pthread_create(pthread_poll+i, NULL, worker, this)!=0)
{
delete [] pthread_poll;
throw std::exception();
}
if(pthread_detach(pthread_poll[i]))//將線(xiàn)程分離
{
delete [] pthread_poll;
throw std::exception();
}
}
}
template <typename T>
threadpool<T>::~threadpool()
{
delete[] pthread_poll;
m_stop = true;
}
template <typename T>
bool threadpool<T>::addjob(T* request)
{
m_queuelocker.lock();
if(m_myworkqueue.size()> max_job)//如果請(qǐng)求隊(duì)列大于了最大請(qǐng)求隊(duì)列,則出錯(cuò)
{
m_queuelocker.unlock();
return false;
}
m_myworkqueue.push_back(request);//將請(qǐng)求加入到請(qǐng)求隊(duì)列中
m_queuelocker.unlock();
m_queuestat.post();//將信號(hào)量增加1
return true;
}
template <typename T>
void* threadpool<T>::worker(void *arg)
{
threadpool *pool = (threadpool*)arg;
pool->run();
return pool;
}
template <typename T>
void threadpool<T> :: run()
{
while(!m_stop)
{
m_queuestat.wait();//信號(hào)量減1,直到為0的時(shí)候線(xiàn)程掛起等待
m_queuelocker.lock();
if(m_myworkqueue.empty())
{
m_queuelocker.unlock();
continue;
}
T* request = m_myworkqueue.front();
m_myworkqueue.pop_front();
m_queuelocker.unlock();
if(!request)
{
continue;
}
request->doit();//執(zhí)行工作隊(duì)列
}
}
#endif
(4)同步機(jī)制的包裝類(lèi)
因?yàn)椴捎昧司€(xiàn)程池,就相當(dāng)于用了多線(xiàn)程編程,此時(shí)就需要考慮各個(gè)線(xiàn)程對(duì)公共資源的訪問(wèn)的限制,因?yàn)榉奖阒蟮拇a采用了三種包裝機(jī)制,分別是信號(hào)量的類(lèi),互斥鎖的類(lèi)和條件變量的類(lèi)。在服務(wù)器中我使用的是信號(hào)量的類(lèi)。其中信號(hào)量的原理和System V IPC信號(hào)量一樣(不抄書(shū)了,直接拍照了。。。)
代碼實(shí)現(xiàn):
#ifndef _MYLOCK_H
#define _MYLOCK_H
#include<iostream>
#include<list>
#include<cstdio>
#include<semaphore.h>
#include<exception>
#include<pthread.h>
#include"myhttp_coon.h"
using namespace std;
/*封裝信號(hào)量*/
class sem{
private:
sem_t m_sem;
public:
sem();
~sem();
bool wait();//等待信號(hào)量
bool post();//增加信號(hào)量
};
//創(chuàng)建信號(hào)量
sem :: sem()
{
if(sem_init(&m_sem,0,0) != 0)
{
throw std ::exception();
}
}
//銷(xiāo)毀信號(hào)量
sem :: ~sem()
{
sem_destroy(&m_sem);
}
//等待信號(hào)量
bool sem::wait()
{
return sem_wait(&m_sem) == 0;
}
//增加信號(hào)量
bool sem::post()
{
return sem_post(&m_sem) == 0;
}
/*封裝互斥鎖*/
class mylocker{
private:
pthread_mutex_t m_mutex;
public:
mylocker();
~mylocker();
bool lock();
bool unlock();
};
mylocker::mylocker()
{
if(pthread_mutex_init(&m_mutex, NULL) != 0)
{
throw std::exception();
}
}
mylocker::~mylocker()
{
pthread_mutex_destroy(&m_mutex);
}
/*上鎖*/
bool mylocker::lock()
{
return pthread_mutex_lock(&m_mutex)==0;
}
/*解除鎖*/
bool mylocker::unlock()
{
return pthread_mutex_unlock(&m_mutex) == 0;
}
/*封裝條件變量*/
class mycond{
private:
pthread_mutex_t m_mutex;
pthread_cond_t m_cond;
public:
mycond();
~mycond();
bool wait();
bool signal();
};
mycond::mycond()
{
if(pthread_mutex_init(&m_mutex,NULL)!=0)
{
throw std::exception();
}
if(pthread_cond_init(&m_cond, NULL)!=0)
{
throw std::exception();
}
}
mycond::~mycond()
{
pthread_mutex_destroy(&m_mutex);
pthread_cond_destroy(&m_cond);
}
/*等待條件變量*/
bool mycond::wait()
{
int ret;
pthread_mutex_lock(&m_mutex);
ret = pthread_cond_wait(&m_cond,&m_mutex);
pthread_mutex_unlock(&m_mutex);
return ret == 0;
}
/*喚醒等待條件變量的線(xiàn)程*/
bool mycond::signal()
{
return pthread_cond_signal(&m_cond) == 0;
}
#endif
(5)epoll多路復(fù)用
epoll系列系統(tǒng)調(diào)用函數(shù)(#include<sys/epoll.h>):
int epoll_create(int size);創(chuàng)建內(nèi)核事件表
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);操作epoll的內(nèi)核事件表
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);一段時(shí)間內(nèi)等待一組文件描述符上的就緒事件
除此這些函數(shù)外,還需要了解epoll的LT模式和ET模式還有EPOLLONESHOT事件.
下面三篇博客了解下:?
https://blog.csdn.net/davidsguo008/article/details/73556811
https://blog.csdn.net/men_wen/article/details/53456491
https://blog.csdn.net/yusiguyuan/article/details/15027821
代碼:
#include<iostream>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<fcntl.h>
#include<stdlib.h>
#include<assert.h>
#include<sys/epoll.h>
#include"threadpool.h"
//#include"myhttp_coon.h"
using namespace std;
const int port = 8888;
int setnonblocking(int fd)
{
int old_option = fcntl(fd, F_GETFL);
int new_option = old_option | O_NONBLOCK;
fcntl(fd, F_SETFL, new_option);
return old_option;
}
void addfd(int epfd, int fd, bool flag)
{
epoll_event ev;
ev.data.fd = fd;
ev.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
if(flag)
{
ev.events = ev.events | EPOLLONESHOT;
}
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
setnonblocking(fd);
}
int main(int argc, char *argv[])
{
threadpool<http_coon>* pool = NULL;
pool = new threadpool<http_coon>;
http_coon* users = new http_coon[100];
assert(users);
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(port);
address.sin_addr.s_addr = htons(INADDR_ANY);
int listenfd = socket(AF_INET,SOCK_STREAM,0);
assert(listenfd >= 0);
int ret;
ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));
assert(ret != -1);
ret = listen(listenfd,5);
assert(ret >= 0);
int epfd;
epoll_event events[1000];
epfd = epoll_create(5);
assert(epfd != -1);
addfd(epfd, listenfd, false);//listen不能注冊(cè)EPOLLONESHOT事件,否則只能處理一個(gè)客戶(hù)連接
while(true)
{
int number = epoll_wait(epfd, events, 1000, -1);
if( (number < 0) && (errno != EINTR) )
{
printf("my epoll is failure!\n");
break;
}
for(int i=0; i<number; i++)
{
int sockfd = events[i].data.fd;
if(sockfd == listenfd)//有新用戶(hù)連接
{
struct sockaddr_in client_address;
socklen_t client_addresslength = sizeof(client_address);
int client_fd = accept(listenfd,(struct sockaddr*)&client_address, &client_addresslength);
if(client_fd < 0)
{
printf("errno is %d\n",errno);
continue;
}
/*如果連接用戶(hù)超過(guò)了預(yù)定于的用戶(hù)總數(shù),則拋出異常*/
/* if(http_coon::m_user_count > MAX_FD)
{
show_error(client_fd, "Internal sever busy");
continue;
}*/
//初始化客戶(hù)連接
cout << epfd << " " << client_fd << endl;
addfd(epfd, client_fd, true);
cout << "client_fd:" << client_fd << "****\n";
users[client_fd].init(epfd,client_fd);
}
else if(events[i].events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR))
{
/*出現(xiàn)異常則關(guān)閉客戶(hù)端連接*/
users[sockfd].close_coon();
}
else if(events[i].events & EPOLLIN)//可以讀取
{
if(users[sockfd].myread())
{
/*讀取成功則添加任務(wù)隊(duì)列*/
pool->addjob(users+sockfd);
}
else{
users[sockfd].close_coon();
}
}
else if(events[i].events & EPOLLOUT)//可寫(xiě)入
{
if(!users[sockfd].mywrite())
{
users[sockfd].close_coon();
}
}
}
}
close(epfd);
close(listenfd);
delete[] users;
delete pool;
return 0;
}
2、主要邏輯思路
首先創(chuàng)建和客戶(hù)端的連接
服務(wù)器通過(guò)客戶(hù)端的HTTP請(qǐng)求解析來(lái)判斷返回何種結(jié)果.HTTP解析是以行為單位的,前提條件是根據(jù)\r\n來(lái)判斷是否完整度入一行,若完整讀入一行了那么就可以進(jìn)行解析了。
通過(guò)HTTP請(qǐng)求的解析后,在寫(xiě)緩沖區(qū)寫(xiě)如HTTP響應(yīng),發(fā)送給客戶(hù)端(HTTP應(yīng)答包括一個(gè)狀態(tài)行,多個(gè)頭部字段,一個(gè)空行和資源內(nèi)容,其中前三個(gè)部分的內(nèi)容一般會(huì)被web服務(wù)器放置在一塊內(nèi)存中,而文檔的內(nèi)容通常會(huì)被放到另一個(gè)單獨(dú)的內(nèi)存中)
發(fā)送響應(yīng)首行后,就可以發(fā)送主要的消息體了
主要就是封裝在myhttp_coon.h中:
#ifndef _MYHTTP_COON_H
#define _MYHTTP_COON_H
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<sys/wait.h>
#include<sys/socket.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<sys/sendfile.h>
#include<sys/epoll.h>
#include<sys/fcntl.h>
#include<sys/stat.h>
#include<sys/types.h>
using namespace std;
#define READ_BUF 2000
class http_coon{
public:
/*NO_REQUESTION是代表請(qǐng)求不完整,需要客戶(hù)繼續(xù)輸入;BAD_REQUESTION是HTTP請(qǐng)求語(yǔ)法不正確;GET_REQUESTION代表獲得并且解析了一個(gè)正確的HTTP請(qǐng)求;FORBIDDEN_REQUESTION是代表訪問(wèn)資源的權(quán)限有問(wèn)題;FILE_REQUESTION代表GET方法資源請(qǐng)求;INTERNAL_ERROR代表服務(wù)器自身問(wèn)題;NOT_FOUND代表請(qǐng)求的資源文件不存在;DYNAMIC_FILE表示是一個(gè)動(dòng)態(tài)請(qǐng)求;POST_FILE表示獲得一個(gè)以POST方式請(qǐng)求的HTTP請(qǐng)求*/
enum HTTP_CODE{NO_REQUESTION, GET_REQUESTION, BAD_REQUESTION, FORBIDDEN_REQUESTION,FILE_REQUESTION,INTERNAL_ERROR,NOT_FOUND,DYNAMIC_FILE,POST_FILE};
/*HTTP請(qǐng)求解析的狀態(tài)轉(zhuǎn)移。HEAD表示解析頭部信息,REQUESTION表示解析請(qǐng)求行*/
enum CHECK_STATUS{HEAD,REQUESTION};
private:
char requst_head_buf[1000];//響應(yīng)頭的填充
char post_buf[1000];//Post請(qǐng)求的讀緩沖區(qū)
char read_buf[READ_BUF];//客戶(hù)端的http請(qǐng)求讀取
char filename[250];//文件總目錄
int file_size;//文件大小
int check_index;//目前檢測(cè)到的位置
int read_buf_len;//讀取緩沖區(qū)的大小
char *method;//請(qǐng)求方法
char *url;//文件名稱(chēng)
char *version;//協(xié)議版本
char *argv;//動(dòng)態(tài)請(qǐng)求參數(shù)
bool m_linger;//是否保持連接
int m_http_count;//http長(zhǎng)度
char *m_host;//主機(jī)名記錄
char path_400[17];//出錯(cuò)碼400打開(kāi)的文件名緩沖區(qū)
char path_403[23];//出錯(cuò)碼403打開(kāi)返回的文件名緩沖區(qū)
char path_404[40];//出錯(cuò)碼404對(duì)應(yīng)文件名緩沖區(qū)
char message[1000];//響應(yīng)消息體緩沖區(qū)
char body[2000];//post響應(yīng)消息體緩沖區(qū)
CHECK_STATUS status;//狀態(tài)轉(zhuǎn)移
bool m_flag;//true表示是動(dòng)態(tài)請(qǐng)求,反之是靜態(tài)請(qǐng)求
public:
int epfd;
int client_fd;
int read_count;
http_coon();
~http_coon();
void init(int e_fd, int c_fd);//初始化
int myread();//讀取請(qǐng)求
bool mywrite();//響應(yīng)發(fā)送
void doit();//線(xiàn)程接口函數(shù)
void close_coon();//關(guān)閉客戶(hù)端鏈接
private:
HTTP_CODE analyse();//解析Http請(qǐng)求頭的函數(shù)
int jude_line(int &check_index, int &read_buf_len);//該請(qǐng)求是否是完整的以行\r\n
HTTP_CODE head_analyse(char *temp);//http請(qǐng)求頭解析
HTTP_CODE requestion_analyse(char *temp);//http請(qǐng)求行解析
HTTP_CODE do_post();//對(duì)post請(qǐng)求中的參數(shù)進(jìn)行解析
HTTP_CODE do_file();//對(duì)GET請(qǐng)求方法中的url 協(xié)議版本的分離
void modfd(int epfd, int sock, int ev);//改變socket為狀態(tài)
void dynamic(char *filename, char *argv);//通過(guò)get方法進(jìn)入的動(dòng)態(tài)請(qǐng)求處理
void post_respond();//POST請(qǐng)求響應(yīng)填充
bool bad_respond();//語(yǔ)法錯(cuò)誤請(qǐng)求響應(yīng)填充
bool forbiden_respond();//資源權(quán)限限制請(qǐng)求響應(yīng)的填充
bool succeessful_respond();//解析成功請(qǐng)求響應(yīng)填充
bool not_found_request();//資源不存在請(qǐng)求響應(yīng)填充
};
void http_coon::init(int e_fd, int c_fd)
{
epfd = e_fd;
client_fd = c_fd;
read_count = 0;
m_flag = false;
}
http_coon::http_coon()
{
}
http_coon::~http_coon()
{
}
/*關(guān)閉客戶(hù)端鏈接*/
void http_coon::close_coon()
{
epoll_ctl(epfd, EPOLL_CTL_DEL, client_fd, 0);
close(client_fd);
client_fd = -1;
}
/*改變事件表中的事件屬性*/
void http_coon::modfd(int epfd, int client_fd, int ev)
{
epoll_event event;
event.data.fd = client_fd;
event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP;
epoll_ctl(epfd, EPOLL_CTL_MOD, client_fd, &event);
}
/*read函數(shù)的封裝*/
int http_coon::myread()
{
bzero(&read_buf,sizeof(read_buf));
while(true)
{
int ret = recv(client_fd, read_buf+read_count, READ_BUF-read_count, 0 );
if(ret == -1)
{
if(errno == EAGAIN || errno == EWOULDBLOCK)//讀取結(jié)束
{
break;
}
return 0;
}
else if(ret == 0)
{
return 0;
}
read_count = read_count + ret;
}
strcpy(post_buf,read_buf);
return 1;
}
/*響應(yīng)狀態(tài)的填充,這里返回可以不為bool類(lèi)型*/
bool http_coon::succeessful_respond()//200
{
m_flag = false;
bzero(requst_head_buf,sizeof(requst_head_buf));
sprintf(requst_head_buf,"HTTP/1.1 200 ok\r\nConnection: close\r\ncontent-length:%d\r\n\r\n",file_size);
}
bool http_coon::bad_respond()//400
{
bzero(url, strlen(url));
strcpy(path_400,"bad_respond.html");
url = path_400;
bzero(filename,sizeof(filename));
sprintf(filename,"/home/jialuhu/linux_net/web_sever/%s",url);
struct stat my_file;
if(stat(filename,&my_file)<0)
{
cout << "文件不存在\n";
}
file_size = my_file.st_size;
bzero(requst_head_buf,sizeof(requst_head_buf));
sprintf(requst_head_buf,"HTTP/1.1 400 BAD_REQUESTION\r\nConnection: close\r\ncontent-length:%d\r\n\r\n",file_size);
}
bool http_coon::forbiden_respond()//403
{
bzero(url, strlen(url));
strcpy(path_403,"forbidden_request.html");
url = path_403;
bzero(filename,sizeof(filename));
sprintf(filename,"/home/jialuhu/linux_net/web_sever/%s",url);
struct stat my_file;
if(stat(filename,&my_file)<0)
{
cout << "失敗\n";
}
file_size = my_file.st_size;
bzero(requst_head_buf,sizeof(requst_head_buf));
sprintf(requst_head_buf,"HTTP/1.1 403 FORBIDDEN\r\nConnection: close\r\ncontent-length:%d\r\n\r\n",file_size);
}
bool http_coon::not_found_request()//404
{
bzero(url, strlen(url));
strcpy(path_404,"not_found_request.html");
url = path_404;
bzero(filename,sizeof(filename));
sprintf(filename,"/home/jialuhu/linux_net/web_sever/%s",url);
struct stat my_file;
if(stat(filename,&my_file)<0)
{
cout << "草擬\n";
}
file_size = my_file.st_size;
bzero(requst_head_buf,sizeof(requst_head_buf));
sprintf(requst_head_buf,"HTTP/1.1 404 NOT_FOUND\r\nConnection: close\r\ncontent-length:%d\r\n\r\n",file_size);
}
/*動(dòng)態(tài)請(qǐng)求處理*/
void http_coon::dynamic(char *filename, char *argv)
{
int len = strlen(argv);
int k = 0;
int number[2];
int sum=0;
m_flag = true;
bzero(requst_head_buf,sizeof(requst_head_buf));
sscanf(argv,"a=%d&b=%d",&number[0],&number[1]);
if(strcmp(filename,"/add")==0)
{
sum = number[0] + number[1];
sprintf(body,"<html><body>\r\n<p>%d + %d = %d </p><hr>\r\n</body></html>\r\n",number[0],number[1],sum);
sprintf(requst_head_buf,"HTTP/1.1 200 ok\r\nConnection: close\r\ncontent-length: %d\r\n\r\n",strlen(body));
}
else if(strcmp(filename,"/multiplication")==0)
{
cout << "\t\t\t\tmultiplication\n\n";
sum = number[0]*number[1];
sprintf(body,"<html><body>\r\n<p>%d * %d = %d </p><hr>\r\n</body></html>\r\n",number[0],number[1],sum);
sprintf(requst_head_buf,"HTTP/1.1 200 ok\r\nConnection: close\r\ncontent-length: %d\r\n\r\n",strlen(body));
}
}
/*POST請(qǐng)求處理*/
void http_coon::post_respond()
{
if(fork()==0)
{
dup2(client_fd,STDOUT_FILENO);
execl(filename,argv,NULL);
}
wait(NULL);
}
/*判斷一行是否讀取完整*/
int http_coon::jude_line(int &check_index, int &read_buf_len)
{
cout << read_buf << endl;
char ch;
for( ; check_index<read_buf_len; check_index++)
{
ch = read_buf[check_index];
if(ch == '\r' && check_index+1<read_buf_len && read_buf[check_index+1]=='\n')
{
read_buf[check_index++] = '\0';
read_buf[check_index++] = '\0';
return 1;//完整讀入一行
}
if(ch == '\r' && check_index+1==read_buf_len)
{
return 0;
}
if(ch == '\n')
{
if(check_index>1 && read_buf[check_index-1]=='\r')
{
read_buf[check_index-1] = '\0';
read_buf[check_index++] = '\0';
return 1;
}
else{
return 0;
}
}
}
return 0;
}
/*解析請(qǐng)求行*/
http_coon::HTTP_CODE http_coon::requestion_analyse(char *temp)
{
char *p = temp;
cout << "p=" << p << endl;
for(int i=0; i<2; i++)
{
if(i==0)
{
method = p;//請(qǐng)求方法保存
int j = 0;
while((*p != ' ') && (*p != '\r'))
{
p++;
}
p[0] = '\0';
p++;
cout << "method:" <<method << endl;
// method++;
}
if(i==1)
{
url = p;//文件路徑保存
while((*p != ' ') && (*p != '\r'))
{
p++;
}
p[0] = '\0';
p++;
cout << "url:" << url << endl;
}
}
version = p;//請(qǐng)求協(xié)議保存
while(*p != '\r')
{
p++;
}
p[0] = '\0';
p++;
p[0] = '\0';
p++;
cout << version << endl;
if(strcmp(method,"GET")!=0&&strcmp(method,"POST")!=0)
{
return BAD_REQUESTION;
}
if(!url || url[0]!='/')
{
return BAD_REQUESTION;
}
if(strcmp(version,"HTTP/1.1")!=0)
{
return BAD_REQUESTION;
}
status = HEAD;//狀態(tài)轉(zhuǎn)移到解析頭部
return NO_REQUESTION;//繼續(xù)解析
}
/*解析頭部信息*/
http_coon::HTTP_CODE http_coon::head_analyse(char *temp)
{
if(temp[0]=='\0')
{
//獲得一個(gè)完整http請(qǐng)求
return GET_REQUESTION;
}
//處理其他頭部
else if(strncasecmp(temp,"Connection:", 11) == 0)
{
temp = temp+11;
while(*temp==' ')
{
temp++;
}
if(strcasecmp(temp, "keep-alive") == 0)
{
m_linger = true;
}
}
else if(strncasecmp(temp,"Content-Length:", 15)==0)
{
temp = temp+15;
while(*temp==' ')
{
cout << *temp << endl;
temp++;
}
m_http_count = atol(temp);//content-length需要填充
}
else if(strncasecmp(temp,"Host:",5)==0)
{
temp = temp+5;
while(*temp==' ')
{
temp++;
}
m_host = temp;
}
else{
cout << "can't handle it's hand\n";
}
return NO_REQUESTION;
}
http_coon::HTTP_CODE http_coon::do_file()//GET方法請(qǐng)求,對(duì)其請(qǐng)求行進(jìn)行解析,存寫(xiě)資源路徑
{
char path[40]="/home/jialuhu/linux_net/web_sever";
char* ch;
if(ch=strchr(url,'?'))
{
argv = ch+1;
*ch = '\0';
strcpy(filename,url);
return DYNAMIC_FILE;
}
else{
strcpy(filename,path);
strcat(filename,url);
struct stat m_file_stat;
if(stat(filename, &m_file_stat) < 0)
{
//cout << "打不開(kāi)\n";
return NOT_FOUND;//NOT_FOUND 404
}
if( !(m_file_stat.st_mode & S_IROTH))//FORBIDDEN_REQUESTION 403
{
return FORBIDDEN_REQUESTION;
}
if(S_ISDIR(m_file_stat.st_mode))
{
return BAD_REQUESTION;//BAD_REQUESTION 400
}
file_size = m_file_stat.st_size;
return FILE_REQUESTION;
}
}
http_coon::HTTP_CODE http_coon::do_post()//POST方法請(qǐng)求,分解并且存入?yún)?shù)
{
int k = 0;
int star;
char path[34]="/home/jialuhu/linux_net/web_sever";
strcpy(filename,path);
strcat(filename,url);
star = read_buf_len-m_http_count;
argv = post_buf + star;
argv[strlen(argv)+1]='\0';
if(filename!=NULL && argv!=NULL)
{
return POST_FILE;
}
return BAD_REQUESTION;
}
/*http請(qǐng)求解析*/
http_coon::HTTP_CODE http_coon::analyse()
{
status = REQUESTION;
int flag;
char *temp = read_buf;
int star_line = 0;
check_index = 0;
int star = 0;
read_buf_len = strlen(read_buf);
int len = read_buf_len;
while((flag=jude_line(check_index, len))==1)
{
temp = read_buf + star_line;
star_line = check_index;
switch(status)
{
case REQUESTION://請(qǐng)求行分析,包括文件名稱(chēng)和請(qǐng)求方法
{
cout << "requestion\n";
int ret;
ret = requestion_analyse(temp);
if(ret==BAD_REQUESTION)
{
cout << "ret == BAD_REQUESTION\n";
//請(qǐng)求格式不正確
return BAD_REQUESTION;
}
break;
}
case HEAD://請(qǐng)求頭的分析
{
int ret;
ret = head_analyse(temp);
if(ret==GET_REQUESTION)//獲取完整的HTTP請(qǐng)求
{
if(strcmp(method,"GET")==0)
{
return do_file();//GET請(qǐng)求文件名分離函數(shù)
}
else if(strcmp(method,"POST")==0)
{
return do_post();//POST請(qǐng)求參數(shù)分離函數(shù)
}
else{
return BAD_REQUESTION;
}
}
break;
}
default:
{
return INTERNAL_ERROR;
}
}
}
return NO_REQUESTION;//請(qǐng)求不完整,需要繼續(xù)讀入
}
/*線(xiàn)程取出工作任務(wù)的接口函數(shù)*/
void http_coon::doit()
{
int choice = analyse();//根據(jù)解析請(qǐng)求頭的結(jié)果做選擇
switch(choice)
{
case NO_REQUESTION://請(qǐng)求不完整
{
cout << "NO_REQUESTION\n";
/*改變epoll的屬性*/
modfd(epfd, client_fd, EPOLLIN);
return;
}
case BAD_REQUESTION: //400
{
cout << "BAD_REQUESTION\n";
bad_respond();
modfd(epfd, client_fd, EPOLLOUT);
break;
}
case FORBIDDEN_REQUESTION://403
{
cout << "forbiden_respond\n";
forbiden_respond();
modfd(epfd, client_fd, EPOLLOUT);
break;
}
case NOT_FOUND://404
{
cout<<"not_found_request"<< endl;
not_found_request();
modfd(epfd, client_fd, EPOLLOUT);
break;
}
case FILE_REQUESTION://GET文件資源無(wú)問(wèn)題
{
cout << "文件file request\n";
succeessful_respond();
modfd(epfd, client_fd, EPOLLOUT);
break;
}
case DYNAMIC_FILE://動(dòng)態(tài)請(qǐng)求處理
{
cout << "動(dòng)態(tài)請(qǐng)求處理\n";
cout << filename << " " << argv << endl;
dynamic(filename, argv);
modfd(epfd, client_fd, EPOLLOUT);
break;
}
case POST_FILE://POST 方法處理
{
cout << "post_respond\n";
post_respond();
break;
}
default:
{
close_coon();
}
}
}
bool http_coon::mywrite()
{
if(m_flag)//如果是動(dòng)態(tài)請(qǐng)求,返回填充體
{
int ret=send(client_fd,requst_head_buf,strlen(requst_head_buf),0);
int r = send(client_fd,body,strlen(body),0);
if(ret>0 && r>0)
{
return true;
}
}
else{
int fd = open(filename,O_RDONLY);
assert(fd != -1);
int ret;
ret = write(client_fd,requst_head_buf,strlen(requst_head_buf));
if(ret < 0)
{
close(fd);
return false;
}
ret = sendfile(client_fd, fd, NULL, file_size);
if(ret < 0)
{
close(fd);
return false;
}
close(fd);
return true;
}
return false;
}
#endif
其中兩個(gè)附加功能加法和減法的實(shí)現(xiàn)(通過(guò)GET方法請(qǐng)求),以及POST方法請(qǐng)求的加法和減法的實(shí)現(xiàn)
動(dòng)態(tài)請(qǐng)求是什么樣子(GET)
sum.html文件:
<html>
<head>
<meta charset="utf-8">
<title>sum</title>
</head>
<body>
<form action="add">
a: <input type="text" name="a"><br>
b: <input type="text" name="b"><br>
<input type="submit" value="提交">
</form>
<p>點(diǎn)擊"提交"按鈕,表單數(shù)據(jù)將被發(fā)送到服務(wù)器上的“add”程序上。</p>
</body>
</html>
服務(wù)器收到的請(qǐng)求是這樣的,首先是打開(kāi)sum.html文件
然后在表單上提交要相加的兩個(gè)數(shù)字
點(diǎn)擊提交后,此時(shí)服務(wù)器收到的請(qǐng)求是這樣的:
看到了/add?a=33&b=33 這就是通過(guò)方法GET提交上來(lái)的參數(shù)a和b ,此時(shí)我們?cè)诮馕稣?qǐng)求行的時(shí)候就可以通過(guò)問(wèn)好來(lái)判斷是否是GET的動(dòng)態(tài)請(qǐng)求,若是那么根據(jù)sscanf()函數(shù),分離出參數(shù)a和b,進(jìn)行相加后就可以填充HTTP響應(yīng)發(fā)送給瀏覽器了。此處我根據(jù)提交的程序名稱(chēng)來(lái)選擇函數(shù),在函數(shù)中相加填充返回給瀏覽器。當(dāng)然我覺(jué)得正確的做法是重新寫(xiě)一個(gè)add.c然后執(zhí)行生產(chǎn)add文件,再在fork()一個(gè)子線(xiàn)程通過(guò)execl( )函數(shù)去執(zhí)行。
那么POST請(qǐng)求又是什么樣子呢,其實(shí)POST請(qǐng)求將參數(shù)放在了請(qǐng)求
修改后的sum.html文件
<html>
<head>
<meta charset="utf-8">
<title>sum</title>
</head>
<body>
<form action="add" method="post">
a: <input type="text" name="a"><br>
b: <input type="text" name="b"><br>
<input type="submit" value="提交">
</form>
<p>點(diǎn)擊"提交"按鈕,表單數(shù)據(jù)將被發(fā)送到服務(wù)器上的“add”程序上。</p>
</body>
</html>
加入了屬性method="post",此時(shí)打開(kāi)sum.html文件依然是GET方法,只是點(diǎn)擊提交表單后用的是POST方法。
和GET不同的是,參數(shù)被在請(qǐng)求的數(shù)據(jù)部分,也就是空行之后,此時(shí)若方法是POST的話(huà),根據(jù)read_buf_len和Content_Length就可以求出參數(shù)在read_buf中的起始位置。然后又可以通過(guò)sscanf( )分離參數(shù)了,然后fork()一個(gè)進(jìn)程,利用dup2函數(shù),將標(biāo)準(zhǔn)輸出重定向到瀏覽器的sockfd上,再執(zhí)行execl( )函數(shù)。此時(shí)我們的add執(zhí)行文件的.c文件如下:
#include<stdio.h>
#include<string.h>
int main(int argc, char *argv[])
{
char re_head[1000];
char message[1000];
int ret;
int a,b,result;
ret = sscanf(argv[0],"a=%d&b=%d", &a, &b);
//printf("a=%d\t b=%d\n",a,b);
if(ret < 0 || ret != 2)
{
sprintf(message,"<html><body>\r\n");
sprintf(message,"%s<p>failure</p>\r\n",message);
sprintf(message,"%s</body></html>");
sprintf(re_head,"HTTP/1.1 GET\r\n");
sprintf(re_head,"%scontent-length: %d\r\n",re_head,strlen(message));
sprintf(re_head,"%scontent-type: text/html\r\n",re_head);
sprintf(re_head,"%sconection: close\r\n\r\n");
/*錯(cuò)誤提示消息*/
}
else{
result = a+b;
/*返回正確信息*/
sprintf(message,"<html><body>\r\n");
sprintf(message,"%s<p>%d + %d = %d</p><br>\r\n",message,a,b,result);
sprintf(message,"%s<p>welcome to the word of jialuhu</p><br>\r\n",message);
sprintf(message,"%s</body></html>\r\n",message);
sprintf(re_head,"HTTP/1.1 200 ok\r\n");
sprintf(re_head,"%sContent-length: %d\r\n",re_head,(int)strlen(message));
sprintf(re_head,"%scontent-type: text/html\r\n\r\n",re_head);
// sprintf(re_head,"%sconection: close\r\n\r\n");
}
printf("%s",re_head);
printf("%s",message);
fflush(stdout);
return 0;
}
當(dāng)然除了加減法,還有很多功能可以去實(shí)現(xiàn)。此處就簡(jiǎn)單實(shí)現(xiàn)了這些功能。還有一些HTML文件,因?yàn)閼邪┰?,所以隨便寫(xiě)了幾個(gè)。
五、總結(jié)
縱觀博客其實(shí)感覺(jué)涉及的知識(shí)有點(diǎn)雜亂,但是很綜合吧。首先滿(mǎn)足代碼上高性能的需求,利用了線(xiàn)城池和epoll多路復(fù)用,其中也包括同步機(jī)制的封裝。其次就是HTTP這塊的知識(shí)了,包括請(qǐng)求格式響應(yīng)格式和請(qǐng)求方法和響應(yīng)狀態(tài)碼,很多很多都是零零碎碎平湊一起的。而且感覺(jué)這個(gè)服務(wù)器的實(shí)現(xiàn),也終于明白了瀏覽器和后臺(tái)是怎么溝通交流的,有時(shí)候看不如動(dòng)手實(shí)現(xiàn)下,很多東西就會(huì)突然明白了。大體模塊就是epoll、線(xiàn)城池、同步機(jī)制、邏輯處理。代碼里肯定也有很多沒(méi)有測(cè)試出來(lái)的bug,但是實(shí)現(xiàn)大概三分之二后還是有丟丟開(kāi)心的吧。
在線(xiàn)
客服
服務(wù)時(shí)間:周一至周日 08:30-18:00
選擇下列產(chǎn)品馬上在線(xiàn)溝通:
客服
熱線(xiàn)
7*24小時(shí)客服服務(wù)熱線(xiàn)
關(guān)注
微信
關(guān)注官方微信