socket编程 -- epoll模型服务端/客户端通信的实现

本文介绍了Linux内核中的epoll多路I/O复用技术,它是select和poll的增强版,能更高效地管理多个文件描述符。epoll通过epoll_create创建句柄,epoll_ctl添加、修改或删除监听事件,epoll_wait等待事件就绪。示例程序展示了服务器如何使用epoll接收并转发客户端数据。
摘要由CSDN通过智能技术生成

多路I/O复用之epoll

1、epoll简介

     epoll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。

2、epoll 常用函数

   epoll使用过程中,经常使用以下3个函数:

int epoll_create(int size); 
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

(1) epoll_create 函数创建一个epoll句柄,参数size表明内核要监听的描述符数量。
调用成功时返回一个epoll句柄描述符,失败时返回-1。

  • 核心数据结构是:1个红黑树和1个链表

(2) epoll_ctl 函数注册要监听的事件类型。
四个参数解释如下:

  • epfd 表示epoll句柄
  • op 表示fd操作类型,有如下3种
    EPOLL_CTL_ADD    注册新的fd到epfd中
    EPOLL_CTL_MOD   修改已注册的fd的监听事件
    EPOLL_CTL_DEL     从epfd中删除一个fd
  • fd 是要监听的描述符
  • event 表示要监听的事件
    epoll_event 结构体定义如下:
		struct epoll_event {
		 __uint32_t events; /* Epoll events */   EPOLLIN表示对应的文件描述符可以读
		epoll_data_t data; /* User data variable */
		 }; 
		typedef union epoll_data { 
		void *ptr; int fd; 
		__uint32_t u32; 
		__uint64_t u64; 
		} epoll_data_t;

(3) epoll_wait 函数等待事件的就绪,成功时返回就绪的事件数目,调用失败时返回 -1,等待超时返回 0。

  • epfd 是epoll句柄
  • events 表示从内核得到的就绪事件集合
  • maxevents 告诉内核events的大小
  • timeout 表示等待的超时事件

3、示例程序,多客户端给服务器发送数据包,服务器收到包后,转发给每个客户端。

服务器代码:

#include<iostream>
#include<unistd.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include<stdio.h>
#include<string.h>
#include<arpa/inet.h>
#include <sys/epoll.h>
#include<vector>

using namespace std;

#define SERVER_PORT    9527

vector<int > client_info;  //定义一个容器来存放accept_fd


int main()
{
	int  w_size=0;
	//socket
	int server_fd;
	server_fd = socket(AF_INET, SOCK_STREAM, 0);
	if (server_fd < 0)
	{
		perror("socket error");
		return -1;
	}
	//bind
	int ret = 0;
	struct sockaddr_in  server_addr;
	bzero(&server_addr, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(SERVER_PORT);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	ret = bind(server_fd, (const struct sockaddr*)&server_addr, sizeof(server_addr));
	if (ret < 0)
	{
		perror("bind  error");
		return -1;
	}
	//listen
	ret = listen(server_fd, 10);
	if (ret < 0)
	{
		perror("listen error");
		return -1;
	}
	//epoll
	int epoll_fd;                            //epoll 文件描述符
	int epoll_wait_return;
	int accept_fd;
	int r_size;
	char buffer[50] = { 0 };
	struct epoll_event  e_event;
	struct epoll_event  event_array[50] = { 0 };
	epoll_fd = epoll_create(10);              //创建epoll
	if (epoll_fd < 0)
	{
		perror("epoll_create error");
		return -1;
	}
	e_event.data.fd = server_fd;
	e_event.events = EPOLLIN;                 //对事件的可读感兴趣
	epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &e_event);

	while (1)
	{
		cout << "epoll wait......" << endl;
		epoll_wait_return = epoll_wait(epoll_fd, event_array, 50, -1);//永久等待
		for (int i = 0; i < epoll_wait_return; i++)
		{
			if (event_array[i].data.fd == server_fd)                 //有客户端连接过来
			{
				accept_fd = accept(server_fd, NULL, NULL);
				cout << "client connect" << endl;
				//把accept_fd 放入epoll 容器里面
				e_event.data.fd = accept_fd;
				e_event.events = EPOLLIN;
				epoll_ctl(epoll_fd, EPOLL_CTL_ADD, accept_fd, &e_event);
				client_info.push_back(accept_fd);                    //把每一个客户端的accept添加到向量中
			}
			else if (event_array[i].events & EPOLLIN)                //检查容器里的是否是读
			{
				r_size = read(event_array[i].data.fd, buffer, sizeof(buffer));
				//判断  r_size  >0 读到数据  ==0 有客户端退出  <0 失败
				if (r_size > 0)
				{
					cout << "server recv: " << buffer << endl;
					cout << "链接的 size=" << client_info.size() << endl;
					for (int i = 0; i < client_info.size(); i++)     //把收到的消息转发到每个客户端
					{
						w_size = write(client_info[i],buffer,sizeof(buffer));
					}
				}
				else if (r_size == 0)
				{
					//把套接字从内核容器中移除
					close(event_array[i].data.fd);
					e_event.data.fd = accept_fd;
					e_event.events = EPOLLIN;
					epoll_ctl(epoll_fd, EPOLL_CTL_DEL, event_array[i].data.fd, &e_event);

					//删除向量中的accept_fd
					vector<int >::iterator it;
					for (it = client_info.begin(); it != client_info.end(); it++)
					{
						if (event_array[i].data.fd== accept_fd)
						{
							break;
						}
					}
					cout << "client disconnect!" << endl;
				}
			}
		}
	}
	return 0;
}

运行效果: 这里客户端使用Linux 自带命令来代替客户端。

命令为:    nc   IP   端口号

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值