Linux网络编程之epoll模型

Linux网络编程之epoll模型


前言

提示:这里可以添加本文要记录的大概内容:

I/O 多路复用使得程序能同时监听多个文件描述符,能够提高程序的性能, Linux 下实现 I/O 多路复用的系统调用主要有 select、 poll 和 epoll。其中,select有最大文件描述符个数1024,虽然可以通过更改系统配置文件进行更改,但是一般不建议修改系统配置文件,因为这样会影响所有的用户。而且使用select模型需要反复从用户区转到内核区,当fd很大时开销也会很大。poll原理和select差不多,其优点是突破了1024文件描述符的限制,但是编写的代码不能移值,因此,linux上很少使用。epoll克服了上述两种模型的缺点,因此在linux网络编程中应用较为广泛。


提示:以下是本篇文章正文内容,下面案例可供参考

一、epoll模型相关的函数和结构体介绍

1.epoll_create
函数原型为

int epoll_create(int size);

函数说明: 创建一个树根
参数说明:
size: 最大节点数, 此参数在linux 2.6.8已被忽略, 但必须传递一个大于0的数.
返回值:
成功: 返回一个大于0的文件描述符, 代表整个树的树根.
失败: 返 回-1, 并设置errno值.

2.两个重要的结构体(联合体)

 typedef union epoll_data {
               void        *ptr;
               int          fd;//目前只要弄懂这个就行,其他三个变量可以先不管
               uint32_t     u32;
               uint64_t     u64;
           } epoll_data_t;

 struct epoll_event {
               uint32_t     events;      /* Epoll events */
               epoll_data_t data;        /* User data variable */
           };
epoll_event.events常用的有:
   EPOLLIN: 读事件
   EPOLLOUT: 写事件
   EPOLLERR: 错误事件
   EPOLLET: 边缘触发模式
epoll_event.data.fd: 要监控的事件对应的文件描述符

3.epoll_wait
函数原型为:

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);``
函数说明:等待内核返回事件发生
参数说明:
	epfd: epoll树根
	events: 传出参数, 其实是一个事件结构体数组
	maxevents: 数组大小
	timeout:
		-1: 表示永久阻塞
		0: 立即返回
		>0: 表示超时等待事件
 返回值:
成功: 返回发生事件的个数
失败: 若timeout=0, 没有事件发生则返回; 返回-1, 设置errno值, 

epoll_wait的events是一个传出参数, 调用epoll_ctl传递给内核什么值, 当epoll_wait返回的时候, 内核就传回什么值,不会对struct event的结构体变量的值做任何修改.

4.epoll_ctl
函数原型如下:

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
函数说明: 将要监听的节点在epoll树上添加, 删除和修改
参数说明:
epfd: epoll树根
op:
EPOLL_CTL_ADD: 添加事件节点到树上
EPOLL_CTL_DEL: 从树上删除事件节点
EPOLL_CTL_MOD: 修改树上对应的事件节点
fd: 事件节点对应的文件描述符

二、代码编写思路

1.创建socket,得到监听文件描述符lfd–socket()

 //创建监听socket
 // int socket(int domain, int type, int protocol);
 int lfd = socket(AF_INET, SOCK_STREAM, 0);

2.设置端口复用–setsockopt()

//设置端口复用
// int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen)
int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

3.绑定—bind()

//绑定端口号
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(12345);
seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(lfd, (struct sockaddr *)&seraddr, sizeof(struct sockaddr));

4.开始监听

//开始监听
listen(lfd, 128);

5.创建epoll实例

 //创建epoll实例
 int epfd = epoll_create(100);

6.创建一个epoll_event,并关联上监听文件描述符

 //创建一个epoll_event,并关联上监听文件描述符
 struct epoll_event epev;
 epev.events = EPOLLIN;
 epev.data.fd = lfd;

7 将监听文件描述符对应的epoll_event上epoll实例树

 //将监听文件描述符对应的epoll_event上epoll实例树
 // int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll_ctl(epfd, EPOLL_CTL_ADD,lfd, &epev);

8.创建一个epoll_event数组,用于关联用户socket

 struct epoll_event epevs[1024];

9.while循环
9.1循环等待事件发生

//循环等待事件的发生
int ret = epoll_wait(epfd, epevs, sizeof(epevs), -1);

9.2错误处理


        if (ret < 0)
        {
            perror("wait error:");
            if (errno == EINTR)
                continue;
            exit(-1);
        }

9.3遍历所发生的epoll事件

for (int i = 0; i < ret; i++)

9.3.1处理监听描述符事件

 if (epevs[i].data.fd == lfd) //先看看监听文件描述符有没有发生变化
   {
   int cfd = accept(lfd, NULL, NULL);
                epev.events = EPOLLIN;
                epev.data.fd = cfd;
                epoll_ctl(epfd, EPOLL_CTL_ADD,cfd, &epev);
                continue;
   }

9.3.2处理客户端发来的消息

             else
            {
                char buf[1024] = {0}; //创建一个接收数据的数组
                int n = read(epevs[i].data.fd, buf, sizeof(buf));
                if (n < 0)
                {
                    perror("read error:");
                    if (errno == EINTR)
                    {
                        continue;
                    }
                }
                else if (n == 0) //客户端下线
                {
                    printf("有客户端下线!\n");
                    epoll_ctl(epfd, EPOLL_CTL_DEL,epevs[i].data.fd, NULL);
                }
                else
                {
                    printf("接收到数据:%s\n", buf);
                    for (int j = 0; j < n; j++)
                    {
                        buf[j] = toupper(buf[j]);
                    }
                    //将数据反馈给客户端
                    int nr = write(epevs[i].data.fd, buf, sizeof(buf));
                    if(nr<0)
                    {
                        perror("write error:");
                    }
                }
            }

三.完整代码

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


int main()
{
    //创建监听socket
    // int socket(int domain, int type, int protocol);
    int lfd = socket(AF_INET, SOCK_STREAM, 0);

    //设置端口复用
    // int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen)
    int opt = 1;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    //绑定端口号
    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(12345);
    seraddr.sin_addr.s_addr = htonl(INADDR_ANY);

    bind(lfd, (struct sockaddr *)&seraddr, sizeof(struct sockaddr));

    //开始监听
    listen(lfd, 128);

    //创建epoll实例
    int epfd = epoll_create(100);

    //创建一个epoll_event,并关联上监听文件描述符
    struct epoll_event epev;
    epev.events = EPOLLIN;
    epev.data.fd = lfd;

    //将监听文件描述符对应的epoll_event上epoll实例树
    // int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    epoll_ctl(epfd, EPOLL_CTL_ADD,lfd, &epev);

    struct epoll_event epevs[1024];
    while (1)
    {
        //循环等待事件的发生
        int ret = epoll_wait(epfd, epevs, sizeof(epevs), -1);

        if (ret < 0)
        {
            perror("wait error:");
            if (errno == EINTR)
                continue;
            exit(-1);
        }

        for (int i = 0; i < ret; i++)
        {
            if (epevs[i].data.fd == lfd) //先看看监听文件描述符有没有发生变化
            {
                int cfd = accept(lfd, NULL, NULL);
                epev.events = EPOLLIN;
                epev.data.fd = cfd;
                epoll_ctl(epfd, EPOLL_CTL_ADD,cfd, &epev);
                continue;
            }
            else
            {
                char buf[1024] = {0}; //创建一个接收数据的数组
                int n = read(epevs[i].data.fd, buf, sizeof(buf));
                if (n < 0)
                {
                    perror("read error:");
                    if (errno == EINTR)
                    {
                        continue;
                    }
                }
                else if (n == 0) //客户端下线
                {
                    printf("有客户端下线!\n");
                    epoll_ctl(epfd, EPOLL_CTL_DEL,epevs[i].data.fd, NULL);
                }
                else
                {
                    printf("接收到数据:%s\n", buf);
                    for (int j = 0; j < n; j++)
                    {
                        buf[j] = toupper(buf[j]);
                    }
                    int nr = write(epevs[i].data.fd, buf, sizeof(buf));
                    if(nr<0)
                    {
                        perror("write error:");
                    }
                }
            }
        }
    }


    close(epfd);
    

    return 0;
}

四.测试结果

服务器:
[外链图片转存失败,源站可能有防盗不要水印啊]!链机制,https://(3.png(ttps://imgblog.csdnimg.cn/09c5881663b74a318547604318933b23.png)]
客户端:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值