主頁面
點擊其中一個目錄項
根據libevent庫中sample照貓畫虎寫了以下代碼。
1、main()函數
//在程序運行前先指定共享的文件目錄 int main(int argc,char* argv[]) { struct evconnlistener *listener; if(argc<2) { printf("Please input path\n"); } int ret=chdir(argv[1]);//改變當前的工作目錄 if(ret==-1) { perror("chdir error\n"); exit(1); } struct event_base *base; struct sockaddr_in sin; base = event_base_new(); if(!base) { fprintf(stderr,"could not initalize libevent!\n"); return -1; } memset(&sin,0,sizeof(sin)); sin.sin_family=AF_INET; sin.sin_port=htons(PORT); //監聽器 listener = evconnlistener_new_bind(base,listener_cb,(void *)base,LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE,-1,(struct sockaddr*)&sin,sizeof(sin)); if(!listener) { fprintf(stderr,"could not create listener!\n"); return -1; } event_base_dispatch(base);//啟動監聽循環 evconnlistener_free(listener); event_base_free(base); }
2、監聽器函數實現
1 //當有新的客戶端請求連接會產生一個新的fd 2 static void listener_cb(struct evconnlistener *listener,evutil_socket_t fd,struct sockaddr *sa,int socklen,void *user_data) 3 { 4 struct event_base *base = user_data; 5 struct bufferevent *bev; 6 //創建bufferevent事件對象 7 bev = bufferevent_socket_new(base,fd,BEV_OPT_CLOSE_ON_FREE); 8 if(!bev) 9 { 10 fprintf(stderr,"can not create bev!\n"); 11 event_base_loopbreak(base); 12 } 13 //設置回調 14 bufferevent_setcb(bev,conn_readcb,NULL,conn_eventcb,NULL); 15 //事件對象中讀緩沖默認關閉,必須打開 16 bufferevent_enable(bev,EV_READ); 17 }
3、讀回調函數
1 //一旦有新的客戶端建立連接並發送數據就會觸發讀回調 2 void conn_readcb(struct bufferevent*bev,void *user_data) 3 { 4 char buf[BUFSIZ]; 5 bufferevent_read(bev,buf,BUFSIZ); //相當於read函數 6 char method[8]; 7 char filename[255]; 8 char protocal[16]; 9 get_info(buf,method,filename,protocal); //獲取method、filename、pro 10 if(strcasecmp(method,"get")==0) //如果是get方法 11 { 12 decode_str(filename,filename); //解碼操作 URL在存漢字時,會把漢字轉換成unicode碼,傳輸時要編碼,接受時要解碼 13 char *file = &filename[1]; //去掉“/” 14 //printf("file=%s\n",file); 15 //printf("filename=%s\n",filename); 16 if(strcmp(filename,"/")==0)//請求的是根目錄 17 { 18 file="./"; 19 } 20 http_request(bev,file); //處理http請求 21 } 22 23 }
我主要用指針來操作內存,把客戶端發送來的請求行中請求方法、請求文件、協議版本放在對應的數組中。雖然簡單但是容易訪問出現非法訪問的錯誤。
1 //獲取請求行中的方法、路徑、協議版本 get / http/1.1 2 void get_info(char *buf,char *method,char *filename,char*protocal) 3 { 4 char *begin,*p; 5 begin = buf; 6 p=strchr(begin,' '); 7 strncpy(method,begin,p-begin); 8 method[p-begin]='\0'; 9 p++; 10 begin=p; 11 p=strchr(begin,' '); 12 strncpy(filename,begin,p-begin); 13 filename[p-begin]='\0'; 14 p++; 15 begin = p; 16 p=strstr(begin,"\r\n"); 17 strncpy(protocal,begin,p-begin); 18 protocal[p-begin]='\0'; 19 20 // printf("method=%s\n",method); 21 //printf("filename=%s\n",filename); 22 //printf("protocal=%s\n",protocal); 23 }
4、處理http請求
1 //處理http請求 2 void http_request(struct bufferevent* bev,const char *file) 3 { 4 // printf("http_request~~~~~~~~file=%s\n",file); 5 struct stat sbuf; 6 int ret = stat(file,&sbuf); 7 if(ret!=0) 8 { 9 //回發錯誤頁面 10 perror("stat error\n"); 11 send_error(bev); 12 } 13 //請求的是文件 14 if(S_ISREG(sbuf.st_mode)) 15 { 16 //printf("請求的是文件\n"); 17 //向客戶端發送響應信息 18 // Http1.1 200 OK 19 // Content-Length: 32 要么不寫 或者 傳-1, 要寫務必精確 20 send_respond(bev,200,"ok",get_file_type(file),sbuf.st_size); 21 //發送正文 22 send_file(bev,file); 23 }else if(S_ISDIR(sbuf.st_mode))//請求的是目錄 24 { 25 send_respond(bev,200,"ok",get_file_type(".html"),-1); 26 //發送目錄 27 send_dir(bev,file); 28 } 29 30 }
服務器在發送客戶端所需的信息前先發送響應信息,內容如下(可以只挑重點寫),代碼簡單,不贅述
1 Http1.1 200 OK 2 Server: xhttpd 3 Content-Type:text/plain; charset=iso-8859-1 4 Date: Fri, 18 Jul 2014 14:34:26 GMT 5 Content-Length: xx 6 Content-Language: zh-CN 7 Last-Modified: Fri, 18 Jul 2014 08:36:36 GMT 8 Connection: close 9 \r\n
5、客戶端請求的是文件
1 //發送文件 2 void send_file(struct bufferevent*bev,const char*file) 3 { 4 //printf("send_file~~~~~~\n"); 5 char buf[BUFSIZ]; 6 int n; 7 int fd=open(file,O_RDONLY); 8 if(fd==-1) 9 { 10 perror("open error\n"); 11 send_error(bev); //發送錯誤頁面,用html標簽做了一個簡單頁面 12 exit(1); 13 } 14 //這里是read函數讀,不是bufferevent_read(),不小心寫錯了,調試了好久 15 while((n=read(fd,buf,BUFSIZ))>0) 16 { 17 bufferevent_write(bev,buf,n); 18 bzero(buf,n); //發送一部分給對端,記得清0 19 } 20 close(fd); 21 //printf("send file end~~~~~\n"); 22 23 }
6、客戶端請求的是目錄,比如在url中輸入IP:端口,訪問網站根目錄。這里要用html組織目錄項形成一個網頁。
1 void send_dir(struct bufferevent*bev,const char*dirname) 2 { 3 char buf[BUFSIZ]; 4 char path[256]; 5 char enstr[1024]; 6 int i; 7 struct dirent **namelist; 8 int num; 9 sprintf(buf,"<html><head><meta charset=\"utf-8\"><tile>%s</tile></head>",dirname); 10 sprintf(buf+strlen(buf),"<body><h1>當前目錄:%s</h1><table>",dirname); 11 //添加目錄內容 12 //遍歷目錄項 13 num=scandir(dirname,&namelist,NULL,alphasort); 14 for(i=0;i<num;i++) 15 { 16 char *name=namelist[i]->d_name; 17 //unicode編碼 18 encode_str(enstr,sizeof(enstr),name); 19 //拼接完整的文件名+路徑 20 sprintf(path,"%s/%s",dirname,enstr); 21 struct stat sbuf; 22 int ret = stat(path,&sbuf); 23 if(ret==-1) 24 { 25 //出錯處理 26 } 27 //如果是文件 28 if(S_ISREG(sbuf.st_mode)) 29 { 30 sprintf(buf+strlen(buf), 31 "<tr><td><a href=\"%s\">%s</a></td></tr>\n", 32 enstr, name); 33 } 34 else if(S_ISDIR(sbuf.st_mode)) 35 { 36 sprintf(buf+strlen(buf), 37 "<tr><td><a href=\"%s\">%s</a></td></tr>\n",enstr, name); 38 39 } 40 bufferevent_write(bev,buf,strlen(buf)); 41 bzero(buf,sizeof(buf)); 42 43 } 44 sprintf(buf,"</table></body></html>"); 45 bufferevent_write(bev, buf, strlen(buf)); 46 47 }
最后一步就是調試了,除了用打印輸出,還可以用telnet命令:step1輸入 telnet ip port 回車;step2 自己組織get請求行