項目Linux高性能服務器——個人總結(待擴充

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次均連接正常:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章