主页面
点击其中一个目录项
根据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请求行