Linux高性能服務器
項目地址:https://github.com/Frank980908/SServer
個人總結:
-
socket通信:
Socket是應用層與TCP/IP協議族通信的中間軟件抽象層
socket編程原理:
服務器端初始化socket ,綁定端口(bind) 並監聽(listen),調用accept() 阻塞直到客戶端初始化socket並連接服務器(connect)。若連接成功,則通信建立,服務端 accept 返回用於傳輸的 socket 的文件描述符,客端端write寫,服務器read讀。客戶端close()斷開連接,服務器讀到EOF並處理完數據後close()關閉連接。 -
服務器併發模式:半同步/半反應堆模式
主線程充當異步線程,負責監聽socket上的所有事件,當socket上有可讀事件發生,即有新的連接請求到達,主線程就接受得到新的連接socket,然後往epoll內核事件表中註冊該socket上的讀寫事件,若連接socket上有讀寫事件發生,主線程就將該連接加入請求隊列(需要互斥鎖),各個空閒的工作線程通過競爭獲取睡眠在請求隊列上的任務接管權(需要互斥鎖)
- 有限狀態機:
解析請求行時的狀態機:
解析請求時的狀態機:
- 統一事件源
信號是一種異步事件:信號處理函數和程序的主循環是兩條不同的執行路線。很顯然,
信號處理函數需要儘可能快地執行完畢,以確保該信號不被屏蔽(爲了避免
一些競態條件,信號在處理期間,系統不會再次觸發它)太久。一種典型的解決方案是:
把信號的主要處理邏輯放到程序的主循環中,當信號處理函數被觸發時,它只是簡單地通
知主循環程序接收到信號,並把信號值傳遞給主循環,主循環再根據接收到的信號值執行
目標信號對應的邏輯代碼。信號處理函數通常使用管道來將信號“傳遞”給主循環:信號
處理函數往管道的寫端寫入信號值,主循環則從管道的讀端讀出該信號值。那麼主循環怎
麼知道管逍上何時有數據可讀呢?這很簡單,我們只需要使用I/0複用系統調用來監聽管
道的讀端文件描述符上的可讀事件。如此一來, 信號事件就能和其他/0事件一樣 被處理,
即統一事件源。
此服務器項目中,採用epoll_wait()監聽描述符上的可讀事件,達到統一事件源的目的。
-
忽略SIGPIPE信號
(SIGPIPE:往一個讀端關閉的管道或者socket連接中寫數據將引發,
默認行爲是結束進程)
爲了避免進程退出, 可以捕獲SIGPIPE信號, 或者忽略它, 給它設置SIG_IGN信號處理函數
(#define SIG_IGN ((void (*) (int)) 1)對捕獲的信號採取忽略操作或者默認操作。):
addsig(SIGPIPE, SIG_IGN);
這樣, 第二次調用write方法時, 會返回-1, 同時errno置爲SIGPIPE. 程序便能知道對端已經關閉.
addsig( SIGPIPE, SIG_IGN ); /*設置信號的處理函數*/
- ET與LT模式
epoll對文件描述符的操作有兩種模式: LT (Level Trigger,電平觸發)模式和ET (Edge
Trigger,邊沿觸發)模式。LT模式是默認的工作模式,這種模式下epoll相當於一個效率較
高的poll。當往epoll內核事件表中註冊一個文件描述符上的EPOLLET事件時,epoll 將以
ET模式來操作該文件描述符。ET模式是epoll的高效工作模式。
對於採用LT工作模式的文件描述符,當epoll_wait檢測到其上有事件發生並將此
事件通知應用程序後,應用程序可以不立即處理該事件。這樣,當應用程序下一次調用
epoll_ wait時,epoll_ wait 還會再次嚮應用程序通告此事件,直到該事件被處理。而對於
採用ET工作模式的文件描述符,當epollwait檢測到其上有事件發生並將此事件通知應
用程序後,應用程序必須立即處理該事件,因爲後續的epoll_ wait 調用將不再向應用程序
通知這一事件。可見,ET模式在很大程度上降低了同一個epoll事件被重複觸發的次數,
因此效率要比LT模式高,但是LT對代碼編寫要求比較低,不容易出現問題
addfd( epollfd , listenfd , false ) ; //對listenfd禁用ET模式 main.cpp
void addfd( int epollfd , int fd , bool one_shot ){
epoll_event event ;
event.data.fd = fd ;
event.events = EPOLLIN | EPOLLET | EPOLLRDHUP ;
/*如果對描述符socket註冊了EPOLLONESHOT事件,
那麼操作系統最多觸發其上註冊的一個可讀、可寫或者異常事件,且只觸發一次。
想要下次再觸發則必須使用epoll_ctl重置該描述符上註冊的事件,包括EPOLLONESHOT 事件。*/
/*只監聽一次事件,當監聽完這次事件之後,
如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列裏 */
//one_shot決定是否註冊EPOLLONESHOT事件。從而決定當事件觸發以後,socket是否會被禁止再次觸發,相當於是否啓用ET模式
if( one_shot ){
event.events |= EPOLLONESHOT ;
}
epoll_ctl( epollfd , EPOLL_CTL_ADD , fd , &event ) ;
setnonblocking( fd ) ;
} // epoll.cpp
void handle_event( int epollfd , int listenfd , threadpool<http_conn>* pool , http_conn* users,
epoll_event* events , int number , int timeout ){
log(LOG_INFO, __FILE__, __LINE__, "number %d",number);
for( int i = 0 ; i < number ; i++ ){
int sockfd = events[i].data.fd ;
log(LOG_INFO, __FILE__, __LINE__, "%d %d",sockfd, events[i].events);
if( sockfd == listenfd ){
/*若就緒文件描述符是listenfd,處理新的連接*/
}else if( events[i].events & ( EPOLLRDHUP | EPOLLHUP | EPOLLERR ) ){
/*有異常,直接關閉客戶連接*/
}else if( events[i].events & EPOLLIN ){
//禁用ET模式,只要socket讀緩存中還有未讀出的數據,這段代碼被觸發
//若使用ET模式,這段代碼不會被重複觸發,所以我們需要循環讀取數據
//以確保把socket讀緩存中的所有數據讀出
/*如
while( true ){
memset( buf, '\0', BUFFER_SIZE );
int ret = recv( sockfd, buf, BUFFER_SIZE - 1, 0 );
if( ret < 0 )
{
//對於非阻塞IO,下面的條件成立表示數據已經全部讀取完畢。此後epoll
// 就能再次觸發sockfd上的EPOLLIN時間,以驅動下一次讀操作。
if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) ){
//讀取完畢
break;
}
close( sockfd );
break;
}
else if( ret == 0 ){
//連接中斷,關閉描述符
}
else{
//進行下一次讀取
}
}
*/
//根據讀的結果,選擇將任務添加到線程池裏,還是關閉連接
}else if( events[i].events & EPOLLOUT ){
//根據寫的結果,決定是否關閉連接
}else{}
}
} // epoll.cpp
- 防止重複定義
#define 條件編譯
頭文件(.h)可以被頭文件或C文件包含;
重複包含(重複定義)
由於頭文件包含可以嵌套,那麼C文件就有可能包含多次同一個頭文件,就可能出現重複定義的問題的。
通過條件編譯開關來避免重複包含(重複定義)
#ifndef __headerfileXXX__
#define __headerfileXXX__
…
文件內容
…
#endif
服務器主函數流程圖如下:
main函數主要完成I/O(ip,port等配置文件存儲在Server_conf.conf),以及調用各功能函數接口,關閉文件
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <cassert>
#include <sys/epoll.h>
#include "locker.h"
#include "threadpool.h"
#include "http_conn.h"
#include "sock_conn.h"
#include "epoll.h"
#include "log.h"
#define MAX_FD 65536
#define MAX_EVENT_NUMBER 10000
/*
extern int addfd( int epollfd , int fd , bool one_shot );
extern int removefd( int epollfd , int fd );
*/
int main( int argc , char* argv[] ){
conn_t connect;
const char* connect_path = "Sever_config.conf";
if( read_con_info( &connect , connect_path ) == CONNECT_ERR ) {
fprintf(stderr, "Read information Failure!");
return -1;
}
addsig( SIGPIPE, SIG_IGN );
threadpool< http_conn >* pool = NULL;
try{
pool = new threadpool< http_conn >;
}
catch( ... ){
return 1;
}
http_conn* users = new http_conn[ MAX_FD ] ;
assert( users ) ;
int user_count = 0 ;
int listenfd = socket_conn( &connect ) ;
epoll_event events[ MAX_EVENT_NUMBER ] ;
int epollfd = createfd( 5 ) ;
addfd( epollfd , listenfd , false ) ; //對listenfd禁用ET模式
http_conn::m_epollfd = epollfd ;
while( true ){
int number = waitfd( epollfd , events , MAX_EVENT_NUMBER , -1 ) ;
if( number < 0 && errno != EINTR ){
printf("epoll failure!\n") ;
break ;
}
handle_event( epollfd , listenfd , pool , users , events , number , -1 );
}
close( epollfd ) ;
close( listenfd ) ;
delete [] users ;
delete pool ;
return 0 ;
}
sock_conn.cpp文件的程序流程圖:
封裝了一個經典的socket從創建,命名,監聽過程,另外還有從Server_conf.conf文件中讀取設置的線程數,ip,port等配置信息。
sock_conn.h
#ifndef SOCK_CONN
#define SOCK_CONN
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <signal.h>
#define PATHLEN 256
#define BUFFERSIZE 1024
#define ADDRESSNUMBER 64
#define MAXLISTEN 1024
enum CONNECT_STATUS{ CONNECT_OK = 0 , CONNECT_ERR } ;
typedef struct CONN{
char root[PATHLEN];
char ip[ADDRESSNUMBER];
int port ;
int thread_number ;
}conn_t ;
CONNECT_STATUS read_con_info( conn_t* connect , const char* filename );
int socket_conn( conn_t* connect );
void addsig( int sig , void( handler )(int) , bool restart = true );
void show_error( int connfd , const char* info );
#endif
sock_conn.cpp
#include "sock_conn.h"
CONNECT_STATUS read_con_info( conn_t* connect , const char* filename ){
FILE* fp = fopen(filename,"r");
if( !fp ){
return CONNECT_ERR ;
}
char buf[BUFFERSIZE];
int pos = 0;
while( fgets( buf + pos , BUFFERSIZE - pos , fp ) ){
if( !strncasecmp( buf + pos ,"threadNumber" , 12 ) ){
connect -> thread_number = atoi( buf + pos + 13 ) ;
}
if( !strncasecmp( buf + pos , "ip" , 2 ) ){
strncpy( connect -> ip , buf+pos+3 , strlen(buf+pos+3)-1 );
}
if( !strncasecmp( buf + pos , "port" , 4 ) ){
connect -> port = atoi( buf + pos + 5 );
}
if( !strncasecmp( buf + pos , "root" , 4 ) ){
strncpy( connect -> root , buf+pos+5 , strlen(buf+pos+5)-1 );
}
pos += strlen( buf + pos ) ;
}
}
int socket_conn( conn_t* connect ){
int listenfd = socket( PF_INET , SOCK_STREAM , 0 );
assert( listenfd >= 0 ) ;
/* <arpa/inet.h>
struct linger {
int l_onoff;
int l_linger;
};
setsockopt( listenfd , SOL_SOCKET , SO_LINGER , &tmp , sizeof(tmp) ) ;
int ret = 0 ;
struct sockaddr_in address ;
bzero( &address , sizeof(address) ) ;
address.sin_family = AF_INET ;
inet_pton( AF_INET , connect -> ip , &address.sin_addr ) ;
address.sin_port = htons( connect -> port ) ;
ret = bind( listenfd , (struct sockaddr* )&address , sizeof(address) ) ;
assert( ret >= 0 ) ;
ret = listen( listenfd , 5 ) ;
assert( ret >= 0 ) ;
return listenfd ;
}
/*信號處理函數*/
void addsig( int sig , void( handler )(int) , bool restart ){
struct sigaction sa ;
memset( &sa , '\0' , sizeof(sa) ) ;
sa.sa_handler = handler ;
if( restart ){
/* SA_RESTART : 重新調用被該信號終止的系統調用*/
sa.sa_flags |= SA_RESTART ;
}
sigfillset( &sa.sa_mask ) ;/*在信號集中設置所有信號*/
assert( sigaction( sig , &sa , NULL ) != -1 ) ;
}
void show_error( int connfd , const char* info ){
printf("%s",info) ;
send( connfd , info , strlen(info) , 0 ) ;
close( connfd ) ;
}
epoll.cpp封裝了Linux特有的epoll()系列系統調用,實現服務器的I/O複用特性:
其中,createfd()函數調用epoll_wait(),創建額外的文件描述符,唯一標識內核中的事件表。
setnonblocking()設置文件描述符爲非阻塞;
addfd()註冊文件描述符上的事件到內核事件表
removefd()調用epoll_ctl(), 從指定的epollfd中刪除fd文件描述符
modfd()增加文件描述符上的註冊事件
waitfd()調用epoll_wait()等待監聽事件發生,同時獲取監聽事件的數目
handle_event()則進行對事件的處理
epoll.h:
#ifndef EPOLL_H
#define EPOLL_H
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <cassert>
#include <sys/epoll.h>
#include "http_conn.h"
#include "sock_conn.h"
#include "threadpool.h"
int setnonblocking( int fd );
int waitfd( int epollfd , epoll_event* event , int max_events , int timeout );
int createfd( int size );
void addfd( int epollfd , int fd , bool one_shot );
void removefd( int epollfd , int fd );
void modfd( int epollfd , int fd , int ev );
void handle_event( int epollfd , int listenfd , threadpool<http_conn>* pool , http_conn* users,
epoll_event* events , int number , int timeout );
#endif
epoll.cpp:
#include "epoll.h"
#define MAX_FD 65536
int createfd( int size ){
int epollfd = epoll_create( size );
assert( epollfd != -1 ) ;
return epollfd ;
}
int setnonblocking( int fd ){
int flag = fcntl( fd , F_GETFL ) ;
int flag_new = flag | O_NONBLOCK ;
fcntl( fd , F_SETFL , flag_new ) ;
return flag ;
}
void addfd( int epollfd , int fd , bool one_shot ){
epoll_event event ;
event.data.fd = fd ;
event.events = EPOLLIN | EPOLLET | EPOLLRDHUP ;
if( one_shot ){
event.events |= EPOLLONESHOT ;
}
epoll_ctl( epollfd , EPOLL_CTL_ADD , fd , &event ) ;
setnonblocking( fd ) ;
}
void removefd( int epollfd , int fd ){
epoll_ctl( epollfd , EPOLL_CTL_DEL , fd , 0 ) ;
close( fd );
}
void modfd( int epollfd , int fd , int ev ){
epoll_event event ;
event.data.fd = fd ;
event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP ;
epoll_ctl( epollfd , EPOLL_CTL_MOD , fd , &event ) ;
}
int waitfd( int epollfd , epoll_event* event , int max_events , int timeout ){
int number = epoll_wait( epollfd , event , max_events , timeout );
return number ;
}
void handle_event( int epollfd , int listenfd , threadpool<http_conn>* pool , http_conn* users,
epoll_event* events , int number , int timeout ){
log(LOG_INFO, __FILE__, __LINE__, "number %d",number);
for( int i = 0 ; i < number ; i++ ){
int sockfd = events[i].data.fd ;
log(LOG_INFO, __FILE__, __LINE__, "%d %d",sockfd, events[i].events);
if( sockfd == listenfd ){
/*若就緒文件描述符是listenfd,處理新的連接*/
struct sockaddr_in client_address ;
socklen_t client_addrlength = sizeof( client_address ) ;
int connfd = accept( listenfd , (struct sockaddr*)&client_address , &client_addrlength) ;
log(LOG_INFO, __FILE__, __LINE__, "new client %d", connfd);
if( connfd < 0 ){
printf("errno is: %d\n",errno) ;
continue ;
}
if( http_conn::m_user_count >= MAX_FD ){
show_error( connfd , "Internal server busy!") ;
continue ;
}
/*初始化客戶連接*/
users[connfd].init( connfd , client_address ) ;
}else if( events[i].events & ( EPOLLRDHUP | EPOLLHUP | EPOLLERR ) ){
/*有異常,直接關閉客戶連接*/
users[sockfd].close_conn() ;
}else if( events[i].events & EPOLLIN ){
/*根據讀的結果,選擇將任務添加到線程池裏,還是關閉連接*/
if( users[sockfd].read() ){
pool -> append( users + sockfd ) ;
}else{
users[sockfd].close_conn() ;
}
}else if( events[i].events & EPOLLOUT ){
/*根據寫的結果,決定是否關閉連接*/
if( !users[sockfd].write() ){
log(LOG_INFO, __FILE__, __LINE__, "close 3");
users[sockfd].close_conn() ;
}
}else{}
}
}
http_conn.cpp文件則封裝了一系列對連接中獲取的http請求(暫時只處理了GET請求)作出的相應處理函數;
對接收到的GET請求,解析請求行,獲得請求方法,目標URL,HTTP版本號(處理了http/1.1),並作出迴應。
/*線程池模板參數類,封裝對邏輯任務的處理*/
#ifndef HTTPCONNECTION_H
#define HTTPCONNECTION_H
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <sys/stat.h>
#include <string.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <stdarg.h>
#include <errno.h>
#include <sys/uio.h>
#include "locker.h"
#include "log.h"
class http_conn
{
public:
/*文件名最大長度*/
static const int FILENAME_LEN = 200;
/*讀寫緩衝區大小*/
static const int READ_BUFFER_SIZE = 2048;
static const int WRITE_BUFFER_SIZE = 1024;
/*HTTP請求方法(暫時只寫GET)*/
enum METHOD { GET = 0, POST, HEAD, PUT, DELETE, TRACE, OPTIONS, CONNECT, PATCH };
enum CHECK_STATE { CHECK_STATE_REQUESTLINE = 0, CHECK_STATE_HEADER, CHECK_STATE_CONTENT };
/*服務器處理HTTP請求的可能結果*/
enum HTTP_CODE { NO_REQUEST, GET_REQUEST, BAD_REQUEST, NO_RESOURCE, FORBIDDEN_REQUEST, FILE_REQUEST, INTERNAL_ERROR, CLOSED_CONNECTION };
enum LINE_STATUS { LINE_OK = 0, LINE_BAD, LINE_OPEN };
public:
http_conn(){}
~http_conn(){}
public:
/*初始化新接受的連接*/
void init( int sockfd, const sockaddr_in& addr );
/*關閉連接*/
void close_conn( bool real_close = true );
/*處理客戶請求*/
void process();
/*非阻塞讀操作*/
bool read();
/*非阻塞寫*/
bool write();
private:
/*初始化連接*/
void init();
/*解析HTTP請求*/
HTTP_CODE process_read();
/*填充HTTP應答*/
bool process_write( HTTP_CODE ret );
/*被process_read調用以分析http請求*/
HTTP_CODE parse_request_line( char* text );
HTTP_CODE parse_headers( char* text );
HTTP_CODE parse_content( char* text );
HTTP_CODE do_request();
char* get_line() { return m_read_buf + m_start_line; }
LINE_STATUS parse_line();
/*被process_write調用以填充http應答*/
void unmap();
bool add_response( const char* format, ... );
bool add_content( const char* content );
bool add_status_line( int status, const char* title );
bool add_headers( int content_length );
bool add_content_length( int content_length );
bool add_linger();
bool add_blank_line();
public:
/*所有socket上的事件都被註冊到同一個epoll內核事件表
故將epoll文件描述符設置爲靜態的*/
static int m_epollfd;
static int m_user_count; //統計用戶數量
private:
/*讀HTTP連接的socket和對方的socket地址*/
int m_sockfd;
sockaddr_in m_address;
/*讀緩衝區*/
char m_read_buf[ READ_BUFFER_SIZE ];
/*標識讀緩衝中已經讀入的客戶數據最後一個字節的下一個位置*/
int m_read_idx;
/*當前正在分析的字符在讀緩衝區中的位置*/
int m_checked_idx;
/*當前正在解析的行的起始位置*/
int m_start_line;
/*寫緩衝區*/
char m_write_buf[ WRITE_BUFFER_SIZE ];
/*寫緩衝區中待發送的字節數*/
int m_write_idx;
/*主狀態機當前所處的狀態*/
CHECK_STATE m_check_state;
/*請求方法*/
METHOD m_method;
/*客戶請求的目標文件完整路徑(doc_root+m_url),doc_root是網站根目錄*/
char m_real_file[ FILENAME_LEN ];
/*客戶請求的目標文件文件名*/
char* m_url;
/*HTTP協議版本號,暫僅支持HTTP/1.1*/
char* m_version;
/*主機名*/
char* m_host;
/*HTTP請求消息長度*/
int m_content_length;
/*HTTP請求是否要求保持連接*/
bool m_linger;
/*客戶請求的目標文件被mmap到內存中的起始位置*/
char* m_file_address;
/*目標文件狀態,判斷文件是否存在,是否爲目錄,是否可讀並獲取文件大小等信息*/
struct stat m_file_stat;
/*採用writev執行寫操作,避免多次系統調用*/
/*
#include <sys/uio.h>
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
struct iovec {
void *iov_base; // Starting address
size_t iov_len; // Number of bytes to transfer
};
*/
struct iovec m_iv[2];
/*被寫內存塊的數量*/
int m_iv_count;
};
#endif
http_conn.cpp
#include "http_conn.h"
#include "epoll.h"
/*定義HTTP響應的一些狀態信息*/
const char* ok_200_title = "OK";
const char* error_400_title = "Bad Request";
const char* error_400_form = "Your request has bad syntax or is inherently impossible to satisfy.\n";
const char* error_403_title = "Forbidden";
const char* error_403_form = "You do not have permission to get file from this server.\n";
const char* error_404_title = "404 Not Found";
const char* error_404_form = "The requested file was not found on this server.\n";
const char* error_500_title = "Internal Error";
const char* error_500_form = "There was an unusual problem serving the requested file.\n";
/*網站根目錄*/
const char* doc_root = "/var/www/html" ;
/*設置非阻塞*/
int http_conn::m_user_count = 0 ;
int http_conn::m_epollfd = -1 ;
void http_conn::close_conn( bool real_close ){
if( real_close & m_sockfd != -1 ){
removefd( m_epollfd , m_sockfd ) ;
m_sockfd = -1 ;
/*關閉一個連接,客戶總量-1*/
m_user_count -- ;
}
}
void http_conn::init( int sockfd , const sockaddr_in& addr ){
m_sockfd = sockfd ;
m_address = addr ;
int error = 0 ;
socklen_t len = sizeof(error) ;
getsockopt( m_sockfd , SOL_SOCKET , SO_ERROR , &error , &len ) ;
addfd( m_epollfd , sockfd , true ) ;
}
void http_conn::init(){
m_check_state = CHECK_STATE_REQUESTLINE ;
m_linger = false ;
m_method = GET ;
m_url = 0 ;
m_version = 0 ;
m_content_length = 0 ;
m_host = 0 ;
m_start_line = 0 ;
m_checked_idx = 0 ;
m_read_idx = 0 ;
m_write_idx = 0 ;
memset( m_read_buf , '\0' , READ_BUFFER_SIZE );
memset( m_write_buf , '\0' , WRITE_BUFFER_SIZE );
memset( m_real_file , '\0' , FILENAME_LEN );
}
/*從狀態機*/
http_conn::LINE_STATUS http_conn::parse_line(){
char ch ;
for( ; m_checked_idx < m_read_idx ; m_checked_idx ++ ){
ch = m_read_buf[ m_checked_idx ];
if( ch == '\r' ){
if( ( m_checked_idx + 1 ) == m_read_idx ){
return LINE_OPEN ;
}else if( m_read_buf[ m_checked_idx + 1 ] == '\n' ){
m_read_buf[ m_checked_idx++ ] = '\0' ;
m_read_buf[ m_checked_idx++ ] = '\0' ;
return LINE_OK ;
}
return LINE_BAD ;
}else if( ch == '\n' ){
if( m_checked_idx > 1 && m_read_buf[m_checked_idx+1] == '\r' ){
m_read_buf[ m_checked_idx - 1 ] = '\0' ;
m_read_buf[ m_checked_idx ++ ] = '\0' ;
return LINE_OK ;
}
return LINE_BAD ;
}
}
return LINE_OPEN ;
}
/*循環讀取客戶數據,直到無數據可讀或者對方關閉連接*/
bool http_conn::read(){
if( m_read_idx >= READ_BUFFER_SIZE ){
return false ;
}
int bytes_read = 0 ;
while( true ){
bytes_read = recv( m_sockfd , m_read_buf + m_read_idx , READ_BUFFER_SIZE - m_read_idx , 0 ) ;
if( bytes_read == -1 ){
if( errno == EAGAIN || errno == EWOULDBLOCK ){
break ;
}
return false ;
}else if( !bytes_read ){
/*recv函數在等待協議接收數據時網絡中斷了,返回0*/
return false ;
}
//無錯誤發生,recv()返回讀入的字節數
m_read_idx += bytes_read ;
}
return true ;
}
/*解析HTTP請求行,獲得 請求方法,目標URL,HTTP版本號*/
http_conn::HTTP_CODE http_conn::parse_request_line( char* text ){
m_url = strpbrk( text , " \t" ) ;
if( !m_url ){
return BAD_REQUEST ;
}
*m_url ++ = '\0' ;
char* method = text ;
if( !strcasecmp( method , "GET" ) ){
m_method = GET ;
}else{
return BAD_REQUEST ;
}
m_url += strspn( m_url , " \t" ) ;
m_version = strpbrk( m_url , " \t" ) ;
if( !m_version ){
return BAD_REQUEST ;
}
*m_version++ = '\0' ;
m_version += strspn( m_version , " \t" );
if( strcasecmp( m_version , "HTTP/1.1" ) ){
return BAD_REQUEST ;
}
if( !strncasecmp( m_url , "http://" , 7 ) ){
m_url += 7 ;
m_url = strchr( m_url , '/' ) ;
}
if( !m_url || m_url[0] != '/' ){
return BAD_REQUEST ;
}
m_check_state = CHECK_STATE_HEADER ; //狀態轉移到檢查頭部信息
return NO_REQUEST ;
}
/*解析HTTP請求的頭部信息*/
http_conn::HTTP_CODE http_conn::parse_headers( char* text ){
/*遇到空行,表示頭部字段解析完畢*/
if( text[0] == '\0' ){
if( m_content_length ){
m_check_state = CHECK_STATE_CONTENT ;
return NO_REQUEST ;
}
/*否則說明得到一個完整的HTTP對象*/
return GET_REQUEST ;
}else if( !strncasecmp( text , "Connection" , 11 ) ){
text += 11 ;
text += strspn( text , " \t" ) ;
if( !strcasecmp( text , "keep-alive" ) ){
m_linger = true ;
}
}else if( !strncasecmp( text, "Content-Length:", 15 ) ){
/*處理Content-Length頭部字段*/
text += 15 ;
text += strspn( text , " \t" ) ;
m_content_length = atol( text );
}else if( !strncasecmp( text, "Host:", 5 ) ){
//處理HOST頭部字段
text += 5 ;
text += strspn( text , " \t" );
m_host = text ;
}else{
printf("oop! unknown header %s\n",text);
}
}
/*這裏沒有真正解析HTTP請求的消息體,只是判斷是否被完整讀入*/
http_conn::HTTP_CODE http_conn::parse_content( char* text ){
if( m_read_idx >= ( m_content_length + m_checked_idx ) ){
text[ m_content_length ] = '\0' ;
return GET_REQUEST ;
}
return NO_REQUEST ;
}
/*主狀態機*/
http_conn::HTTP_CODE http_conn::process_read(){
LINE_STATUS line_status = LINE_OK ;
HTTP_CODE ret = NO_REQUEST ;
char* text = 0 ;
while( ( m_check_state == CHECK_STATE_CONTENT && line_status == LINE_OK )
|| ( line_status = parse_line() ) == LINE_OK ){
text = get_line() ;
m_start_line = m_checked_idx ;
printf("got one http line: %s\n",text );
switch( m_check_state ){
case CHECK_STATE_REQUESTLINE:{
ret = parse_request_line( text );
if( ret == BAD_REQUEST ){
return BAD_REQUEST ;
}
break ;
}
case CHECK_STATE_HEADER:{
ret = parse_headers( text );
if( ret == BAD_REQUEST ){
return BAD_REQUEST ;
}else if( ret == GET_REQUEST ){
return do_request() ;
}
break;
}
case CHECK_STATE_CONTENT:{
ret = parse_content( text ) ;
if( ret == GET_REQUEST ){
return do_request() ;
}
line_status = LINE_OPEN ;
break ;
}
default:{
return INTERNAL_ERROR ;
}
}
}
return NO_REQUEST ;
}
/*得到一個完整,正確的HTTP請求時,分析目標文件的屬性
若目標文件存在,且對所有用戶可讀,並且不是目錄,則使用mmap將其映射到內存地址
m_file_address,並告訴調用者獲取文件成功*/
http_conn::HTTP_CODE http_conn::do_request(){
strcpy( m_real_file , doc_root ) ;
int len = strlen( doc_root ) ;
strncpy( m_real_file + len , m_url , FILENAME_LEN - len - 1 ) ;
if( stat( m_real_file , &m_file_stat ) < 0 ){
return NO_RESOURCE ;
}
if( !( m_file_stat.st_mode & S_IROTH ) ){
return FORBIDDEN_REQUEST ;
}
if ( S_ISDIR( m_file_stat.st_mode ) ){
return BAD_REQUEST;
}
int fd = open( m_real_file , O_RDONLY ) ;
m_file_address = (char*)mmap( 0 , m_file_stat.st_size , PROT_READ , MAP_PRIVATE, fd , 0 ) ;
close( fd );
return FILE_REQUEST ;
}
/*對內存映射去執行munmap操作,取消參數所指的映射內存起始地址*/
void http_conn::unmap(){
if( m_file_address ){
munmap( m_file_address , m_file_stat.st_size ) ;
m_file_address = 0 ;
}
}
/*寫HTTP響應*/
bool http_conn::write(){
int temp = 0;
int bytes_have_send = 0;
int bytes_to_send = m_write_idx;
if( !bytes_to_send ){
modfd( m_epollfd , m_sockfd , EPOLLIN ) ;
init() ;
return true ;
}
while( true ){
temp = writev( m_sockfd , m_iv , m_iv_count ) ;
if( temp <= -1 ){
/*如果TCP寫緩衝沒有空間,則等待下一輪EPOLLOUT事件
雖在此期間,服務器無法立即接受到同一客戶的下一個請求,但這可以
保證連接的完整性*/
if( errno == EAGAIN ){
modfd( m_epollfd , m_sockfd , EPOLLOUT );
return true;
}
unmap();
return false ;
}
bytes_to_send -= temp ;
bytes_have_send += temp ;
if( bytes_to_send <= bytes_have_send ){
unmap() ;
/*發送HTTP響應成功,根據HTTP請求中的Connection字段決定是否立即關閉連接*/
if( m_linger ){
init() ;
modfd( m_epollfd , m_sockfd , EPOLLIN );
return true ;
}else{
modfd( m_epollfd , m_sockfd , EPOLLIN );
return false ;
}
}
}
}
/*往寫緩衝中寫入待發送的數據*/
bool http_conn::add_response( const char* format , ... ){
if( m_write_idx >= WRITE_BUFFER_SIZE ){
return false ;
}
va_list arg_list ;
/*C 庫宏 void va_start(va_list ap, last_arg)
初始化 ap 變量,它與 va_arg 和 va_end 宏是一起使用的。
last_arg 是最後一個傳遞給函數的已知的固定參數,即省略號之前的參數。*/
va_start( arg_list , format );
int len = vsnprintf( m_write_buf + m_write_idx, WRITE_BUFFER_SIZE - 1 - m_write_idx, format, arg_list );
if( len >= ( WRITE_BUFFER_SIZE - 1 - m_write_idx ) ){
return false;
}
m_write_idx += len;
va_end( arg_list );
return true;
}
bool http_conn::add_status_line( int status , const char* title ){
return add_response("%s %d %s\r\n","HTTP/1.1",status,title);
}
bool http_conn::add_headers( int content_len ){
add_content_length( content_len );
add_linger() ;
add_blank_line() ;
}
bool http_conn::add_content_length( int content_len ){
return add_response( "Content-Length: %d\r\n", content_len );
}
bool http_conn::add_linger(){
return add_response( "Connection: %s\r\n", ( m_linger == true ) ? "keep-alive" : "close" );
}
bool http_conn::add_blank_line(){
return add_response( "%s", "\r\n" );
}
bool http_conn::add_content( const char* content ){
return add_response( "%s", content );
}
/*根據服務器處理HTTP請求的結果,決定返回給客戶端的內容*/
bool http_conn::process_write( HTTP_CODE ret ){
switch ( ret ){
case INTERNAL_ERROR:{
add_status_line( 500, error_500_title );
add_headers( strlen( error_500_form ) );
if ( ! add_content( error_500_form ) ){
return false;
}
break;
}
case BAD_REQUEST:{
add_status_line( 400, error_400_title );
add_headers( strlen( error_400_form ) );
if ( ! add_content( error_400_form ) ){
return false;
}
break;
}
case NO_RESOURCE:{
add_status_line( 404, error_404_title );
add_headers( strlen( error_404_form ) );
if ( ! add_content( error_404_form ) ){
return false;
}
break;
}
case FORBIDDEN_REQUEST:{
add_status_line( 403, error_403_title );
add_headers( strlen( error_403_form ) );
if ( ! add_content( error_403_form ) ){
return false;
}
break;
}
case FILE_REQUEST:{
add_status_line( 200, ok_200_title );
if ( m_file_stat.st_size != 0 ){
add_headers( m_file_stat.st_size );
m_iv[ 0 ].iov_base = m_write_buf;
m_iv[ 0 ].iov_len = m_write_idx;
m_iv[ 1 ].iov_base = m_file_address;
m_iv[ 1 ].iov_len = m_file_stat.st_size;
m_iv_count = 2;
return true;
}
else{
const char* ok_string = "<html><body></body></html>";
add_headers( strlen( ok_string ) );
if ( ! add_content( ok_string ) ){
return false;
}
}
}
default:{
return false;
}
}
m_iv[ 0 ].iov_base = m_write_buf;
m_iv[ 0 ].iov_len = m_write_idx;
m_iv_count = 1;
return true;
}
/*由線程池中的工作線程調用,處理HTTP請求的入口函數*/
void http_conn::process(){
HTTP_CODE read_ret = process_read();
log(LOG_INFO, __FILE__, __LINE__, "read HTTP CODE %d", read_ret);
if ( read_ret == NO_REQUEST ){
modfd( m_epollfd, m_sockfd, EPOLLIN );
return;
}
bool write_ret = process_write( read_ret );
log(LOG_INFO, __FILE__, __LINE__, "write HTTP CODE %d", write_ret);
if ( ! write_ret ){
close_conn();
}
modfd( m_epollfd, m_sockfd, EPOLLOUT );
}
locker.h 封裝信號量,條件變量,互斥鎖
#ifndef LOCKER_H
#define LOCKER_H
#include <exception>
#include <pthread.h>
#include <semaphore.h>
/*封裝信號量的類*/
class sem{
public:
sem(){
if( sem_init( &m_sem , 0 , 0 ) != 0 ){
/*構造函數沒有返回值,可以通過拋出異常報告錯誤*/
throw std::exception() ;
}
}
/*銷燬信號量*/
~sem(){
sem_destroy( &m_sem );
}
/*等待信號量*/
bool wait(){
return !sem_wait( &m_sem ) ;
}
/*增加信號量*/
bool post(){
return !sem_post( &m_sem );
}
private:
sem_t m_sem ;
};
/*封裝互斥鎖的類*/
class locker{
public:
/*創建並初始化互斥鎖*/
locker(){
if( pthread_mutex_init( &m_mutex , NULL ) ){
throw std::exception() ;
}
}
/*銷燬互斥鎖*/
~locker(){
pthread_mutex_destroy( &m_mutex ) ;
}
/*獲取互斥鎖*/
bool lock(){
return !pthread_mutex_lock( &m_mutex ) ;
}
/*釋放互斥鎖*/
bool unlock(){
return !pthread_mutex_unlock( &m_mutex ) ;
}
private:
pthread_mutex_t m_mutex ;
};
/*封裝條件變量的類*/
class cond{
public:
/*創建並初始化條件變量*/
cond(){
if( pthread_mutex_init( &m_mutex , NULL ) ){
throw std::exception() ;
}
if( pthread_cond_init( &m_cond , NULL ) ){
/*構造函數中一旦出現問題,就應該立即釋放已經成功分配了的資源*/
pthread_mutex_destroy( &m_mutex );
throw std::exception() ;
}
}
/*銷燬條件變量*/
~cond(){
pthread_mutex_destroy( &m_mutex );
pthread_cond_destroy( &m_cond );
}
/*等待條件變量*/
bool wait(){
int ret = 0 ;
pthread_mutex_lock( &m_mutex ) ;
ret = pthread_cond_wait( &m_cond , &m_mutex );
pthread_mutex_unlock( &m_mutex );
return !ret ;
}
/*喚醒等待條件變量的線程*/
bool signal(){
return !pthread_cond_signal( &m_cond );
}
private:
pthread_mutex_t m_mutex ;
pthread_cond_t m_cond ;
};
#endif
threadpool.h : 封裝了半同步/半反應堆併發模式的線程池類,使用互斥鎖完成對線程池的各種操作(必須保證所有用戶請求時無狀態,因爲實現可能同一連接上的不同請求是不同線程處理)
#ifndef THREADPOOL_H
#define THREADPOOL_H
#include <list>
#include <cstdio>
#include <exception>
#include <pthread.h>
#include "locker.h"
/*線程池類,將它定義爲模板類是爲了代碼複用,模板參數T是任務類*/
template< typename T >
class threadpool{
public:
/*參數thread_number是線程池中線程的數量,
max_requests是請求隊列中最多允許的,等待處理的請求的數量*/
threadpool( int thread_number = 8 , int max_requents = 1000 ) ;
~threadpool();
/*往請求隊列中添加任務*/
bool append( T* request );
private:
/*工作線程運行的函數,它不斷從工作隊列取出任務並執行之*/
static void* worker( void* arg );
void run() ;
private:
int m_thread_number ; /*線程池中的線程數*/
int m_max_requests ; /*請求隊列中允許的最大請求數*/
pthread_t* m_threads ; /*描述線程池的數組,大小爲m_thread_number*/
std::list< T* > m_workqueue ; /*請求隊列*/
locker m_queuelocker ; /*保護請求隊列的互斥鎖*/
sem m_queuestat ; /*是否有任務需要處理*/
bool m_stop ; /*是否結束線程*/
};
template< typename T >
threadpool< T >::threadpool( int thread_number , int max_requests ):
m_thread_number( thread_number ),m_max_requests( max_requests ),
m_stop(false),m_threads(NULL){
if( thread_number <= 0 || max_requests <= 0 ){
throw std::exception() ;
}
m_threads = new pthread_t( m_thread_number ) ;
if( !m_threads ){
throw std::exception() ;
}
/*創建thread_number個線程,並將它們都設置爲脫離線程*/
for( int i = 0 ; i < thread_number ; i++ ){
printf("create the %dth thread\n",i);
if( pthread_create( m_threads + i , NULL , worker , this ) ){
delete [] m_threads ;
throw std::exception() ;
}
if( pthread_detach( m_threads[i] ) ){
delete [] m_threads ;
throw std::exception() ;
}
}
}
template< typename T >
threadpool< T >::~threadpool(){
delete[] m_threads ;
m_stop = true ;
}
template< typename T >
bool threadpool< T >::append( T* request ){
/*操作工作隊列時一定要加鎖*/
m_queuelocker.lock() ;
if( m_workqueue.size() > m_max_requests ){
m_queuelocker.unlock() ;
return false ;
}
m_workqueue.push_back( request );
m_queuelocker.unlock() ;
m_queuestat.post() ;
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();
m_queuelocker.lock() ;
if( m_workqueue.empty() ){
m_queuelocker.unlock() ;
continue ;
}
T* request = m_workqueue.front() ;
m_workqueue.pop_front() ;
m_queuelocker.unlock() ;
if( !request ){
continue ;
}
request -> process() ;
}
}
#endif
log.h
#ifndef LOG_H
#define LOG_H
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <syslog.h>
#include <cstdarg>
void set_loglevel(int log_level = LOG_DEBUG);
void log(int log_level, const char* file_name, int line_num, const char* format, ...);
#endif
log.cpp
#include "log.h"
static int level = LOG_INFO;
static int LOG_BUFFER_SIZE = 2048;
static const char* loglevels[] = {
"emerge!", "alert!", "critical!", "error!", "warn!", "notice:", "info:","debug:"
};
void set_loglevel(int log_level) {
level = log_level;
}
void log(int log_level, const char* file_name, int line_num, const char* format, ...) {
if(log_level > level) {
return;
}
time_t tmp = time(NULL);
struct tm* cur_time = localtime(&tmp);
if(!cur_time) {
return;
}
char arg_buffer[LOG_BUFFER_SIZE];
memset(arg_buffer, 0, sizeof(arg_buffer));
strftime(arg_buffer, LOG_BUFFER_SIZE-1, "[%x %X] ", cur_time);
printf("%s", arg_buffer);
printf("%s:%04d ", file_name, line_num);
printf("%s ",loglevels[log_level-LOG_EMERG]);
va_list arg_list;
va_start(arg_list, format);
memset(arg_buffer, 0, LOG_BUFFER_SIZE);
vsnprintf(arg_buffer, LOG_BUFFER_SIZE-1, format, arg_list);
printf("%s\n", arg_buffer);
fflush(stdout);
va_end(arg_list);
}
配置文件:Server_config.conf
threadNumber=4
ip=127.0.0.1
port=1234
root=/var/html/www
編寫makefile使用make同時編譯文件
make
運行服務器::創建8個線程
利用壓力測試文件訪問服務器1000次均連接正常: