【Linux网络编程】I/O多路转接之 epoll 高性能简洁http服务器模型

和之前的select ,poll一样,服务器比较简陋,为了学习模型的基本框架,只向客户端回写一条html语句。启动服务器后,用手机或者电脑浏览器发起请求,服务端向浏览器写回html,响应字符串,然后可以看到,浏览器解析并显示 Hello epoll!.

启动服务器:


浏览器访问,服务端打印出请求报文:


浏览器解析出结果;


完整代码:


#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h> 
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/epoll.h>


/* 事件数组最大值 */
#define MAX_EVENTS 256

/* 每一个客户端在事件集中 events.data.ptr 所指向的数据 */
typedef struct fd_buf
{
	int  fd;        /* 保存当前客户端的socket描述符 */
	char buf[1024]; /* 当前客户端的缓冲区 */
}fd_buf_t, *fd_buf_p;

/* 为每一个客户端动态分配fd_buf_t 结构体 */
static void* new_fd_buf(int fd);

/* epoll事件集 */
struct epoll_event evts[MAX_EVENTS];

/* 获取一个监听连接的sockfd */
int run_getsockfd(const char*ip, int port);

/* 执行epoll逻辑 (创建,添加监听事件,等待返回)*/
void run_epoll(int listen_sockfd);

/* 处理客户端事件就绪 */
void run_action(int epollfd, int index);

/* 处理客户端连接请求,并添加到epoll模型中 */
void run_accept(int epollfd, int listen_sockfd);


int main(int argc, char** argv)
{

	/* 使用格式 ./xxx  服务器ip  服务器端口 */
	if(argc != 3)
	{
		printf("usage: [server_ip] [server_port]\n");
		return 1;
	}

	/* 获取监听socketfd  */
	int listen_sockfd = run_getsockfd(argv[1], atoi(argv[2]));

	/* 执行epoll 逻辑 */
	run_epoll(listen_sockfd);

	return 0;
}


/* 获取一个监听socket */
int run_getsockfd(const char* ip, int port)
{
	int sock = socket(AF_INET, SOCK_STREAM, 0);
	if( sock < 0){
		perror("socket()");
		exit(1);
	}

	/* 复用*/
	int opt = 1;
	setsockopt(sock , SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

	struct sockaddr_in server;
	bzero(&server, sizeof(server));
	server.sin_addr.s_addr = inet_addr(ip);
	server.sin_port = htons(port);
	server.sin_family = AF_INET;

	if(bind(sock, (struct sockaddr *)&server, sizeof(server) ) < 0){
		perror("bind()");
		exit(2);
	}

	if(listen(sock, 5) < 0){
		perror("listen()");
		exit(3);
	}

	return sock;
}



/* 执行epoll逻辑 (创建,添加监听事件,等待返回)*/
void run_epoll(int listen_sockfd)
{
	// 1. 创建 epoll 模型
	int epollfd = epoll_create(256);
	if( epollfd < 0)
	{
		perror("epoll_create()");
		exit(4);
	}

	// 2. 添加监听描述符到模型中,并关心读事件
	struct epoll_event evt;
	evt.events = EPOLLIN;
	evt.data.ptr = new_fd_buf(listen_sockfd);
	epoll_ctl(epollfd, EPOLL_CTL_ADD,listen_sockfd, &evt);

	int nfds = 0;        /* 就绪事件个数 */
	int timeout = 1000;  /* 一秒超时  */
	while(1)
	{
		nfds = epoll_wait(epollfd, evts, MAX_EVENTS, timeout );	
		if( nfds < 0)
		{
			perror("epoll_wait()");
			continue;
		}
		else if (nfds == 0)
		{
			printf("epoll_wait() timeout\n");
		}
		else
		{
			/* 变量处理已经就绪的事件 */
			int idx_check = 0;
			for(; idx_check < nfds; idx_check++)
			{
				fd_buf_p fp  = evts[idx_check].data.ptr;
				/* 监听socket 读事件就绪 */
				if( fp->fd == listen_sockfd &&\
					evts[idx_check].events & EPOLLIN )
				{
					/* 接受客户端的请求 */
					run_accept(epollfd, listen_sockfd);
				}/* 客户端socket 事件就绪 */
				else if ( fp->fd != listen_sockfd )
				{
					run_action(epollfd, idx_check);
				}
			}
		}
	}
}

static void* new_fd_buf(int fd)
{
	fd_buf_p ret  = (fd_buf_p)malloc(sizeof(fd_buf_t));
	assert( ret != NULL);

	ret->fd = fd;
	memset(ret->buf, 0, sizeof(ret->buf));
	return ret; 
}

/* 处理客户端连接请求,并添加到epoll模型中 */
void run_accept(int epollfd, int listen_sockfd)
{
	
	struct sockaddr_in cliaddr;
	socklen_t clilen = sizeof(cliaddr);

	int new_sock = accept(listen_sockfd, (struct sockaddr*)&cliaddr, &clilen);
	if(new_sock < 0)
	{
		perror("accept");
		return;
	}

	printf("与客户端连接成功: ip %s port %d \n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));

	/* 将与客户端连接的sockfd 添加到epoll模型中,并关心读事件 */
	struct epoll_event evt;
	evt.events = EPOLLIN;
	evt.data.ptr = new_fd_buf(new_sock); /* 为每一个客户端链接分配fd和缓冲区 */
	epoll_ctl(epollfd, EPOLL_CTL_ADD, new_sock ,&evt);
}

/* 处理客户端事件就绪 */
void run_action(int epollfd, int index)
{

	/* 取得客户端结构体数据指针 */
	fd_buf_p fdp = evts[index].data.ptr;

	/* 读事件就绪 */
	if(evts[index].events & EPOLLIN)
	{
		ssize_t s = read(fdp->fd, fdp->buf, sizeof(fdp->buf));
		if(s > 0)
		{
			fdp->buf[s] = 0;
			printf("客户端请求消息:\n");
			printf("\n %s \n", fdp->buf);
			struct epoll_event evt; 
			evt.events = EPOLLOUT;
			evt.data.ptr = fdp;
			epoll_ctl(epollfd, EPOLL_CTL_MOD, fdp->fd, &evt);
		}
		else if(s <= 0)
		{
			/* 记得关闭socket,在epoll 模型中删除结点并释放分配的内存 */
			printf("\n客户端退出!\n");
			close(fdp->fd);
			epoll_ctl(epollfd, EPOLL_CTL_DEL, fdp->fd, NULL );
			free(fdp);
		}
	}
	else if(evts[index].events & EPOLLOUT)
	{/* 写事件就绪 */
		/* 发送完毕后,记得做清理操作 */
		const char* msg = "HTTP/1.1 200 OK\r\n\r\n<html><h1>Hello epoll!</h1></html>";
		write(fdp->fd, msg, strlen(msg));
		close(fdp->fd);
		epoll_ctl(epollfd, EPOLL_CTL_DEL, fdp->fd, NULL);
		free(fdp);
	}
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值