C++ socket实现多请求响应服务器(二)

这里把昨天实现的线程池加入了进来。提高了处理并发请求的能力。

感觉还是加深了一点对多线程编程的理解。

线程池实现可以看这里;

请求处理程序(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,之前,没有启用多线程之前,关闭套接字连接都是在主线程中进行。之后引入了多线程机制,主线程进度会先于收发线程,使得在发送请求之前套接字就被关闭了。因此改正方法就是把关闭套接字放在收发线程中,以免顺序错乱。

猜你喜欢

转载自blog.csdn.net/Monster_ixx/article/details/88542820