高并发服务器编程之epoll(一)

I/O多路复用-epoll

        epoll与select和poll在使用和实现上有很大区别。首先,epoll使用一组函数来完成,而不是单独的一个函数;其次,epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,无需向select和poll那样每次调用都要重复传入文件描述符集合事件集。

IO多路复用epoll模型优缺点

        思路:单进程调用epoll_wait()函数来处理多个连接请求

        优点:单进程可执行同时处理多个网络连接请求,可以达到硬件上限,性能上由于只管理就绪连接,不需要轮询事件集合,因此性能大大提高

        缺点:仅支持linux平台

函数

epoll_create()

size参数并不起作用,只是给内核一个提示,告诉它事件表需要多大。

epoll_ctl()


epfd:文件句柄,标识事件表

op:操作类型,

       EPOLL_CTL_ADD:注册fd上的事件

       EPOLL_CTL_MOD:修改fd上注册的事件

       EPOLL_CTL_DEL:删除fd上注册的事件

event:指定事件,定义如下


常用events:

    EPOLLIN:可读事件

    EPOLLOUT:可写事件

    EPOLLET:边缘事件

epoll_wait()


服务器端示例

这里使用epoll_data联合体中的fd, ptr的使用将在以后讲解。

ptr的使用链接:epoll_data.ptr

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

#define MAX_SIZE 1024

int main(int argc, char *argv[])
{
	int sockfd;
	int ret;
	struct sockaddr_in server_addr;
	struct sockaddr_in client_addr;
	int client_sockfd;
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd < 0){
		perror("socket error");
		return -1;
	}
	
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(9000);
	server_addr.sin_addr.s_addr = inet_addr("192.168.121.128");

	int len = sizeof(struct sockaddr_in);
	ret = bind(sockfd, (struct sockaddr*)&server_addr, len);
	if (ret == -1){
		perror("bind error");
		return -1;
	}
	ret = listen(sockfd, MAX_SIZE);
	if (ret < 0){
		perror("listen error");
		return -1;
	}
	int epfd, nfds = 0, i = 0;
	struct epoll_event ev, evs[MAX_SIZE];
	//创建一个epoll监听集合
	epfd = epoll_create(MAX_SIZE);
	ev.events = EPOLLIN;
	ev.data.fd = sockfd;
	//将监听socket描述符与定义好的事件添加到epoll监听集合中
	if (epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev) < 0){
		perror("epoll_ctl error");
		close(sockfd);
		return -1;
	}
	char buff[1024] = {0};
	while (1){
		//epoll阻塞等待事件触发,当某个描述符触发事件,则将触发的事件写入到evs中
		//并且返回触发事件的描述符个数nfds
		nfds = epoll_wait(epfd, evs, MAX_SIZE, 3000);
		if (nfds < 0){
			//epoll等待出错
			perror("epoll_wait error");
			continue;
		}else if (nfds == 0){
			//3000毫秒内没有任何描述符触发事件
			printf("timeout\n");
			continue;
		}
		//循环从evs中取出触发的事件
		for (i = 0;i<nfds;i++){
			//如果evs[i].data.fd == sockfd,代表客户端请求通道有客户端请求到来
			//则接受客户端连接请求
			if (evs[i].data.fd == sockfd){
				client_sockfd = accept(sockfd, (struct sockaddr*)&client_addr, &len);
				if (client_sockfd < 0){
					perror("new client error");
					continue;
				}
				printf("new accept----ip:[%s],port:[%d]\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
				ev.events = EPOLLIN;
				ev.data.fd = client_sockfd;
				//add event
				ret = epoll_ctl(epfd, EPOLL_CTL_ADD, client_sockfd, &ev);
				if (ret < 0){
					perror("epoll_ctl error");
					continue;
				}
				//EPOLLIN代表可读事件
			}else if (evs[i].events & EPOLLIN){
				//代表有其他数据传输,则接受数据
				memset(buff, 0x00, 1024);
				ret = recv(evs[i].data.fd, buff, 1024, 0);
				if (ret <= 0){
					//ret == 0代表对端断开了tcp连接
					//ret < 0 出错
					//EINTR被信号打断出错
					//EAGAIN描述符没有准备好
					if (errno == EAGAIN || errno == EINTR){
						continue;
					}
					//除了上面2个错误之外则从监听集合中删除描述符并关闭socket
					epoll_ctl(epfd, EPOLL_CTL_DEL, evs[i].data.fd, &ev);
					close(evs[i].data.fd);
				}
				printf("%s--]\n", buff);
			}
		}
	}
	close(sockfd);
	return 0;
}

客户端示例

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>


int main(int argc, char *argv[])
{
	int sock_fd;
	struct sockaddr_in ser_addr;
	struct sockaddr_in cli_addr;
	char buff[1024] = {0};
	//调用socket函数创建套接字
	sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (sock_fd < 0){
		perror("socket error!");
		return -1;
	}
	//赋值发送地址信息,服务器端地址信息
	ser_addr.sin_family = AF_INET;
	ser_addr.sin_port = htons(9000);
	ser_addr.sin_addr.s_addr = inet_addr("192.168.121.128");

	cli_addr.sin_family = AF_INET;
	cli_addr.sin_port = htons(8000);
	cli_addr.sin_addr.s_addr = inet_addr("192.168.121.128");

	int len = sizeof(struct sockaddr_in);
	int ret;
	//调用 bind 函数绑定一个发送地址信息
	ret = bind(sock_fd, (struct sockaddr*)&cli_addr, len);
	if (ret < 0){
		perror("bind error!!!");
		return -1;
	}
	//调用 connect 函数连接服务器
	ret = connect(sock_fd, (struct sockaddr*)&ser_addr, len);
	if (ret < 0){
		perror("connect error!!!");
		return -1;
	}
	int i = 0;
	while (1){
		memset(buff, 0x00, 1024);
		sprintf(buff, "test-%d", i++);
		//连接成功后,调用 send 函数发送数据到服务器端
		send(sock_fd, buff, 1024, 0);
		sleep(1);
	}
	//调用 close 函数关闭套接字
	close(sock_fd);
	return 0;
}


猜你喜欢

转载自blog.csdn.net/qq_33408113/article/details/80176612