网络IO 多路IO复用 之 epoll

简介: 网络IO 多路IO复用 之 epoll

网络IO 多路IO复用

select

poll

epoll

 

epoll用于多路io复用,具体是如何实现的?

内部使用

epoll_create()

epoll_ctl()

epoll_wait()

3个函数来实现 多路io复用

epoll模型的底层是用红黑树来处理的

 

其中的水平触发和边沿触发的区别?

   ET 是recvbuf 从没有数据到有数据,就会触发

   LT 是 recvbuf里面有数据  就会一直触发

   

   大块数据 用  LT

   小块数据 用  ET

   以recvbuf为界限的

 

epoll的实现流程

1、创建sockfd

socket(AF_INET,SOCK_STREAM,0)

2、初始化addr 属性 struct sockaddr_in (sa_handler ,sa_port,sa_addr.s_addr)

3、bind sockfd

4、listen 设置最大连接数

5、创建 epoll的句柄,epollfd,初始化专门用于增、删、改的事件的ev属性,第一个ev 是用于连接的,用水平触发的方式,同时创建events事件数组 ,struct epoll_event

6、开始使用epoll_wait阻塞等待事件的发生,参数是epollfdepoll句柄,作为传出参数的event,epoll的大小 ,以及时间 ,返回值为 nready,实际的触发事件数

7、遍历实际触发的事件数,判断是触发的是sockfd链接,还是客户端的数据通信 事件,

若是sockfd的连接事件,则定义好客户端的数据结构和数据长度,作为传出参数,传入到 accept中,返回客户端的fd,将fd加入到epoll 红黑树中

若是客户端的fd发送数据的事件,则使用read/recv函数读取该fd上的数据,并进行数据处理,

判断数据通信的时候服务器 使用 recv/read 读取数据,放入到buff中,

如果函数返回值是 <0 ,则需要判断,错误号若为 EAGAIN || EWOULDBLOCK -- 被信号打断了,未读取到数据,将当前客户端的fd移除rfds集合,且关闭 当前客户端fd

如果函数返回值是 == 0,说明客户端断开连接了,将当前客户端的fd移除rfds集合,且关闭 当前客户端fd

如果函数返回值是 > 0,说明正常读取到数据,开始对数据进行处理

若处理完毕后,将该客户端的fd移出epoll红黑树。

 

最后 关闭sockfd 套接字 close(sockfd)

 

编码

写一个简单的demo,来感受一下epoll的使用

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/poll.h>
#include <unistd.h>
#define BUFFER_LENGTH 1024
#define EPOLL_LENGTH  1024
int main(int argc,char * argv[])
{
//端口从命令行输入   如 : ./server 8888
  if(argc < 2)
  {
    perror("please input port nunber");
    return -1;
  }
//建立tcp套接字
  int sockfd = socket(AF_INET,SOCK_STREAM,0);
  if(sockfd < 0)
  {
    perror("socket");
    return -1;
  }
//初始化属性
  struct sockaddr_in seraddr;
  memset(&seraddr,0,sizeof(seraddr));
  seraddr.sin_family = AF_INET;
  seraddr.sin_port = htons(atoi(argv[1]));
  seraddr.sin_addr.s_addr = INADDR_ANY;
//bind
  int ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(struct sockaddr_in));
  if(ret < 0){
    perror("bind");
    return -1;
  }
//设置最大连接数
  ret = listen(sockfd,10);
  if(ret < 0){
    perror("listen");
    return -1;
  }
//epoll
//创建epoll fd
  int epollfd = epoll_create(EPOLL_LENGTH); //这个参数 只有 0 和大于0 的区别,作用不大
  struct epoll_event ev,event[EPOLL_LENGTH]={0};
  ev.events = EPOLLIN;  //用于连接的,最好使用水平触发,因为水平触发的时候,有数据的时候会一直触发
  ev.data.fd = sockfd;
  epoll_ctl(epollfd,EPOLL_CTL_ADD,sockfd,&ev);
//开始监听
  while(1){
    int nready = epoll_wait(epollfd,event,EPOLL_LENGTH,-1);
    if (nready == -1) {
      printf("epoll_wait error\n");
      break;
    }
    for(int i = 0;i<nready;i++){
 //如果是发起连接的
      if(event[i].data.fd == sockfd ){
   //准备好客户端的数据结构
        struct sockaddr_in client_addr;
        memset(&client_addr, 0, sizeof(struct sockaddr_in));
        socklen_t client_len = sizeof(client_addr);
        int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);
        if (clientfd <= 0) continue;
        char str[INET_ADDRSTRLEN] = {0};
        printf("recvived from %s at port %d, sockfd:%d, clientfd:%d\n", inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str)),
            ntohs(client_addr.sin_port), sockfd, clientfd);
  //将客户端的fd加入到event中
        ev.events = EPOLLIN | EPOLLET;//通信的时候,可以选择水平或者是边沿触发,大块数据用边沿触发,小块数据用水平触发
        ev.data.fd = clientfd;
        epoll_ctl(epollfd,EPOLL_CTL_ADD,clientfd,&ev);
      }
  //如果是进行数据通信的
      else{
        int clientfd = event[i].data.fd;
        char buffer[BUFFER_LENGTH] = {0};
        int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);
        //可能是因为信号打断,因此要判断一下错误号
        if (ret < 0) {
          if (errno == EAGAIN || errno == EWOULDBLOCK) {
            printf("read all data");
          }
          close(clientfd);
          //将该fd 从 epoll中删除
          ev.events = EPOLLIN | EPOLLET;
          ev.data.fd = clientfd;
          epoll_ctl(epollfd, EPOLL_CTL_DEL, clientfd, &ev);
          //客户端关闭连接
        } else if (ret == 0) {
          printf(" disconnect %d\n", clientfd);
          close(clientfd);
          ev.events = EPOLLIN | EPOLLET;
          ev.data.fd = clientfd;
          epoll_ctl(epollfd, EPOLL_CTL_DEL, clientfd, &ev);
          break;
        } else {
          printf("Recv: %s, %d Bytes\n", buffer, ret);
        }
      }
    }
}
  close(sockfd);
  return 0;
}

 

编译:

gcc -o server_epoll server_epoll.c

 

相关文章
|
2月前
|
存储 网络协议 算法
从HPACK到多路复用,揭秘HTTP/2如何终结网络拥堵
HTTP/2通过HPACK压缩头部冗余信息,提升传输效率;并利用多路复用技术,在单个TCP连接上并行处理多个请求,避免队头阻塞,显著提升性能。同时支持服务器推送和流优先级设置,优化资源加载体验。
202 7
|
6月前
|
监控 应用服务中间件 Linux
掌握并发模型:深度揭露网络IO复用并发模型的原理。
总结,网络 I/O 复用并发模型通过实现非阻塞 I/O、引入 I/O 复用技术如 select、poll 和 epoll,以及采用 Reactor 模式等技巧,为多任务并发提供了有效的解决方案。这样的模型有效提高了系统资源利用率,以及保证了并发任务的高效执行。在现实中,这种模型在许多网络应用程序和分布式系统中都取得了很好的应用成果。
213 35
|
Ubuntu 网络协议 Unix
02理解网络IO:实现服务与客户端通信
网络IO指客户端与服务端通过网络进行数据收发的过程,常见于微信、QQ等应用。本文详解如何用C语言实现一个支持多客户端连接的TCP服务端,涉及socket编程、线程处理及通信流程,并分析“一消息一线程”模式的优缺点。
286 0
|
8月前
|
存储 网络协议 安全
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
351 23
|
8月前
|
机器学习/深度学习 API Python
Python 高级编程与实战:深入理解网络编程与异步IO
在前几篇文章中,我们探讨了 Python 的基础语法、面向对象编程、函数式编程、元编程、性能优化、调试技巧、数据科学、机器学习、Web 开发和 API 设计。本文将深入探讨 Python 在网络编程和异步IO中的应用,并通过实战项目帮助你掌握这些技术。
|
9月前
|
网络协议 Linux Go
用 Go 基于 epoll 实现一个最小化的IO库
Go 语言社区中存在多个异步网络框架,如 evio、nbio、gnet 和 netpoll 等。这些框架旨在解决标准库 netpoll 的低效问题,如一个连接占用一个 goroutine 导致的资源浪费。easyio 是一个最小化的 IO 框架,核心代码不超过 500 行,仅实现 Linux 下的 epoll 和 TCP 协议。它通过 Worker Pool、Buffer 等优化提高了性能,并提供了简单的事件处理机制。
144 0
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
1月前
|
Java Unix Go
【Java】(8)Stream流、文件File相关操作,IO的含义与运用
Java 为 I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中。!但本节讲述最基本的和流与 I/O 相关的功能。我们将通过一个个例子来学习这些功能。
176 1
|
Java 大数据
解析Java中的NIO与传统IO的区别与应用
解析Java中的NIO与传统IO的区别与应用
|
Java 大数据 API
Java 流(Stream)、文件(File)和IO的区别
Java中的流(Stream)、文件(File)和输入/输出(I/O)是处理数据的关键概念。`File`类用于基本文件操作,如创建、删除和检查文件;流则提供了数据读写的抽象机制,适用于文件、内存和网络等多种数据源;I/O涵盖更广泛的输入输出操作,包括文件I/O、网络通信等,并支持异常处理和缓冲等功能。实际开发中,这三者常结合使用,以实现高效的数据处理。例如,`File`用于管理文件路径,`Stream`用于读写数据,I/O则处理复杂的输入输出需求。
696 12