使用Libevent庫實現一個簡單的共享文件服務器


 

 

                    主頁面

  點擊其中一個目錄項

 

  根據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請求行

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM