从上面的几个图片我们可以看出http是一个超级简单、跨平台、实现容易的模型,
主要的几个关键步骤在于:
1、初始化:建立epoll树、绑定服务器地址结构,把lfd挂上树,循环监听
主要函数:void epoll_run(int port) ;int init_lfd(int port,int efd)
2、建立连接:把满足读条件的事件挂上树、重复监听树上的事件,如果有读事件满足的时候,我们就进行读操作
主要函数:void do_accept(int fd,int efd);void do_read(int fd, int efd)
3、收到信息后进行拆分,因为http的发送过来的协议头包含客户端请求的文件、和请求的事件(GET)
主要函数:int get_line(int fd, const char *buf, sizeof(buf));http_send_request(int fd, const char* file);
4、最后一步就是根据http的协议头发送服务器需要的文件即可
主要的函数:void send_file(int fd, const char* file);
上代码!!!
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<errno.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include<sys/select.h>
#include<sys/time.h>
#include<poll.h>
#include<sys/epoll.h>
#include<fcntl.h>
#define SER_PORT 9527
#define MAX_SIZE 1288
void disconnect(int cfd, int efd); //关闭通道,从树上摘下
int get_line(int fd, const char *buf, sizeof(buf)); //读取第一行的信息
void send_respond(int cfd,int no, char * disp, char * type, int len); //利用sprintf的方法拼接出合适的buf,注意添加\r\n作为结束符号
void send_file(int fd, const char* file); //持续地发送文件
{
int n = 0, ret;
char buf[4096];
int fd = open(file, O_RDONLY);
while (n = read(fd, buf, siezeof(buf)) > 0) {
ret = send(cfd, buf, n, 0);
}
}
void http_send_request(int fd, const char* file) {
//判断文件后,回发信息
struct stat sbuf;
stat(file, &sbuf);
if (S_ISREG(sbuf.st_mode) {
send_respond(cfd,200,"OK","Content-Type:text/plain; charset=iso-8859-1",sbuf.st_size);
send_file(cfd, file);
})
}
void do_read(int fd, int efd) {
//根据fd进行文件的通信,读取一行http协议,拆分,获取 get 文件名 协议号
//http协议中的第9行是空行,是标志位
//具体的步骤为1、拆分文件头 2、判断文件是否存在、是否目录、如果是就发送目录项下面的文件名、如果是文件就发送文件内容 3、回发协议,写http头、内容等等
//http/1.1 200 ok Content-Type:text/plain; charset=iso-8859-1
char buf[1024], method[1024], prtocol[1024],path[1024];
int ret = get_line(fd, buf, sizeof(buf));
if(len==0){
printf("服务器,检查到客户端关闭....\n");
disconnect(cfd, epfd);
}else{
sscanf(buf, "%[^ ] %[^ ] %[^ ]", method, path, prtocol); //获取第一行的文件
}
if (strncasecmp(method, "GET", 3) == 0) { // 判断是否为GET方法
char* file = path + 1;
http_send_request(cfd, file);
disconnect(cfd, efd);
}
}
void do_accept(int fd,int efd){
struct sockaddr_in client; //每次添加新的epoll的事件时初始化客户端的地址结构
socklen_t len =sizeof(client);
int cfd =accept(fd,(struct sockaddr*)&client,&len);
char client_ip[64]={0};
printf("New ClientIP:%s,Port:%d,cfd=%d\n",
inet_ntop(AF_INET,&client.sin_addr.s_addr,client_ip,sizeof(client_ip)), //打印客户端信息
ntohs(client.sin_port), cfd);
int flags =fcntl(cfd,F_GETFL);
flags |=O_NONBLOCK; //设置非阻塞
fcntl(cfd,F_SETFL,flags);
struct epoll_event ev;
ev.data.fd=cfd;
ev.events =EPOLLIN|EPOLLET; //设置边缘触发
epoll_ctl(efd,EPOLL_CTL_ADD,cfd,&ev); //把新到的事件添加到树上
}
int init_lfd(int port,int efd){
int lfd;
struct sockaddr_in ser_addr;
ser_addr.sin_family = AF_INET; //初始化服务器端的地址结构
ser_addr.sin_port =port;
ser_addr.sin_addr.s_addr =htonl(INADDR_ANY);
lfd =socket(AF_INET,SOCK_STREAM,0);
int opt =1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); //port reuse
bind(lfd,(struct sockaddr*)&ser_addr,sizeof(ser_addr));
listen(lfd,128);
struct epoll_event ev;
ev.data.fd=lfd;
ev.events =EPOLLIN;
epoll_ctl(efd,EPOLL_CTL_ADD,lfd,&ev); //将lfd添加树上
return lfd;
}
void epoll_run(int port){ //CREATE THE EPOLL TREE
int i;
struct epoll_event max_event[MAX_SIZE];
int efd =epoll_create(MAX_SIZE);
int lfd =init_lfd(port,efd); //init the lfd;
while (1)
{
/* 重复监听端口,是否有新的连接 */
int ret = epoll_wait(efd,max_event,MAX_SIZE,-1);
for(i =0;i<=ret;++i){
struct epoll_event *ev =&max_event[i];
if(ev->events & EPOLLIN){ //只监听读事件,其他一概不考虑
if(ev->data.fd == lfd){
do_accept(ev->data.fd,efd);
}else{
do_read(ev->data.fd,efd); //接收到新的通信,准备回发信息;
}
}else{
continue;
}
}
}
}
int main(int argc, char* argv[]){
if(argc < 3){
printf("sever port path\n");
}
int port =atoi(argv[1]);
int ret =chdir(argv[2]);
if(ret !=0){
perror("chdir error:");
exit(1);
}
epoll_run(port);
}