基于EPOLL的http模型超详细代码讲解(C语言版)

 

 

 

 从上面的几个图片我们可以看出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);
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值