main.cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <signal.h>
#include "locker.h"
#include "threadpool.h"
#include "http_conn.h"
#include <assert.h>
#define MAX_FD 65535
#define MAX_EVENT_NUMBER 10000
void addsig(int sig, void(handler)(int)){
struct sigaction sa;
memset(&sa, '\0', sizeof(sa));
sa.sa_handler= handler;
sigfillset(&sa.sa_mask);
assert( sigaction( sig, &sa, NULL ) != -1 );
}
extern void addfd(int epollfd, int fd, bool one_shot);
extern void removefd(int epollfd, int fd);
extern void modfd(int epollfd, int fd, int ev);
int main(int argc, char * argv[]){
if(argc <= 1){
printf("按照如下格式运行: %s port_number\n", basename(argv[0]));
}
int port = atoi(argv[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 ];
int listenfd = socket(PF_INET, SOCK_STREAM, 0);
int reuse = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_port = htons(port);
address.sin_addr.s_addr = INADDR_ANY;
bind(listenfd, (struct sockaddr *)&address, sizeof(address));
listen(listenfd, 5);
epoll_event events[ MAX_EVENT_NUMBER ];
int epollfd = epoll_create(5);
addfd(epollfd, listenfd, false);
http_conn::m_epollfd = epollfd;
while(true){
int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
if( (number < 0) && (errno != EINTR)){
printf("epoll failure\n");
break;
}
for(int i = 0; i < number; i++){
int sockfd = events[i].data.fd;
if(sockfd == listenfd){
struct sockaddr_in client_address;
socklen_t client_addrlength = sizeof(client_address);
int connfd = accept(listenfd, (struct sockaddr*)&client_address, &client_addrlength);
if(connfd < 0){
printf( "errno is: %d\n", errno);
continue;
}
if(http_conn::m_user_count >= MAX_FD){
close(connfd);
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()){
users[sockfd].close_conn();
}
}
}
}
close(epollfd);
close(listenfd);
delete []users;
delete pool;
return 0;
}
http_conn.h
#ifndef HTTPCONNECTION_H
#define HTTPCONNECTION_H
#include <sys/epoll.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <stdarg.h>
#include <errno.h>
#include "locker.h"
#include <sys/uio.h>
#include <string.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;
enum METHOD {GET = 0, POST, HEAD, PUT, DELETE, TRACE, OPTIONS, CONNECT};
enum CHECK_STATE { CHECK_STATE_REQUESTLINE = 0, CHECK_STATE_HEADER, CHECK_STATE_CONTENT };
enum LINE_STATUS { LINE_OK = 0, LINE_BAD, LINE_OPEN };
enum HTTP_CODE { NO_REQUEST, GET_REQUEST, BAD_REQUEST, NO_RESOURCE, FORBIDDEN_REQUEST, FILE_REQUEST, INTERNAL_ERROR, CLOSED_CONNECTION };
public:
http_conn(){}
~http_conn(){}
public:
void process();
void init(int sockfd, const sockaddr_in & addr);
void close_conn();
bool read();
bool write();
void unmap();
private:
void init();
HTTP_CODE process_read();
HTTP_CODE parse_request_line(char * text);
HTTP_CODE parse_headers(char * text);
HTTP_CODE parse_content(char * text);
bool process_write( HTTP_CODE ret );
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();
bool add_content(const char* content);
bool add_content_type();
bool add_response(const char* format, ...);
LINE_STATUS parse_line();
char * get_line(){ return m_read_buf + m_start_line; }
HTTP_CODE do_request();
public:
static int m_epollfd;
static int m_user_count;
private:
int m_sockfd;
sockaddr_in m_address;
char m_read_buf[READ_BUFFER_SIZE];
int m_read_idx;
int m_check_index;
int m_start_line;
CHECK_STATE m_check_state;
METHOD m_method;
char m_real_file[ FILENAME_LEN ];
char * m_url;
char * m_version;
char * m_host;
int m_content_length;
bool m_linger;
struct stat m_file_stat;
char m_write_buf[ WRITE_BUFFER_SIZE ];
int m_write_idx;
char* m_file_address;
struct iovec m_iv[2];
int m_iv_count;
int bytes_to_send;
int bytes_have_send;
};
#endif
http_conn.cpp
#include "http_conn.h"
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 = "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";
int http_conn::m_epollfd = -1;
int http_conn::m_user_count = 0;
const char* doc_root = "/home/nowcoder/webserver1/resources";
int setnonblocking(int fd){
int old_flag = fcntl(fd, F_GETFL);
int new_flag = old_flag | O_NONBLOCK;
fcntl(fd, F_SETFL, new_flag);
return old_flag;
}
void addfd(int epollfd, int fd, bool one_shot){
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | 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);
}
extern void modfd(int epollfd, int fd, int ev){
epoll_event event;
event.data.fd = fd;
event.events = ev | EPOLLONESHOT | EPOLLRDHUP;
epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event);
}
void http_conn::init(int sockfd, const sockaddr_in & addr){
m_sockfd = sockfd;
m_address = addr;
int reuse = 1;
setsockopt(m_sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
addfd(m_epollfd, m_sockfd, true);
m_user_count++;
init();
}
void http_conn::init(){
bytes_to_send = 0;
bytes_have_send = 0;
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_check_index = 0;
m_read_idx = 0;
m_write_idx = 0;
bzero(m_read_buf, READ_BUFFER_SIZE);
bzero(m_write_buf, READ_BUFFER_SIZE);
bzero(m_real_file, FILENAME_LEN);
}
void http_conn::close_conn(){
if(m_sockfd != -1){
removefd(m_epollfd, m_sockfd);
m_sockfd = -1;
m_user_count--;
}
}
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 == 0){
printf("client is closed!\n");
return false;
}
m_read_idx += bytes_read;
}
printf("读取到了数据:\n");
printf("%s\n", m_read_buf);
return true;
}
bool http_conn::write(){
printf("write+++++++++++++++++++++++++++++++++++++++\n");
int temp = 0;
if( bytes_to_send == 0){
modfd(m_epollfd, m_sockfd, EPOLLIN);
init();
return true;
}
while(1){
temp = writev(m_sockfd, m_iv, m_iv_count);
if(temp <= -1){
if( errno == EAGAIN ){
modfd( m_epollfd, m_sockfd, EPOLLIN);
return true;
}
unmap();
return false;
}
bytes_have_send += temp;
bytes_to_send -= temp;
if(bytes_have_send > m_iv[0].iov_len){
m_iv[0].iov_len = 0;
m_iv[1].iov_base = m_file_address + (bytes_have_send - m_write_idx);
m_iv[1].iov_len = bytes_to_send;
}else{
m_iv[0].iov_base = m_write_buf + bytes_have_send;
m_iv[0].iov_len -= temp;
}
if(bytes_to_send <= 0){
unmap();
modfd(m_epollfd, m_sockfd, EPOLLIN);
if(m_linger){
init();
return true;
}else{
return false;
}
}
}
}
void http_conn::unmap(){
if(m_file_address){
munmap(m_file_address, m_file_stat.st_size);
m_file_address = 0;
}
}
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_check_index;
printf( "got 1 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_conn::HTTP_CODE http_conn::parse_request_line(char * text){
m_url = strpbrk(text, " \t");
*m_url++ = '\0';
char * method = text;
if( strcasecmp(method, "GET") == 0){
m_method = GET;
}else{
return BAD_REQUEST;
}
m_version = strpbrk(m_url, " \t");
if(!m_version){
return BAD_REQUEST;
}
*m_version++ = '\0';
if(strcasecmp(m_version, "HTTP/1.1") != 0){
return BAD_REQUEST;
}
if(strncasecmp(m_url, "http://", 7) == 0){
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_conn::HTTP_CODE http_conn::parse_headers(char * text){
if( text[0] == '\0'){
if(m_content_length != 0){
m_check_state = CHECK_STATE_CONTENT;
return NO_REQUEST;
}
return GET_REQUEST;
}else if( strncasecmp(text, "Connection:", 11) == 0){
text += 11;
text += strspn(text, " \t");
if( strcasecmp(text, "keep-alive") == 0 ){
m_linger = true;
}
}else if( strncasecmp (text, "Content-Length:", 15) == 0){
text += 15;
text += strspn(text, " \t");
m_content_length = atol(text);
}else if( strncasecmp (text, "Host:", 5) == 0){
text += 5;
text += strspn(text, "\t");
m_host = text;
}else{
printf("oop! unknow header %s\n",text);
}
return NO_REQUEST;
}
http_conn::HTTP_CODE http_conn::parse_content(char * text){
if( m_read_idx >= ( m_content_length + m_check_index) ){
text[m_content_length] = '\0';
return GET_REQUEST;
}
return NO_REQUEST;
}
http_conn::LINE_STATUS http_conn::parse_line(){
char temp;
for(; m_check_index < m_read_idx; ++m_check_index){
temp = m_read_buf[m_check_index];
if( temp == '\r' ){
if( m_check_index + 1 == m_read_idx){
return LINE_OPEN;
}else if(m_read_buf[m_check_index + 1] == '\n'){
m_read_buf[m_check_index++] = '\0';
m_read_buf[m_check_index++] = '\0';
return LINE_OK;
}
return LINE_BAD;
}else if(temp == '\n'){
if((m_check_index > 1) && (m_read_buf[m_check_index - 1] == '\r')){
m_read_buf[m_check_index - 1] = '\0';
m_read_buf[m_check_index ++ ] = '\0';
return LINE_OK;
}
return LINE_BAD;
}
}
return LINE_OPEN;
}
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;
}
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 );
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;
bytes_to_send = m_write_idx + m_file_stat.st_size;
return true;
default:
return false;
}
m_iv[ 0 ].iov_base = m_write_buf;
m_iv[ 0 ].iov_len = m_write_idx;
m_iv_count = 1;
bytes_to_send = m_write_idx;
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_content_type();
add_linger();
add_blank_line();
return true;
}
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 );
}
bool http_conn::add_content_type() {
return add_response("Content-Type:%s\r\n", "text/html");
}
bool http_conn::add_response( const char* format, ... ) {
if( m_write_idx >= WRITE_BUFFER_SIZE ) {
return false;
}
va_list arg_list;
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;
}
void http_conn::process(){
HTTP_CODE read_ret = process_read();
if(read_ret == NO_REQUEST){
modfd(m_epollfd, m_sockfd, EPOLLIN);
return;
}
bool write_ret = process_write( read_ret );
if( !write_ret ){
close_conn();
}
modfd( m_epollfd, m_sockfd, EPOLLOUT);
}
threadpool.h
#ifndef THREADPOOL_H
#define THREADPOOL_H
#include <pthread.h>
#include <list>
#include "locker.h"
#include <cstdio>
#include <exception>
using namespace std;
template <typename T>
class threadpool
{
public:
threadpool(int thread_number = 8, int max_requests = 10000);
~threadpool();
bool append(T* request);
private:
static void * worker(void * arg);
void run();
private:
int m_thread_number;
pthread_t * m_threads;
int m_max_requests;
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(m_thread_number <= 0 || m_max_requests <= 0){
throw exception();
}
m_threads = new pthread_t[m_thread_number];
if(!m_threads){
throw exception();
}
for(int i = 0; i < thread_number; i++){
printf("create the %dth request\n", i);
if( pthread_create(m_threads + i, NULL, worker, this) != 0){
delete [] m_threads;
throw exception();
}
if( pthread_detach(m_threads[i]) ){
delete [] m_threads;
throw 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();
throw exception();
}
m_workqueue.push_back(request);
m_queuestat.post();
m_queuelocker.unlock();
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
locker.h
#ifndef LOCKER_H
#define LOCKER_H
#include <exception>
#include <pthread.h>
#include <semaphore.h>
using namespace std;
class locker{
private:
pthread_mutex_t m_mutex;
public:
locker(){
if(pthread_mutex_init(&m_mutex, NULL) != 0){
throw exception();
}
}
~locker(){
pthread_mutex_destroy(&m_mutex);
}
bool lock(){
return pthread_mutex_lock(&m_mutex);
}
bool unlock(){
return pthread_mutex_unlock(&m_mutex);
}
pthread_mutex_t * get(){
return &m_mutex;
}
};
class cond{
private:
pthread_cond_t m_cond;
public:
cond(){
if(pthread_cond_init(&m_cond, NULL) != 0){
throw exception();
}
}
~cond(){
pthread_cond_destroy(&m_cond);
}
bool wait(pthread_mutex_t * mutex){
return pthread_cond_wait(&m_cond, mutex) == 0;
}
bool timedwait(pthread_mutex_t * mutex, struct timespec t){
return pthread_cond_timedwait(&m_cond, mutex, &t) == 0;
}
bool signal(pthread_mutex_t * mutex){
return pthread_cond_signal(&m_cond) == 0;
}
bool broadcast(pthread_mutex_t * mutex){
return pthread_cond_broadcast(&m_cond) == 0;
}
};
class sem{
private:
sem_t m_sem;
public:
sem(){
if( sem_init(&m_sem, 0, 0) != 0){
throw exception();
}
}
sem(int num){
if( sem_init(&m_sem, 0, num) != 0){
throw exception();
}
}
~sem(){
sem_destroy(&m_sem);
}
bool wait(){
return sem_wait(&m_sem) == 0;
}
bool post(){
return sem_post(&m_sem) == 0;
}
};
#endif