这里把昨天实现的线程池加入了进来。提高了处理并发请求的能力。
感觉还是加深了一点对多线程编程的理解。
线程池实现可以看这里;
请求处理程序(Deal_req.cpp):
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<unistd.h>
#include<sys/socket.h>
#include<sys/stat.h>
#include<errno.h>
#include"Deal_req.h"
#include"File_ope.h"
using namespace std;
int http_req::req_init(int n_sock,char *n_req){
sock=n_sock;
int len=strlen(n_req);
req=(char *)calloc(len,sizeof(char));
strcpy(req,n_req);
return 0;
}
int http_req::req_break(){
char *tmp=req;
if(tmp==NULL)
return -1;
req_line=tmp;
tmp=strchr(tmp,'\r');
if(tmp==NULL)
return -1;
*tmp='\0';
tmp++;
*tmp='\0';
tmp++;
req_head=tmp;
int cnt=0;
for(;cnt!=2&&*tmp!='\0';){
if(*tmp=='\r'){
cnt++;
}
else if(*tmp!='\n')
cnt=0;
tmp++;
}
*(tmp-1)='\0';
*tmp='\0';
tmp++;
req_body=tmp;
return 0;
}
int http_req::reqline_analyse(){
if(req_line==NULL)
return -1;
char *tmp=req_line;
method=tmp;
tmp=strchr(tmp,' ');
if(tmp==NULL)
return -1;
*tmp='\0';
tmp++;
url=tmp;
tmp=strchr(tmp,' ');
if(tmp==NULL)
return -1;
*tmp='\0';
tmp++;
version=tmp;
return 0;
}
int http_req::deal_get(){
char *p=strchr(url,'?');
if(p!=NULL){
argv=p+1; //获取get参数
*p='\0';
}
strcpy(file_path,url);
if(file_path[0]=='\0')
return -1;
char *tmp=strrchr(file_path,'.');
memset(file_type,0,sizeof(file_type));
if(tmp==NULL)
strcpy(file_type,"text/plain");
else
tmp++;
if(!strcmp(tmp,"html")||!strcmp(tmp,"htm"))
strcpy(file_type,"text/html");
else if(!strcmp(tmp,"css"))
strcpy(file_type,"text/css");
else if(!strcmp(tmp,"gif"))
strcpy(file_type,"image/gif");
else if(!strcmp(tmp,"jpeg")||!strcmp(tmp,"jpg"))
strcpy(file_type,"image/jpeg");
else if(!strcmp(tmp,"png"))
strcpy(file_type,"image/png");
else
strcpy(file_type,"text/plain");
return 0;
}
int http_req::deal_post(){
strcpy(file_path,url);
if(file_path[0]=='\0')
return -1;
char *tmp=strrchr(file_path,'.');
if(tmp==NULL)
strcpy(file_type,"text/plain");
else
tmp++;
memset(file_type,0,sizeof(file_type));
if(!strcmp(tmp,"html")||!strcmp(tmp,"htm"))
strcpy(file_type,"text/html");
else if(!strcmp(tmp,"css"))
strcpy(file_type,"text/css");
else if(!strcmp(tmp,"gif"))
strcpy(file_type,"image/gif");
else if(!strcmp(tmp,"jpeg")||!strcmp(tmp,"jpg"))
strcpy(file_type,"image/jpeg");
else if(!strcmp(tmp,"png"))
strcpy(file_type,"image/png");
else
strcpy(file_type,"text/plain");
return 0;
}
int http_req::http_404(struct stat buf){
char *Error;
Error=(char *)calloc(BUFFSIZE,sizeof(char));
sprintf(Error,"HTTP/1.1 404 NOT_FOUND\r\n\
Connection: close\r\ncontent-length:%lld\r\n\r\n",buf.st_size);
int res=send(sock,Error,strlen(Error),0);
free(Error);
return res;
}
int http_req::http_403(struct stat buf){
char *Error;
Error=(char *)calloc(BUFFSIZE,sizeof(char));
sprintf(Error,"HTTP/1.1 403 FORBIDDEN\r\n\
Connection: close\r\ncontent-length:%lld\r\n\r\n",buf.st_size);
int res=send(sock,Error,strlen(Error),0);
free(Error);
return res;
}
void* deal_req(void* arg){
http_req *Got_req=(http_req *)arg;
// printf("DEBUG___________socket2: %d\n",Got_req->sock);
Got_req->req_break();
Got_req->reqline_analyse();
if(!strcmp(Got_req->method,"POST")){
Got_req->deal_post();
}
else if(!strcmp(Got_req->method,"GET")){
Got_req->deal_get();
}
struct stat buf;
int ret=stat(Got_req->file_path,&buf);
if(ret==-1){
if(errno==ENOENT) //文件不存在
Got_req->http_404(buf);
else if(errno==EACCES) //访问受限,linux中为EACCESS,unix中为EACCES
Got_req->http_403(buf);
}
else{
FILE *fd=fopen(Got_req->file_path,"rb");
int file_size=get_file_size(Got_req->file_path);
//int opened_file=open(fp,O_RDONLY);
char *memfile=(char *)calloc(file_size,sizeof(char));
fread(memfile,file_size,1,fd);
//close(opened_file); //关闭文件
printf("The file size is:%d\r\n",file_size);
printf("The context is: \r\n");
write(1,memfile,file_size);
printf("\r\n");
char buff[1024],FlTp[256],ConLen[256];
memset(buff,0,sizeof(buff));
memset(ConLen,0,sizeof(ConLen));
memset(FlTp,0,sizeof(FlTp));
strcat(buff,"\r\nHTTP/1.0 200 ok\r\n"); //响应报文的格式
sprintf(FlTp,"Content-Type: %s;charset=UTF-8\r\n",Got_req->file_type);
strcat(buff,FlTp);
// strcat(buff,"Content-Type: text/html;charset=UTF-8\r\n");
sprintf(ConLen,"Content-Length: %d\r\n\r\n",file_size);
strcat(buff,ConLen);
send(Got_req->sock,buff,strlen(buff),0);
send(Got_req->sock,memfile,file_size,0);
printf("Send successfully\r\n\r\n");
close(Got_req->sock);
free(memfile);
}
return NULL;
}
主程序(WebServer.cpp):
#include<cstdio>
#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<unistd.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/stat.h>
#include<sys/mman.h>
#include<sys/event.h>
#include<sys/time.h>
#include<fcntl.h>
#include"WebServer.h"
#include"Deal_req.h"
#include"File_ope.h"
#include"Thread_pool.h"
using namespace std;
int main(int argc,char *argv[]){
struct sockaddr_in addr,naddr; //在头文件<netinet/in.h>中
socklen_t len;
struct kevent *chlist; //监听事件
struct kevent *evlist; //触发事件
struct http_req *reqs; //用来处理请求
threadpool pool; //线程池
int kq; //kqueue队列
int sockfd=socket(PF_INET,SOCK_STREAM,0);//建立套接字
if(sockfd==-1){
perror("Socket");
exit(1);
}
addr.sin_family=AF_INET;
addr.sin_port=htons(PORT); //转化为网络字节序
addr.sin_addr.s_addr=INADDR_ANY; //初始化为我的IP
bzero(&(addr.sin_zero),sizeof(addr.sin_zero)); //多余的字节初始为0
//将本地端口和套接字绑定
if(bind(sockfd,(const struct sockaddr*)&addr,sizeof(addr))==-1){
perror("bind");
exit(1);
}
if(listen(sockfd,BACKLOG)==-1){ //第二个参数是等待队列的长度
perror("listen");
exit(1);
}
printf("listening...\n");
kq=kqueue();
if(kq==-1){
perror("kqueue");
exit(1);
}
//初始化kevent结构体
chlist=(struct kevent*)malloc(sizeof(struct kevent));
evlist=(struct kevent*)malloc(sizeof(struct kevent)*MAXEVENT);
//初始化请求结构体
reqs=(struct http_req*)malloc(sizeof(struct http_req)*MAXEVENT);
//初始化线程池线程数为最大能处理请求
threadpool_init(&pool, MAXEVENT);
EV_SET(chlist,sockfd,EVFILT_READ,EV_ADD|EV_ENABLE,0,0,0); //注册事件
while(true){
char *buff=(char *)calloc(BUFFSIZE,sizeof(char)); //初始化buff
int nev=kevent(kq,chlist,1,evlist,MAXEVENT,NULL); //无限阻塞
if(nev<0){
perror("kevent");
}
else if(nev>MAXEVENT){
printf("Too many requests\n");
exit(1);
}
else{
printf("The num of req is:%d\n",nev);
if(evlist[0].flags&EV_EOF){ //读取socket关闭指示
exit(EXIT_FAILURE);
}
for(int i=0;i<nev;i++){
printf("------------------------------------------------\n");
if(evlist[i].flags&EV_ERROR){
printf("EV_ERROR:%s\n",strerror(evlist[i].data));
exit(EXIT_FAILURE);
}
if(evlist[i].ident==sockfd){
int nsockfd=accept(sockfd,(struct sockaddr*)&naddr,&len); //呼叫地址
if(nsockfd==-1){
perror("accept");
exit(1);
}
printf("client connected\n\n");
recv(nsockfd,buff,BUFFSIZE,0); //recv将接收到的数据放到buff中
// send(nsockfd,buff,BUFFSIZE,0); //向客户端发送buff中的内容
printf("Recive message from client: \n%s\n",buff);
if(buff!=NULL){
printf("--------Got request.-------\n");
reqs[i].req_init(nsockfd,buff);
http_req *arg=(http_req *)malloc(sizeof(http_req));
*arg=reqs[i];
// printf("DEBUG________________SOCKET1:%d\n",nsockfd);
//deal_req(arg);
threadpool_add_task(&pool,deal_req,arg); //加入线程池
}
}
printf("------------------------------------------------\n");
}
// sleep(100);
}
free(buff);
}
threadpool_destroy(&pool); //销毁线程池
free(chlist); //内存释放
free(evlist);
close(kq);
close(sockfd); //close用以关闭一般的文件描述符
return 0;
}
其他缺少的头文件都可以在之前的博客中找到。
中间还是出现了一点小BUG,之前,没有启用多线程之前,关闭套接字连接都是在主线程中进行。之后引入了多线程机制,主线程进度会先于收发线程,使得在发送请求之前套接字就被关闭了。因此改正方法就是把关闭套接字放在收发线程中,以免顺序错乱。