-
项目名称:httpserver服务器
- 项目结构
- 相关知识说明
- 启动程序开发
- 核心程序开发
上篇文章我们写到了启动程序,和核心程序的主逻辑部分,接下来就来将主逻辑的代码展开。
- createfd 创建监听socket
int createlfd(int port){
int lfd = socket(AF_INET,SOCK_STREAM,0);
/*
设置端口复用:原因是在socket的在四次挥手的时候
Fin
Fin_wait_1 client -----> server close_wait
ack
Fin_wait_2 client <------ server close_wait
Fin
Time_wait client ------> server last_ack
acl
client <------- server
其中 在Fin_wait -- > Time_wait 中间会有一段缓冲时间,保证服务器端发送数据成功。
*/
int optval = 1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval));
/*
sockaddr_in
*/
struct sockaddr_in serveraddr;
serveraddr.sin_family=AF_INET;
serveraddr.sin_addr.s_addr=inet_addr("0.0.0.0");
serveraddr.sin_port=htons(port);
bind(lfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
//监听服务
listen(lfd,20);
return lfd;
}
创建epoll树,监听文件描述符的读取缓冲区
//添加监听socket到时间树上,返回二叉树efd
int addfdtoevets(int efd,int lfd){
/*
epool 的平滑模式:当有数据到达的时候触发一次,然后读取数据,如果没有读取完缓冲区的数据,则在触发,直到读取完成为止
epool 边缘阻塞模式: 当有数据到达时,就开始读取,直到读完为止,只触发一次
*/
ev.events=EPOLLIN | EPOLLET;
ev.data.fd=lfd;
epoll_ctl(efd,EPOLL_CTL_ADD,lfd,&ev);
return efd;
}
- 解析http协议,并进行响应
void parsecontent(int confd,int efd){
char buffer[1024]={0};
FILE * handler = fdopen(confd,"r");
//将socket 转换为文件字符,用fgets读取一行
char *header = fgets(buffer,1024,handler);
if (!header)
{
/* code */
return;
}
//解析头部
/*
请求方法,请求资源, 协议版本,
*/
char* delim = " ";
char* p;
printf("method:%s\n", strtok(header, delim));
//printf("method:%s\n", strtok(s, delim));
char* uri = strtok(NULL, delim);
decode_str(uri, uri);
char* file = uri+1;
printf("uri :%s\n",uri);
char* protocal = strtok(NULL, delim);
printf("protocal :%s\n", protocal);
//解析请求行
int flag =0;
if(strcmp(uri, "/") == 0)
{
// file的值, 资源目录的当前位置
file = "./";
}
if (!strcmp(uri,"/favicon.ico"))
{
//如果两个值相等,则返回,不响应此请求;
return;
}
//判断是文件还是目录
struct stat s_buf;
stat(file,&s_buf);
if(S_ISDIR(s_buf.st_mode)){
printf("it is hrere..............\n");
char * retval = send_dir(confd,file,flag);
//发送响应头和响应行
send_response_header("text/html",countlengh(retval),confd);
发送数据
write(confd,retval,countlengh(retval));
}
else{
int fp = open(file,O_RDWR);
int size = get_file_size(fp);
char tmp[1024] ={0};
strcpy(tmp,file);
//判断文件类型
char * filetype = orgnizefiletype(tmp);
printf("The Last FileType is %s\n",filetype);
//发送响应头和响应行
send_response_header(filetype,size,confd);
//发送文件信息
send_file(confd,file);
}
}
- 如果是目录
char * send_dir(int confd,char * Path, int flag){
printf("senddir path %s\n",Path);
char buffer[8096] ="";
char tmp[1024]={0};
struct dirent *direntp;
char filepath[1024]={0};
DIR* sdir = opendir(Path);
//如果返回NULL,表示打开目录失败
if (!sdir)
{
//这里表示是文件
//发送文件到客户端
perror("can't open the file!\n");
return NULL ;
}
sprintf(buffer+strlen(buffer),"%s","
");while( (direntp = readdir(sdir)) != NULL )//读取当前目录下的文件和目录信息
{
if( strcmp(direntp->d_name,".")==0||strcmp(direntp->d_name,"..")==0)
continue;
char * name = direntp->d_name;
char enstr[1024]={0};
encode_str(enstr, sizeof(enstr), name);
if (direntp->d_type & DT_DIR)
{
/* code */
sprintf(buffer+strlen(buffer),"
",name,name);
}
else{
sprintf(buffer+strlen(buffer),"
",name,name);}
}
//组装目录信息和目录下的文件为html代码并返回
sprintf(buffer+strlen(buffer),"%s","
%s |
%s |
sprintf(buffer+strlen(buffer),"%s","");
char * ret = buffer;
closedir(sdir);
return ret;
}
- 如果是文件
void send_file(int confd,const char * path){
printf("the file path is %s\n",path);
//如果是文件,就开发文件并发送文件到socket缓冲区
//这里的文件包括文本类型,图片
int fd = open(path,O_RDONLY);
if (fd==-1)
{
/* code */
perror("Not Found The File,Please Check Path!");
return ;
}
char buffer[4096]={0};
int len=0;
while ((len=read(fd,buffer,sizeof(buffer)))>0)
{
write(confd,buffer,len);
}
if (len==-1)
{
perror("read file error!");
exit(1);
}
close(fd);
}
通过以上代码,我们可以总结一下逻辑:
- 本地起socket服务
- 接受连接请求
- 解析http协议,解析出请求的资源
- 判断资源是何种类型,如果是目录,则遍历当前目录,并返回
- 如果是文件,则进行读取文件内容,发送给client端
其核心逻辑还是比较简单的,主要是要了解httpserver的通信原理,搭配着epoll可以写出一个支持多客户端的httpserver。
如果要查看完整代码,请点击右侧连接:https://github.com/kaiven11/cproject ,里面有文章的代码。