使用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