基於epoll的簡單的http伺服器
阿新 • • 發佈:2019-01-22
該http伺服器已經可以處理併發連線,支援多個客戶端併發訪問,每個連線可以持續讀寫資料,當然,這只是一個簡單的學習例子,還有很多bug,發表出來只是希望大家可以互相學習,我也在不斷的改進,希望大家有什麼意見可以多多指點,謝謝
server.h
/* * server.h * * Created on: Jun 23, 2014 * Author: fangjian */ #ifndef SERVER_H_ #define SERVER_H_ #include "epoll_event.h" struct web_event_t; struct web_connection_t { int fd; int state;//當前處理到哪個階段 struct web_event_t* read_event; struct web_event_t* write_event; char* querybuf; int query_start_index;//請求資料的當前指標 int query_end_index;//請求資料的下一個位置 int query_remain_len;//可用空間 char method[8]; char uri[128]; char version[16]; char host[128]; char accept[128]; char conn[20]; }; struct server { int epollfd; }; void web_epoll_ctl(int epollfd,int ctl,int fd,int flag); int setnonblocking(int fd); void initConnection(web_connection_t* &conn); void web_accept(struct web_connection_t* conn); void read_request( struct web_connection_t* conn ); void process_request_line(struct web_connection_t* conn); void process_head(struct web_connection_t* conn); void process_body(struct web_connection_t* conn); void send_response(struct web_connection_t* conn); void try_to_enlarge_buffer(struct web_connection_t& conn); void empty_event_handler(struct web_connection_t* conn); void close_conn( struct web_connection_t* conn ); #endif /* SERVER_H_ */
server.cpp
/* * server.cpp * * Created on: Jun 23, 2014 * Author: fangjian */ #include "server.h" #include "epoll_event.h" #include <stdio.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> #include<signal.h> #include <sys/socket.h> #include <sys/epoll.h> #include <sys/stat.h> #include <sys/sendfile.h> #include <iostream> using namespace std; int main(int argc,char* argv[]) { const char* ip = "127.0.0.1"; int port = 8083; signal(SIGPIPE,SIG_IGN);//原因:http://blog.sina.com.cn/s/blog_502d765f0100kopn.html int listenfd = socket(AF_INET,SOCK_STREAM,0); struct sockaddr_in address; bzero(&address,sizeof(address)); address.sin_family = AF_INET; inet_pton(AF_INET,ip,&address.sin_addr); address.sin_port = htons(port); bind(listenfd,(struct sockaddr*)&address,sizeof(address)); listen(listenfd,50); web_connection_t* conn = NULL; epoll_init_event(conn); initConnection(conn);//建立一個用於接受連線的結構體 if(conn == NULL){printf("---建立監聽結構體失敗---\n");return -1;};//建立監聽結構體 conn->fd = listenfd; conn->read_event->handler = web_accept; epoll_add_event(conn,EPOLLIN | EPOLLERR); setnonblocking(listenfd); fork(); ngx_epoll_process_events();//進入事件迴圈,等待事件到達 } void initConnection(web_connection_t* &conn) { conn = (web_connection_t*)malloc(sizeof(web_connection_t)); conn->read_event = (web_event_t*)malloc(sizeof(web_event_t)); conn->write_event = (web_event_t*)malloc(sizeof(web_event_t)); conn->state = ACCEPT; conn->querybuf = (char*)malloc(QUERY_INIT_LEN); if(!conn->querybuf) { printf(" malloc error\n"); return; } conn->query_start_index = 0; conn->query_end_index = 0; conn->query_remain_len = QUERY_INIT_LEN; } 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 web_accept(web_connection_t* conn) { printf("-----------accept-------\n"); struct sockaddr * client_address; socklen_t client_addrlength = sizeof(client_address); int connfd = accept(conn->fd,(struct sockaddr*)&(client_address),&client_addrlength); if(connfd == -1) { printf("accept error\n"); return; } web_connection_t* new_conn = NULL; initConnection(new_conn);//建立一個新的連線結構體 if(new_conn == NULL){printf("---建立連線結構體失敗---\n");return;}; new_conn->fd = connfd; new_conn->state = READ; new_conn->read_event->handler = read_request; epoll_add_event(new_conn,EPOLLIN | EPOLLERR); setnonblocking(connfd); } void read_request( struct web_connection_t* conn ) { printf("-----------read_begin-------\n"); int len,fd = conn->fd; while(true) { /* 嘗試增加緩衝區空間 */ try_to_enlarge_buffer(*conn); len= recv(fd,conn->querybuf + conn->query_end_index,conn->query_remain_len,0); if(len < 0) { printf("----資料讀取完畢-----\n"); break;//表示當前資料讀取完畢,不是出錯 } else if(len > 0) { conn->query_end_index += len; conn->query_remain_len-= len; } else if(len == 0) { printf("----連線關閉-----\n"); epoll_del_event(conn); close_conn(conn ); return ; } } cout << "-----客戶端的內容是 " << endl; cout << conn->querybuf << endl; process_request_line(conn); return ; } void process_request_line(struct web_connection_t* conn) { int len; char* ptr = strpbrk(conn->querybuf + conn->query_start_index," \t"); if( !ptr) { printf("請求行解析失敗\n"); return; } len = ptr - conn->querybuf - conn->query_start_index; strncpy(conn->method,conn->querybuf + conn->query_start_index,len); cout <<"metnod="<<conn->method<<endl; conn->query_start_index += (len+1); ptr = strpbrk(conn->querybuf + conn->query_start_index," \t"); if( !ptr) { printf("請求行解析失敗\n"); return; } len = ptr - conn->querybuf - conn->query_start_index; strncpy(conn->uri,conn->querybuf + conn->query_start_index,len); cout << "uri="<<conn->uri<<endl; conn->query_start_index += (len+1); ptr = strpbrk(conn->querybuf,"\n");//先是回車\r,再是換行\n if(!ptr) { printf("請求行解析失敗\n"); return; } len = ptr - conn->querybuf - conn->query_start_index; strncpy(conn->version,conn->querybuf + conn->query_start_index,len); cout << "version="<<conn->version<<endl; conn->query_start_index += (len+1); cout <<"-----請求行解析完畢----------"<<endl; process_head(conn); } void process_head(struct web_connection_t* conn) { cout << "-------開始解析首部------" << endl; char* end_line; int len; while(true) { end_line = strpbrk(conn->querybuf + conn->query_start_index,"\n"); len = end_line - conn->querybuf - conn->query_start_index; if(len == 1) { printf("解析完畢\n"); conn->query_start_index += (len +1); cout << conn->querybuf + conn->query_start_index << endl; break; } else { if(strncasecmp(conn->querybuf+conn->query_start_index,"Host:",5) == 0) { strncpy(conn->host,conn->querybuf+conn->query_start_index + 6,len-6); cout << "host="<<conn->host<<endl; } else if(strncasecmp(conn->querybuf+conn->query_start_index,"Accept:",7) == 0) { strncpy(conn->accept,conn->querybuf+conn->query_start_index + 8,len-8); cout <<"accept="<<conn->accept <<endl; } else if(strncasecmp(conn->querybuf+conn->query_start_index,"Connection:",11) == 0) { strncpy(conn->conn,conn->querybuf+conn->query_start_index + 12,len-12); cout <<"connection="<<conn->conn <<endl; } else { } conn->query_start_index += (len +1); } } process_body(conn); printf("----首部解析完畢----------\n"); } void process_body(struct web_connection_t* conn) { if(conn->query_start_index == conn->query_end_index) { printf("---包體為空----\n"); } else { printf("---丟體包體-----\n"); } conn->query_start_index = conn->query_end_index = 0; conn->state = SEND_DATA; conn->write_event->handler = send_response; conn->read_event->handler = empty_event_handler;//讀事件回撥函式設定為空 epoll_mod_event(conn,EPOLLOUT | EPOLLERR); } void send_response(struct web_connection_t* conn) { char path[128] = "http";//根目錄下的資料夾 int len = strlen(conn->uri); memcpy(path+4,conn->uri,len); len += 4; path[len] = '\0';//很重要 int filefd = open(path,O_RDONLY); if(filefd < 0) { cout << "無法開啟該檔案" <<endl; return ; } struct stat stat_buf; fstat(filefd,&stat_buf); sendfile(conn->fd,filefd,NULL,stat_buf.st_size); close(filefd); //close(conn->fd);//如果不關閉該連線socket,則瀏覽器一直在載入,如何解決,保持keep-alive? conn->state = READ; conn->read_event->handler = read_request; epoll_mod_event(conn,EPOLLIN | EPOLLERR); //sleep(2); } void try_to_enlarge_buffer(struct web_connection_t& conn) { if(conn.query_remain_len < REMAIN_BUFFER) { int new_size = strlen(conn.querybuf) + QUERY_INIT_LEN; conn.querybuf = (char*)realloc(conn.querybuf,new_size); conn.query_remain_len = new_size - conn.query_end_index; } } void empty_event_handler(struct web_connection_t* conn) { } //關閉一個連線 void close_conn( struct web_connection_t* conn ) { static int count = 0; count ++; printf("關閉第%d個連線\n",count); close( conn->fd); free(conn->querybuf); free(conn->read_event); free(conn->write_event); free(conn); }
epoll_event.h
/* * event.h * * Created on: Jun 25, 2014 * Author: fangjian */ #ifndef EVENT_H_ #define EVENT_H_ #include <netinet/in.h> #include "server.h" #define MAX_EVENT_NUMBER 10000 #define QUERY_INIT_LEN 1024 #define REMAIN_BUFFER 512 /* 以下是處理機的狀態 */ #define ACCEPT 1 #define READ 2 #define QUERY_LINE 4 #define QUERY_HEAD 8 #define QUERY_BODY 16 #define SEND_DATA 32 struct web_connection_t; typedef void (*event_handler_pt)(web_connection_t* conn); //每一個事件都由web_event_t結構體來表示 struct web_event_t { /*為1時表示事件是可寫的,通常情況下,它表示對應的TCP連線目前狀態是可寫的,也就是連線處於可以傳送網路包的狀態*/ unsigned write:1; /*為1時表示此事件可以建立新的連線,通常情況下,在ngx_cycle_t中的listening動態陣列中,每一個監聽物件ngx_listening_t對應的讀事件中 的accept標誌位才會是1*/ unsigned accept:1; //為1時表示當前事件是活躍的,這個狀態對應著事件驅動模組處理方式的不同,例如:在新增事件、刪除事件和處理事件時,該標誌位的不同都會對應著不同的處理方式 unsigned active:1; unsigned oneshot:1; unsigned eof:1;//為1時表示當前處理的字元流已經結束 unsigned error:1;//為1時表示事件處理過程中出現了錯誤 event_handler_pt handler;//事件處理方法,每個消費者模組都是重新實現它 unsigned closed:1;//為1時表示當前事件已經關閉 }; void epoll_init_event(web_connection_t* &conn); void epoll_add_event(web_connection_t* conn,int flag); void epoll_mod_event(web_connection_t* conn,int flag); void epoll_del_event(web_connection_t* conn); int ngx_epoll_process_events(); #endif /* EVENT_H_ */
epoll_event.cpp
/*
* event.cpp
*
* Created on: Jun 25, 2014
* Author: fangjian
*/
#include "epoll_event.h"
#include <sys/epoll.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
static int ep = -1;//epoll物件的描述符,每個程序只有一個
void epoll_init_event(web_connection_t* &conn)
{
ep = epoll_create(1024);
}
/* 新增事件,conn已經設定好回撥函式和fd了 */
void epoll_add_event(web_connection_t* conn,int flag)
{
epoll_event ee;
int fd = conn->fd;
ee.data.ptr = (void*)conn;
ee.events = flag;
epoll_ctl(ep,EPOLL_CTL_ADD,fd,&ee);
}
/* 修改事件,event已經設定好回撥函式和fd了 */
void epoll_mod_event(web_connection_t* conn,int flag)
{
epoll_event ee;
int fd = conn->fd;
ee.data.ptr = (void*)conn;
ee.events = flag;
epoll_ctl(ep,EPOLL_CTL_MOD,fd,&ee);
}
//刪除該描述符上的所有事件,若想只刪除讀事件或寫事件,則把相應的事件設定為空函式
void epoll_del_event(web_connection_t* conn)
{
epoll_ctl( ep, EPOLL_CTL_DEL, conn->fd, 0 );//刪除事件最後一個引數為0
}
//事件迴圈函式
int ngx_epoll_process_events()
{
epoll_event event_list[MAX_EVENT_NUMBER];
while(true)
{
int number = epoll_wait(ep,event_list,MAX_EVENT_NUMBER,-1);
printf("number=%d\n",number);
printf("當前程序ID為: %d \n",getpid());
int i;
for(i = 0;i < number;i++)
{
web_connection_t* conn = (web_connection_t*)(event_list[i].data.ptr);
int socket = conn->fd;//當前觸發的fd
//讀事件
if ( event_list[i].events & EPOLLIN )
{
conn->read_event->handler(conn);
}
//寫事件
else if( event_list[i].events & EPOLLOUT )
{
conn->write_event->handler(conn);
}
else if( event_list[i].events & EPOLLERR )
{
}
}
}
return 0;
}
使用方法:
伺服器使用方法:直接執行即可
客戶端使用方法:編譯客戶端程式碼,然後 ./client 127.0.0.1 8083 5(最後一個代表客戶端程序數)
本程式在linux平臺下測試成功