基於epoll的簡單的http伺服器

來源:互聯網
上載者:User

標籤:http伺服器   web server   伺服器   c++   

本人用epoll寫了一個簡單的http伺服器,該伺服器在用戶端第一次發送資料時可以正確處理,但是當用戶端不關閉繼續發送資料時,伺服器無法讀取,請求大家幫忙看看哪裡有問題,謝謝

</pre></p><p>server.h</p><p><pre name="code" class="cpp">/* * server.h * *  Created on: Jun 23, 2014 *      Author: fangjian */#include <netinet/in.h>#ifndef SERVER_H_#define SERVER_H_#define QUERY_INIT_LEN  10#define REMAIN_BUFFER 5/* 以下是處理機的狀態 */#define ACCEPT 1#define READ 2#define QUERY_LINE 4#define QUERY_HEAD 8#define QUERY_BODY 16#define SEND_DATA 32struct connection{int fd;struct sockaddr_in client_address;int state;//當前處理到哪個階段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);struct connection* initConnection(int fd);void state_machine(struct connection& conn);void web_accept(struct connection& conn);void read_request(struct connection& conn);void process_request_line(struct connection& conn);void process_head(struct connection& conn);void process_body(struct connection& conn);void send_response(struct connection& conn);void try_to_enlarge_buffer(struct connection& conn);void close_connection(int fd);#endif /* SERVER_H_ */


server.cpp:

/* * server.cpp * *  Created on: Jun 23, 2014 *      Author: fangjian */#include "server.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>#include <map>using namespace std;#define MAX_EVENT_NUMBER 10000map<int,connection> map_conn;struct server server;int main(int argc,char* argv[]){const char* ip = "172.16.55.67";int port =  8083;signal(SIGPIPE,SIG_IGN);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);epoll_event events[MAX_EVENT_NUMBER];server.epollfd = epoll_create(1024);web_epoll_ctl(server.epollfd,EPOLL_CTL_ADD,listenfd,EPOLLIN);//添加讀串連事件setnonblocking(listenfd);//fork();while(true){int number = epoll_wait(server.epollfd,events,MAX_EVENT_NUMBER,-1);printf("number=%d\n",number);printf("當前進程ID為: %d \n",getpid());int i;for(i = 0;i < number;i++){int socket = events[i].data.fd;//當前觸發的fd//有新串連到達if(socket == listenfd){printf("有新串連到達\n");//建立一個串連結構體struct connection* conn = initConnection(socket);//進入狀態機器處理請求state_machine(*conn);}//讀事件到達else if(events[i].events & EPOLLIN){printf("--------開始處理請求行------------\n");state_machine(map_conn[socket]);printf("--------處理請求行結束--------------\n");//sleep(2);web_epoll_ctl(server.epollfd,EPOLL_CTL_MOD,socket,EPOLLIN);}//有寫事件到達else if(events[i].events & EPOLLOUT){printf("--------開始發送資料--------------\n");state_machine(map_conn[socket]);printf("---------發送資料結束--------------\n");}//異常else{}}}}void state_machine(struct connection& conn){switch (conn.state){case ACCEPT:{web_accept(conn);break;}case READ:{read_request(conn);break;}case QUERY_LINE:{process_request_line(conn);break;}case SEND_DATA:{send_response(conn);break;}}}/* 調用epoll_ctl處理 */void web_epoll_ctl(int epollfd,int ctl,int fd,int flag){epoll_event event;event.data.fd =fd;event.events = flag;epoll_ctl(epollfd,ctl,fd,&event);}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;}struct connection* initConnection(int fd){struct connection* conn  = (struct connection*)malloc(sizeof(struct connection));conn->fd = fd;conn->state = ACCEPT;conn->querybuf = (char*)malloc(QUERY_INIT_LEN);if(!conn->querybuf){printf(" malloc error\n");return NULL;}conn->query_start_index = 0;conn->query_end_index = 0;conn->query_remain_len = QUERY_INIT_LEN;return conn;}void web_accept(struct connection& conn){socklen_t client_addrlength = sizeof(conn.client_address);int connfd = accept(conn.fd,(struct sockaddr*)&(conn.client_address),&client_addrlength);if(connfd == -1){printf("accept error\n");return;}web_epoll_ctl(server.epollfd,EPOLL_CTL_DEL,conn.fd,EPOLLIN);//刪除監聽事件close(conn.fd);//關閉監聽描述符,因為keep_alive是保持串連描述符不關閉conn.fd = connfd;conn.state = READ;map_conn[connfd] = conn;web_epoll_ctl(server.epollfd,EPOLL_CTL_ADD,connfd,EPOLLIN);setnonblocking(connfd);}void read_request(struct connection& conn){int len,fd = conn.fd;while(true){/* 嘗試增加緩衝區空間 */try_to_enlarge_buffer(conn);len= read(fd,conn.querybuf+conn.query_end_index,conn.query_remain_len);if(len == -1){printf("----資料讀完-----\n");conn.state = QUERY_LINE;//進入解析階段web_epoll_ctl(server.epollfd,EPOLL_CTL_DEL,conn.fd,EPOLLIN);//刪除該串連上的讀事件break;}else if(len == 0){printf("----用戶端關閉串連------\n");conn.state = QUERY_LINE;//進入解析階段web_epoll_ctl(server.epollfd,EPOLL_CTL_DEL,conn.fd,EPOLLIN);//刪除該串連上的讀事件break;}else if(len > 0){conn.query_end_index += len;conn.query_remain_len -= len;}}cout << "-----用戶端的內容是 " << endl;cout << conn.querybuf << endl;process_request_line(conn);}void process_request_line(struct connection& 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,再是換行\nif(!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 connection& 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 connection& conn){if(conn.query_start_index == conn.query_end_index){printf("---包體為空白----\n");}else{printf("---丟體包體-----\n");}conn.query_start_index = conn.query_end_index = 0;send_response(conn);}void send_response(struct connection& 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?//web_epoll_ctl(server.epollfd,EPOLL_CTL_MOD,conn.fd,EPOLLIN);conn.state = READ;}void close_connection(int fd){map_conn.erase(fd);close(fd);}void try_to_enlarge_buffer(struct connection& 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;}}

用戶端代碼:

#include <stdlib.h>#include <stdio.h>#include <assert.h>#include <unistd.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 <string.h>static const char* request = "GET /index.html HTTP/1.1\r\nConnection: keep-alive\r\n\r\nxxxxxxxxxxxx";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 epoll_fd, int fd ){    epoll_event event;    event.data.fd = fd;    event.events = EPOLLOUT | EPOLLET | EPOLLERR;    epoll_ctl( epoll_fd, EPOLL_CTL_ADD, fd, &event );    setnonblocking( fd );}bool write_nbytes( int sockfd, const char* buffer, int len ){    int bytes_write = 0;    printf( "write out %d bytes to socket %d\n", len, sockfd );    while( 1 )     {           bytes_write = send( sockfd, buffer, len, 0 );        if ( bytes_write == -1 )        {               return false;        }           else if ( bytes_write == 0 )         {               return false;        }           len -= bytes_write;        buffer = buffer + bytes_write;        if ( len <= 0 )         {               return true;        }       }   }bool read_once( int sockfd, char* buffer, int len ){    int bytes_read = 0;    memset( buffer, '\0', len );    bytes_read = recv( sockfd, buffer, len, 0 );    if ( bytes_read == -1 )    {        return false;    }    else if ( bytes_read == 0 )    {        return false;    }printf( "read in %d bytes from socket %d with content: %s\n", bytes_read, sockfd, buffer );    return true;}void start_conn( int epoll_fd, int num, const char* ip, int port ){    int ret = 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 );    for ( int i = 0; i < num; ++i )    {        sleep( 1 );        int sockfd = socket( PF_INET, SOCK_STREAM, 0 );        printf( "create 1 sock\n" );        if( sockfd < 0 )        {            continue;        }        if (  connect( sockfd, ( struct sockaddr* )&address, sizeof( address ) ) == 0  )        {            printf( "build connection %d\n", i );            addfd( epoll_fd, sockfd );        }    }}void close_conn( int epoll_fd, int sockfd ){    epoll_ctl( epoll_fd, EPOLL_CTL_DEL, sockfd, 0 );    close( sockfd );}int main( int argc, char* argv[] ){    assert( argc == 4 );    int epoll_fd = epoll_create( 100 );    start_conn( epoll_fd, atoi( argv[ 3 ] ), argv[1], atoi( argv[2] ) );    epoll_event events[ 10000 ];    char buffer[ 2048 ];    while ( 1 )    {        int fds = epoll_wait( epoll_fd, events, 10000, 2000 );        for ( int i = 0; i < fds; i++ )        {               int sockfd = events[i].data.fd;            if ( events[i].events & EPOLLIN )            { printf("----伺服器發來資料-----\n");                if ( ! read_once( sockfd, buffer, 2048 ) )                {                    close_conn( epoll_fd, sockfd );                }                struct epoll_event event;                event.events = EPOLLOUT | EPOLLET | EPOLLERR;                event.data.fd = sockfd;                epoll_ctl( epoll_fd, EPOLL_CTL_MOD, sockfd, &event );            }            else if( events[i].events & EPOLLOUT )             {printf("---------向伺服器發送資料-----------\n");                if ( ! write_nbytes( sockfd, request, strlen( request ) ) )                {                    close_conn( epoll_fd, sockfd );                }printf("--------資料發送完畢-------------\n");                struct epoll_event event;                event.events = EPOLLIN | EPOLLET | EPOLLERR;                event.data.fd = sockfd;                epoll_ctl( epoll_fd, EPOLL_CTL_MOD, sockfd, &event );            }            else if( events[i].events & EPOLLERR )            {                close_conn( epoll_fd, sockfd );            }        }    }}

測試方法,在伺服器項目中建一個名字為"http"的檔案夾,裡面放一個index.html靜態檔案,如:

<!DOCTYPE html><html><head><title>Welcome to nginx!</title><style>    body {        width: 35em;        margin: 0 auto;        font-family: Tahoma, Verdana, Arial, sans-serif;    }</style></head><body><h1>Welcome to nginx!</h1><p>If you see this page, the nginx web server is successfully installed andworking. Further configuration is required.</p><p>For online documentation and support please refer to<a href="http://nginx.org/">nginx.org</a>.<br/>Commercial support is available at<a href="http://nginx.com/">nginx.com</a>.</p><p><em>Thank you for using nginx.</em></p></body></html>
然後將伺服器的IP改為自己的IP,即可運行伺服器;用戶端直接運行 ./main IP 8083 1 即可,謝謝大家的幫忙

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.