標籤: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 即可,謝謝大家的幫忙