前面我实现了一个完整的动态线程池,接下来我要使用该线程池来实现服务器。下面介绍一下本服务器的基本特点。
前文
c++实现一个高并发服务器(二)线程池
c++实现一个高并发服务器(一)任务队列
成员 | 简介 |
---|---|
Server() | 创建线程池 |
~Server() | 释放线程池 |
run() | 进行端口和工作目录的设定,调用epollrun() |
initListenFd() | 创建socket,绑定地址端口,监听等一些列操作 |
epollRun(int lfd) | 创建epoll,将服务器fd加入监听,循环监听 |
ThreadPool *m_pool | 线程池指针 |
成员 | 简介 |
---|---|
int fd | 文件描述符 |
int epfd | epoll描述符 |
pthread_t tid | 对应的线程号 |
函数 | 简介 |
---|---|
static void* acceptClient(void * arg); | 当epoll中监听服务器fd可读就代表有新的客户端连接,客户端连接处理函数,将新的客户端fd加入监听 |
static void* recvHttpRequest(void* arg); | 参数强制转换为FdInfo,从此fd中读取http请求,读取完毕后调用解析函数,即下面的函数 |
static int parseRequestLine(const char*line,int cfd); | 利用正则表达式将http请求中的method,path,protocol获取,将path转换为相对路径,发送http响应头和对应文件 |
static int sendHeadMsg(int cfd,int status,const char* descr,const char* type,int length); | 发送响应头,包括版本,状态码,状态码描述,content-type和content-length |
static int sendFile(const char* fileName,int cfd); | 将对应文件发送给对应客户端fd |
static int sendDir(const char* dirName,int cfd); | 使用一个文件名链表,获取当前目录所有文件名,根据每个文件的文件属性编写响应报文(检查是不是子目录),最后发送响应报文 |
static const char* getFileType(const char *name); | 解析文件名称,根据后缀名返回对应的html语句 |
static int hexToDec(char c); | 16进制的字符转10进制数字 |
static void decodeMsg(char * to,char * from); | 将16进制的http请求报文转换为10进制 |
Server.h
#pragma once
#include
#include"ThreadPool.h"
#include"TaskQueue.h"
struct FdInfo{
int fd;
int epfd;
pthread_t tid;
};
class Server{
public:
Server();
~Server();
int run(int argc, char* argv[]);
private:
int initListenFd(unsigned short port);
int epollRun(int fd);
private:
ThreadPool *m_pool;
};
Server.cpp
#include"Server.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static void* acceptClient(void * arg);
static void* recvHttpRequest(void* arg);
static int parseRequestLine(const char*line,int cfd);
static int sendHeadMsg(int cfd,int status,const char* descr,const char* type,int length);
static int sendFile(const char* fileName,int cfd);
static int sendDir(const char* dirName,int cfd);
static const char* getFileType(const char *name);
static int hexToDec(char c);
static void decodeMsg(char * to,char * from);
Server::Server(){
m_pool=new ThreadPool(8,8);
}
Server::~Server(){
if(m_pool!=nullptr){
delete m_pool;
m_pool=nullptr;
}
}
int Server::run(int argc,char* argv[]){
if(argc<3){
printf("Please input : ./xxx port path\n");
return -1;
}
unsigned short port=atoi(argv[1]);
chdir(argv[2]);
printf("Init listen...\n");
int lfd=initListenFd(port);
if(lfd==-1){
return -1;
}
printf("epoll run ...\n");
epollRun(lfd);
return 0;
}
int Server::initListenFd(unsigned short port){
//1.create socket
int lfd=socket(AF_INET, SOCK_STREAM, 0);
if(lfd==-1){
perror("socket");
return -1;
}
//2.set addr reuse
int opt=1;
int ret=setsockopt(lfd, SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
if(ret==-1){
perror("setsockopt");
return -1;
}
//3.bind the addr
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr =INADDR_ANY;
addr.sin_port = htons(port);
ret=bind(lfd,(struct sockaddr *)&addr,sizeof(addr));
if(ret==-1){
perror("bind");
return -1;
}
//4.set listen
ret=listen(lfd,128);
if(ret==-1){
perror("listen");
return -1;
}
return lfd;
}
int Server::epollRun(int lfd){
//1.create a epoll instance
int epfd=epoll_create(1);
if(epfd==-1){
perror("eppoll_create");
return -1;
}
//2.register lfd in epoll
struct epoll_event ev;
ev.data.fd=lfd;
ev.events=EPOLLIN;
int ret=epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);
if(ret==-1){
perror("epoll_create");
return -1;
}
//loop to check
struct epoll_event evs[1024];
int size=sizeof(evs)/sizeof(struct epoll_event);
while(1){
int num=epoll_wait(epfd,evs,size,-1);
for(int i=0;i<num;++i){
int fd=evs[i].data.fd;
struct FdInfo* info=new struct FdInfo;
info->fd=fd;
info->epfd=epfd;
if(fd==lfd){
acceptClient(info);
printf("acceptClient...\n");
usleep(1);
}else{
printf("recvHttpRequest...\n");
m_pool->addTask(Task(recvHttpRequest,info));
usleep(1);
}
}
}
return 0;
}
static void * acceptClient(void* arg){
printf("acceptClient threadId:%ld\n",pthread_self());
struct FdInfo* info=(struct FdInfo*)arg;
//1.establish connection
int cfd=accept(info->fd,NULL,NULL);
if(cfd==-1){
perror("accept");
return NULL;
}
//set cfd is noblock
int flag=fcntl(cfd,F_GETFL);
flag |=O_NONBLOCK;
fcntl(cfd,F_SETFL,flag);
//add cfd to epoll
struct epoll_event ev;
ev.data.fd=cfd;
ev.events=EPOLLIN|EPOLLET;
int ret=epoll_ctl(info->epfd,EPOLL_CTL_ADD,cfd,&ev);
if(ret==-1){
perror("epoll_ctl");
return NULL;
}
free(info);
return NULL;
}
static void* recvHttpRequest(void *arg){
printf("recvHttpRequest threadId:%ld\n",pthread_self());
struct FdInfo* info=(struct FdInfo*)arg;
int len=0,total=0;
char tmp[1024]={0};
char buf[4096]={0};
while((len=recv(info->fd,tmp,sizeof tmp,0))>0){
if(total+len<sizeof buf){
memcpy(buf+total,tmp,len);
}
total=total+len;
}
if(len==-1&&errno==EAGAIN){
parseRequestLine(buf,info->fd);
}else if(len==0){
epoll_ctl(info->epfd,EPOLL_CTL_DEL,info->fd,NULL);
close(info->fd);
}else{
perror("recv");
}
free(info);
return NULL;
}
static int parseRequestLine(const char *line ,int cfd){
printf("pareRequestLine...\n");
char method[12]="";
char path[1024]="";
char protocol[256]="";
sscanf(line,"%[^ ] %[^ ] %[^ \r\n]",method,path,protocol);
printf("before decode: [%s] [%s] [%s]\n",method,path,protocol);
decodeMsg(path,path);
printf("after decode: [%s] [%s] [%s]\n",method,path ,protocol);
if(strcasecmp(method,"get")!=0){
printf("not get\n");
return -1;
}
char *file=NULL;
//deal the static file which client asked
if(strcmp(path,"/")==0){
file="./";
}else{
file=path +1;
}
//get file property
struct stat st;
int ret=stat(file,&st);
if(ret==-1){
//file not exist
sendHeadMsg(cfd,404,"Not Found",getFileType(".html"),-1);
sendFile("404.html",cfd);
return 0;
}
if(S_ISDIR(st.st_mode)){
sendHeadMsg(cfd,200,"OK",getFileType(".html"),-1);
sendDir(file,cfd);
}else{
sendHeadMsg(cfd,200,"OK",getFileType(file),st.st_size);
sendFile(file,cfd);
}
return 0;
}
static int sendHeadMsg(int cfd,int status,const char* descr,const char* type,int length){
//response line
char buf[4096]={0};
sprintf(buf,"HTTP/1.1 %d %s\r\n",status,descr);
//response head
sprintf(buf+strlen(buf),"content-type:%s\r\n",type);
sprintf(buf+strlen(buf),"content-length:%d\r\n\r\n",length);
send(cfd,buf,strlen(buf),0);
return 0;
}
static int sendFile(const char* fileName,int cfd){
printf("send file: %s\n",fileName);
int fd=open(fileName,O_RDONLY);
assert(fd>0);
int size=lseek(fd,0,SEEK_END);
lseek(fd,0,SEEK_SET);
off_t offset=0;
while(offset<size){
//sendfile function has limitation of sending
int ret=sendfile(cfd,fd,&offset,size-offset);
usleep(1);
printf("ret=%d\n",ret);
if(ret==-1){
if(errno==EAGAIN){
printf("no data.../");
}
perror("sendfile");
}
}
close(fd);
return 0;
}
static int sendDir(const char* dirName,int cfd){
printf("send Dir:%s\n",dirName);
char buf[4096]={0};
sprintf(buf,"%s ",dirName);
struct dirent** namelist;//point to a pointer array
//struct dirent* tmp[]
int num=scandir(dirName,&namelist,NULL,alphasort);
for(int i=0;i<num;i++){
char *name=namelist[i]->d_name;
struct stat st;
char subPath[1024]={0};
sprintf(subPath,"%s/%s",dirName,name);
stat(subPath,&st);
if(S_ISDIR(st.st_mode)){
sprintf(buf+strlen(buf),
"%s %ld ",
name,name,st.st_size);
}else{
sprintf(buf+strlen(buf),
"%s %ld ",
name,name,st.st_size);
}
send(cfd,buf,strlen(buf),0);
memset(buf,0,sizeof(buf));
free(namelist[i]);
}
sprintf(buf,"
");
send(cfd,buf,strlen(buf),0);
free(namelist);
return 0;
}
static const char* getFileType(const char* name){
const char *dot=strrchr(name,'.');
//judge file type by file name
if(dot==NULL)
return "text/plain;charset=utf-8";
if(strcmp(dot,".html")==0||strcmp(dot,"htm")==0)
return "text/html;charset=utf-8";
if(strcmp(dot,".jpg")==0||strcmp(dot,".jpeg")==0)
return "image/jpeg";
if(strcmp(dot,".gif")==0)
return "image/gif";
if(strcmp(dot,".png")==0)
return "image/png";
if(strcmp(dot,".css")==0)
return "text/css";
if(strcmp(dot,"au")==0)
return "audio/.au";
if(strcmp(dot,".wav")==0)
return "audio/.wav";
if(strcmp(dot,".avi")==0)
return "video/x-msvideo";
if(strcmp(dot,".mov")==0||strcmp(dot,".qt")==0)
return "video/quicktime";
if(strcmp(dot,".mpeg")==0||strcmp(dot,".mpe")==0)
return "video/mepeg";
if(strcmp(dot,".mp3")==0)
return "audio/mpeg";
if(strcmp(dot,".vrml")==0)
return "model/vrml";
if(strcmp(dot,".ogg")==0)
return "application/ogg";
if(strcmp(dot,".pac")==0)
return "application/x-nx-proxy-autoconfig";
return"text/plain;charset=utf-8";
}
static int hexToDec(char c){
if(c>='0'&&c<='9')
return c-'0';
if(c>='a'&&c<='f')
return c-'a'+10;
if(c>='A'&&c<='F')
return c-'a'+10;
return 0;
}
static void decodeMsg(char* to,char* from){
for(;*from!='\0';++to,++from){
//judge from %20 3 chars
if(from[0]=='%'&&isxdigit(from[1])&&isxdigit(from[2])){
//turn hex to dec
//3 chars become 1 char ,this original data
*to=hexToDec(from[1])*16+hexToDec(from[2]);
from+=2;
}else{
*to=*from;
}
}
*to='\0';
}
main.cpp
#include
#include"Server.h"
#include
#include
int main(int argc, char **argv){
Server server;
return server.run(argc,argv);
return 0;
}
到这里我们的高并发服务器基本上以及实现完毕了,后续还会做一些小的改进,或者添加一些新的功能。