网编day4-poll-epoll

7.2 网络编程_day4

复习:

select的超时时间检测:

超时检测的必要性:
1. 避免进程在没有数据时无限制的阻塞;
2. 规定时间未完成语句应有的功能,则会执行相关功能;

头文件: #include<sys/select.h> #include<sys/time.h>
#include<sys/types.h> #include<unistd.h>
声明: int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
功能:监测是哪些文件描述符产生事件,阻塞等待产生.
参数:nfds: 监测的最大文件描述个数(文件描述符从0开始,这里是个数,记得+1)
readfds: 读事件集合; // 键盘鼠标的输入,客户端连接都是读事件
writefds: 写事件集合; //NULL表示不关心
exceptfds:异常事件集合; //NULL 表示不关心
timeout: 设为NULL,等待直到某个文件描述符发生变化;
设为大于0的值,有描述符变化或超时时间到才返回。
超时时间检测:如果规定时间内未完成函数功能,返回一个超时的信息,我们可以根 据该信息设定相应需
求;
返回值: <0 出错 >0 表示有事件产生;
如果设置了超时检测时间:&tv
<0 出错 >0 表示有事件产生; ==0 表示超时时间已到;
结构体如下:
struct timeval {
long tv_sec; 以秒为单位,指定等待时间
long tv_usec; 以毫秒为单位,指定等待时间
};
void FD_CLR(int fd, fd_set *set); //将set集合中的fd清除掉
int FD_ISSET(int fd, fd_set *set); //判断fd是否在set集合中产生了事件
void FD_SET(int fd, fd_set *set); //将fd加入到集合中
void FD_ZERO(fd_set *set); //清空集合

在这里插入图片描述

POLL:

特点:

  1. 优化文件描述符个数的限制;
    (根据poll函数第一个函数的参数来定,如果监听的事件为1个,则结构体数组容量为1,如果想监听100个,那么这个结构体数组的容量就为100,多少文件描述符由程序员自己来决定)
  2. poll被唤醒之后需要重新轮询一遍驱动,效率比较低(消耗CPU)
  3. poll不需重新构造文件描述符表(也不需清空表),只需要从用户空间向内核空间拷贝一次数据(效率相对比较高)

poll的流程

使用: 1.创建结构体数组
2.添加结构体成员的文件描述符以及触发方式
3.保存数组内最后一个有效元素的下标
4. 调用函数poll
5.判断结构体内文件描述符是否触发事件
6.根据不同的文件描述符触发不同事件
7. 做对应的逻辑处理

poll函数

机制:

在这里插入图片描述

./server:

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <poll.h>
//select TCP_server 实现链接多个客户端
int main(int argc, const char *argv[])
{
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd < 0)
	{
	   perror("socker is err:");
	   return -1;
	}

    struct sockaddr_in saddr,caddr;
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(atoi(argv[1]));
	saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
   
    int len = sizeof(caddr);

	if(bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr)) <0)
	{
	   perror("bind is err:");
	   return -1;
	}

	if(listen(sockfd,5) < 0)
	{
	  perror("listen is err:");
	  return -1;
	}
    
    //1. 创建结构体数组
    struct pollfd fds[100]; //最多接受100个文件描述符
    //2.添加关心的文件描述符 并 给文件描述符设置触发方式
    fds[0].fd = 0; // 标准输入
    fds[0].events = POLLIN; //读事件
    
    fds[1].fd = sockfd;
    fds[1].events = POLLIN;

    //3.保存数组内最后一个有效元素的下标
    int nfds = 1;

    char buf[128];
    int ret;
    while(1)
    {
         //4.调用poll函数,检测文件描述符产生事件
         ret = poll(fds,nfds+1,-1);
         if(ret < 0)
         {
             perror("poll is err:");
             return -1;
         }

         for(int i = 0; i <= nfds; i++)
         {
             if(fds[i].revents == POLLIN) //如果第三个参数被内核填充,则说明有事件响应
             {
                 if(fds[i].fd == 0)  //键盘输入   i == 0
                 {
                    fgets(buf,sizeof(buf),stdin);
                     if(buf[strlen(buf) -1] == '\n')
                        buf[strlen(buf) -1] = '\0';

                    printf("key: %s\n",buf);
                 }
                 else if(fds[i].fd == sockfd) // i  == 1
                 {
                     int acceptfd = accept(sockfd,(struct sockaddr *)&caddr,&len);
                     if(acceptfd < 0)
                     {
                         perror("accept is err:");
                         return -1;
                     }
                     printf("fd: %d, ip: %s, port: %d\n",acceptfd,inet_ntoa(caddr.sin_addr),\
                                                    ntohs(caddr.sin_port));
                     
                      nfds++;// 1  >> 2
                      fds[nfds].fd = acceptfd;
                      fds[nfds].events = POLLIN;
                      
                      break; 
                 }
                 else
                 {
                     int recvbyte = recv(fds[i].fd,buf,sizeof(buf),0);
                     if(recvbyte < 0)
                     {
                         perror("recv is err:");
                         return -1;
                     }
                     else if(recvbyte == 0)
                     {
                         printf("%d client is exit\n",fds[i].fd);
                         //关闭文件描述符
                         close(fds[i].fd);
                         //将最后一个数组下标的内容移到当前数组下标
                          fds[i] = fds[nfds];
                         //有效数组元素下标-1
                           nfds--;
                //下次循环仍然是本轮循环, 这样可以检测 fds[nfds]的内容  
                          i--;

                     }
                     else
                     {
                         printf("client: %d  %s\n",fds[i].fd,buf);
                     }
                 }
                 
                 
             }
         }

    }
  close(sockfd);
 return 0;
}

关于: 退出客户端, 为什么要进行i-- ,nfds–等操作:
在这里插入图片描述

超时时间检测:

在这里插入图片描述

epoll实现: (异步)

epoll了解其机制就可以
select,poll都属于 同步IO机制(轮询)
epoll属于异步IO机制(不轮询):

epoll的提出–》它所支持的文件描述符上限是系统可以最大打开的文件的数目;
eg:1GB机器上,这个上限10万个左右。
每个fd上面有callback(回调函数)函数,只有产生事件的fd才有主动调用callback,不需要轮询。
注意:
Epoll处理高并发,百万级

1.红黑树: 是特殊的二叉树(每个节点带有属性),Epoll怎样能监听很多个呢?首先创建树的根节点,每个节点都是一个fd以结构体的形式存储(节点里面包含了一些属性,callback函数)

2.就绪链表: 当某一个文件描述符产生事件后,会自动调用callback函数,通过回调callback函数来找到链表对应的事件(读时间还是写事件)。

epoll特点

1.监听的最大的文件描述符没有个数限制(取决与你自己的系统 1GB - 10万个左右)

2.异步I/O,epoll当有事件产生被唤醒之后,文件描述符主动调用callback(回调函数)函数直接拿到唤醒的文件描述符,不需要轮询,效率高

3.epoll不需要重新构造文件描述符表,只需要从用户空间向内核空间拷贝一次数据即可.

epoll的流程:

Epoll的使用:
1.创建红黑树 和 就绪链表
2.添加文件描述符和事件信息到树上
3.阻塞等待事件的产生,一旦产生事件,则进行处理
4.根据链中准备处理的文件描述符 进行处理

在这里插入图片描述

epoll_create 创建红黑树以及链表

头文件:#include <sys/epoll.h>
声明:int epoll_create(int size); 
功能:创建红黑树根节点(创建epoll实例) , 同时也会创建就绪链表
返回值:成功时返回一个实例(二叉树句柄),失败时返回-1

epoll_ctl 控制epoll属性

声明: int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

功能:控制epoll属性,比如给红黑树添加节点

参数: 1. epfd:   epoll_create函数的返回句柄。//一个标识符
     2. op:表示动作类型,有三个宏:			        
                EPOLL_CTL_ADD:注册新的fd到epfd中
			      EPOLL_CTL_MOD:修改已注册fd的监听事件
			      EPOLL_CTL_DEL:从epfd中删除一个fd
     3. 要操作的文件描述符
     4. 结构体信息: 
 typedef union epoll_data {
               int fd;      //要添加的文件描述符
               uint32_t u32;  typedef unsigned int
               uint64_t u64;   typedef unsigned long int
        } epoll_data_t;

   struct epoll_event {
       uint32_t events; 事件
       epoll_data_t data; //共用体(看上面)
		};

	  关于events事件:
			 EPOLLIN:  表示对应文件描述符可读
		    EPOLLOUT: 可写
			 EPOLLPRI:有紧急数据可读;
		    EPOLLERR:错误;
		    EPOLLHUP:被挂断;
			 EPOLLET:触发方式,边缘触发;(默认使用边缘触发)
			 ET模式:表示状态的变化;
           NULL: 删除一个文件描述符使用,无事件
           
返回值:成功:0, 失败:-1

epoll_wait等待事件产生

声明: int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

功能:等待事件产生
   内核会查找红黑树中有事件响应的文件描述符, 并将这些文件描述符放入就绪链表
    就绪链表中的内容, 执行epoll_wait会同时复制到第二个参数events

参数: 	epfd:句柄;
		events:用来保存从就绪链表中响应事件的集合;
		maxevents:  表示每次在链表中拿取响应事件的个数;
		timeout:超时时间,毫秒,0立即返回  ,-1阻塞	

返回值: 成功: 实际从链表中拿出的数目     失败时返回-1

./server:

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>

//select TCP_server 实现链接多个客户端
int main(int argc, const char *argv[])
{
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd < 0)
	{
	   perror("socker is err:");
	   return -1;
	}

    struct sockaddr_in saddr,caddr;
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(atoi(argv[1]));
	saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
   
    int len = sizeof(caddr);

	if(bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr)) <0)
	{
	   perror("bind is err:");
	   return -1;
	}

	if(listen(sockfd,5) < 0)
	{
	  perror("listen is err:");
	  return -1;
	}
    //1.创建红黑数和就绪链表
    int epfd = epoll_create(1);
    //2.添加文件描述符的属性到树上
    struct epoll_event event;
    struct epoll_event events[20];
    event.data.fd = 0; //终端输入
    event.events = EPOLLIN|EPOLLET;
    //上树
    epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event);

    event.data.fd = sockfd; //套接字
    event.events = EPOLLIN|EPOLLET;
    epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&event);

    char buf[128];
    int ret;
    while(1)
    {
         //3.阻塞等待文件描述符的事件产生
         ret = epoll_wait(epfd,events,20,-1);
        if(ret < 0)
        {
            perror("epoll_waite is err:");
            return -1;
        }
        //4.根据结构体数组内的文件描述符 进行相应的逻辑处理
        for(int i = 0;i < ret;i++) //ret代表产生事件的文件描述符的数量
        {
            if(events[i].data.fd == 0) //终端输入
            {
               fgets(buf,sizeof(buf),stdin);
               if(buf[strlen(buf) -1] == '\n')
                 buf[strlen(buf) -1] = '\0';

                 printf("key: %s\n",buf);
            }
            else if(events[i].data.fd == sockfd) //建立通信
            {
               int acceptfd = accept(sockfd,(struct sockaddr *)&caddr,&len);
               if(acceptfd < 0)
               {
                   perror("accept is err:");
                   return -1;
               }
               printf("fd: %d  ip: %s port: %d\n",acceptfd,inet_ntoa(caddr.sin_addr),\
                                        ntohs(caddr.sin_port));
. 
                //新的文件描述符上树
                event.data.fd = acceptfd;
                event.events = EPOLLIN|EPOLLET;
                //上树
                epoll_ctl(epfd,EPOLL_CTL_ADD,acceptfd,&event);
            }
            else
            {
        int recvbyte =recv(events[i].data.fd,buf,sizeof(buf),0);
                if(recvbyte < 0)
                {
                    perror("recv is err:");
                    return -1;
                }
                else if(recvbyte == 0)
                {
                printf("client %d is exit\n",events[i].data.fd);
                    close(events[i].data.fd);
                    //树上的文件描述符 下树
                       epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,NULL);
                }
                else 
                {
                printf("client %d: %s\n",events[i].data.fd,buf);
                }
            }
        }
    } 

close(sockfd);
return 0;
}

超时检测

在这里插入图片描述
在这里插入图片描述

  1. 掌握poll代码以及流程和特点, 了解epoll代码,掌握epoll特点
    2.画一个表 (IO多路复用的特点以及流程的表)
    3.完成多进程/线程实现TCP全双工
    4.UDP聊天室
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值