linux下select、poll、epoll机制使用介绍

简介: 在上一篇文章编写了一个 Linux下基于TCP协议的群聊系统设计(多线程+select) 案例,演示了select函数的使用方法。这篇文章接着介绍剩下的poll、epoll函数。并且也是使用群聊系统的案例编写例子,方便理解实际用法。下面先介绍这3种函数的原型和相关头文件虽然select在上篇文章里已经介绍过,这里为了方便比较这3个函数,再把select详细介绍粘贴过来。

一、功能介绍

在上一篇文章,编写了一个 Linux下基于TCP协议的群聊系统设计(多线程+select) 案例,演示了select函数的使用方法。这篇文章接着介绍剩下的poll、epoll函数。并且也是使用群聊系统的案例编写例子,方便理解实际用法。

下面先介绍这3种函数的原型和相关头文件
虽然select在上篇文章里已经介绍过,这里为了方便比较这3个函数,再把select详细介绍粘贴过来。

1.1 select函数

#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);
函数功能: 监听指定数量的文件描述符的状态。
函数参数:
int nfds : 监听最大的文件描述符+1的值
fd_set *readfds :监听读事件的文件描述符集合,不想监听读事件这里可以填NULL
fd_set *writefds :监听写事件的文件描述符集合,不想监听事件这里可以填NULL
fd_set *exceptfds :监听其他事件的文件描述符集合,不想监听事件这里可以填NULL
struct timeval *timeout : 指定等待的时间。 如果填NULL表示永久等待,直到任意一个文件描述符产生事件再返回。
                          如果填正常时间,如果在等待的时间内没有事件产生,也会返回。
struct timeval {
        long    tv_sec;         /* seconds */
        long    tv_usec;        /* microseconds */
    };

返回值: 表示产生事件文件描述符数量。  ==0表示没有事件产生。  >0表示事件数量  <0表示错误。

void FD_CLR(int fd, fd_set *set);   //清除某个集合里的指定文件描述符
int  FD_ISSET(int fd, fd_set *set); //判断指定集合里的指定文件描述符是否产生了某个事件。 为真就表示产生了事件
void FD_SET(int fd, fd_set *set);  //将指定的文件描述符添加到指定的集合
void FD_ZERO(fd_set *set);  //清空整个集合。

1.2 poll函数

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
函数功能: 监听多个文件描述符的事件。
函数参数:
struct pollfd *fds :监听的事件,可以填结构体数组。
nfds_t nfds :监听的数量。
int timeout :等待的时间.ms单位.  >0表示正常等待时间。 ==0表示不等待  <0永久等待.
返回值:
    产生事件数量

struct pollfd {
    int   fd;         /* file descriptor  监听的文件描述符*/
    short events;     /* requested events 监听的事件   POLLIN表示可读事件*/
    short revents;    /* returned events  产生的事件--作为判断条件*/
};

1.3 epoll函数

#include <sys/epoll.h>

int epoll_create(int size);
函数功能: 创建epoll专用文件描述符的缓冲区。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
函数功能: 添加、删除、修改 监听文件描述符。
函数参数:
int epfd  epoll专用的文件描述符
int op    操作命令。EPOLL_CTL_ADD  EPOLL_CTL_MOD  EPOLL_CTL_DEL
int fd    要操作文件描述符
struct epoll_event *event  存放监听文件描述符信息的结构体

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
函数功能: 等待事件发生。
函数参数:
int epfd  epoll专用的文件描述符
struct epoll_event *events : 存放产生事件的文件描述结构体。
int maxevents  :最大监听的数量.
int timeout    :等待事件ms单位.   <0  >0 ==0

返回值:  产生事件的数量。

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      EPOLLIN  输入事件 */
    epoll_data_t data;      /* User data variable */
};

二、epoll、poll函数使用案例

在上篇文章里已经贴出了详细的群聊系统的源码,并且有运行效果图、思路讲解。

这里就只贴出客户端的代码,把原来select监听的客户端的代码分别改成了epoll、poll两种。

2.1 poll函数使用案例: 应用在TCP客户端

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sys/select.h>
#include <sys/time.h>
#include <poll.h>

//消息结构体
struct MSG_DATA
{
    char type; //消息类型.  0表示有聊天的消息数据  1表示好友上线  2表示好友下线
    char name[50]; //好友名称
    int number;   //在线人数的数量
    unsigned char buff[100];  //发送的聊天数据消息
};
struct MSG_DATA msg_data;

//文件接收端
int main(int argc,char **argv)
{   
    if(argc!=4)
    {
        printf("./app  <IP地址> <端口号> <名称>\n");
        return 0;
    }
    int sockfd;
    //忽略 SIGPIPE 信号--方式服务器向无效的套接字写数据导致进程退出
    signal(SIGPIPE,SIG_IGN); 

    /*1. 创建socket套接字*/
    sockfd=socket(AF_INET,SOCK_STREAM,0);
    /*2. 连接服务器*/
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_port=htons(atoi(argv[2])); // 端口号0~65535
    addr.sin_addr.s_addr=inet_addr(argv[1]); //IP地址
    if(connect(sockfd,(const struct sockaddr *)&addr,sizeof(struct sockaddr_in))!=0)
    {
        printf("客户端:服务器连接失败.\n");
        return 0;
    }

    /*3. 发送消息表示上线*/
    msg_data.type=1;
    strcpy(msg_data.name,argv[3]);
    write(sockfd,&msg_data,sizeof(struct MSG_DATA));

    int cnt;
    struct pollfd fds[2];
    fds[0].fd=sockfd;
    fds[0].events=POLLIN;
    fds[1].fd=0;
    fds[1].events=POLLIN;
    
    while(1)
    {
       //监听事件
       cnt=poll(fds,2,-1);
        if(cnt)
        {
            if(fds[0].events&fds[0].revents) //判断收到服务器的消息
            {
                cnt=read(sockfd,&msg_data,sizeof(struct MSG_DATA));
                if(cnt<=0) //判断服务器是否断开了连接
                {
                    printf("服务器已经退出.\n");
                    break;
                }
                else if(cnt>0)
                {
                    if(msg_data.type==0)
                    {
                        printf("%s:%s  在线人数:%d\n",msg_data.name,msg_data.buff,msg_data.number);
                    }
                    else if(msg_data.type==1)
                    {
                        printf("%s 好友上线. 在线人数:%d\n",msg_data.name,msg_data.number);
                    }
                    else if(msg_data.type==2)
                    {
                        printf("%s 好友下线. 在线人数:%d\n",msg_data.name,msg_data.number);
                    }
                }
            }
            
            if(fds[1].events&fds[1].revents)  //判断键盘上有输入
            {
                gets(msg_data.buff); //读取键盘上的消息
                msg_data.type=0; //表示正常消息
                strcpy(msg_data.name,argv[3]); //名称
                write(sockfd,&msg_data,sizeof(struct MSG_DATA));
            }
        }
    }
    close(sockfd);
    return 0;
}

2.2 epoll函数使用案例: 应用再TCP客户端

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sys/select.h>
#include <sys/time.h>
#include <poll.h>
#include <sys/epoll.h>

//消息结构体
struct MSG_DATA
{
    char type; //消息类型.  0表示有聊天的消息数据  1表示好友上线  2表示好友下线
    char name[50]; //好友名称
    int number;   //在线人数的数量
    unsigned char buff[100];  //发送的聊天数据消息
};
struct MSG_DATA msg_data;

#define MAX_EVENTS 10
struct epoll_event ev, events[MAX_EVENTS];
int epollfd;
int nfds;

//文件接收端
int main(int argc,char **argv)
{   
    if(argc!=4)
    {
        printf("./app  <IP地址> <端口号> <名称>\n");
        return 0;
    }
    int sockfd;
    //忽略 SIGPIPE 信号--方式服务器向无效的套接字写数据导致进程退出
    signal(SIGPIPE,SIG_IGN); 

    /*1. 创建socket套接字*/
    sockfd=socket(AF_INET,SOCK_STREAM,0);
    /*2. 连接服务器*/
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_port=htons(atoi(argv[2])); // 端口号0~65535
    addr.sin_addr.s_addr=inet_addr(argv[1]); //IP地址
    if(connect(sockfd,(const struct sockaddr *)&addr,sizeof(struct sockaddr_in))!=0)
    {
        printf("客户端:服务器连接失败.\n");
        return 0;
    }

    /*3. 发送消息表示上线*/
    msg_data.type=1;
    strcpy(msg_data.name,argv[3]);
    write(sockfd,&msg_data,sizeof(struct MSG_DATA));

    int cnt;
    int i;
    //创建专用文件描述符
    epollfd = epoll_create(10);
    //添加要监听的文件描述符
    ev.events = EPOLLIN;
    ev.data.fd = sockfd;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &ev);

    ev.events = EPOLLIN;
    ev.data.fd = 0; //标准输入文件描述符
    epoll_ctl(epollfd, EPOLL_CTL_ADD, 0, &ev);

    while(1)
    {
        //监听事件
        nfds=epoll_wait(epollfd,events,MAX_EVENTS,-1);
        if(nfds)
        {
            for(i=0;i<nfds;i++)
            {
                if(events[i].data.fd==sockfd) //判断收到服务器的消息
                {
                    cnt=read(sockfd,&msg_data,sizeof(struct MSG_DATA));
                    if(cnt<=0) //判断服务器是否断开了连接
                    {
                        printf("服务器已经退出.\n");
                        goto SERVER_ERROR;
                    }
                    else if(cnt>0)
                    {
                        if(msg_data.type==0)
                        {
                            printf("%s:%s  在线人数:%d\n",msg_data.name,msg_data.buff,msg_data.number);
                        }
                        else if(msg_data.type==1)
                        {
                            printf("%s 好友上线. 在线人数:%d\n",msg_data.name,msg_data.number);
                        }
                        else if(msg_data.type==2)
                        {
                            printf("%s 好友下线. 在线人数:%d\n",msg_data.name,msg_data.number);
                        }
                    }
                }
                else if(events[i].data.fd==0) //表示键盘上有数据输入
                {
                    gets(msg_data.buff); //读取键盘上的消息
                    msg_data.type=0; //表示正常消息
                    strcpy(msg_data.name,argv[3]); //名称
                    write(sockfd,&msg_data,sizeof(struct MSG_DATA));
                }
            }
        }
    }
SERVER_ERROR:
    close(sockfd);
    return 0;
}
目录
相关文章
|
1月前
|
网络协议 安全 Linux
Linux C/C++之IO多路复用(select)
这篇文章主要介绍了TCP的三次握手和四次挥手过程,TCP与UDP的区别,以及如何使用select函数实现IO多路复用,包括服务器监听多个客户端连接和简单聊天室场景的应用示例。
91 0
|
11天前
|
缓存 Linux 开发者
Linux内核中的并发控制机制:深入理解与应用####
【10月更文挑战第21天】 本文旨在为读者提供一个全面的指南,探讨Linux操作系统中用于实现多线程和进程间同步的关键技术——并发控制机制。通过剖析互斥锁、自旋锁、读写锁等核心概念及其在实际场景中的应用,本文将帮助开发者更好地理解和运用这些工具来构建高效且稳定的应用程序。 ####
29 5
|
13天前
|
Linux 数据库
Linux内核中的锁机制:保障并发操作的数据一致性####
【10月更文挑战第29天】 在多线程编程中,确保数据一致性和防止竞争条件是至关重要的。本文将深入探讨Linux操作系统中实现的几种关键锁机制,包括自旋锁、互斥锁和读写锁等。通过分析这些锁的设计原理和使用场景,帮助读者理解如何在实际应用中选择合适的锁机制以优化系统性能和稳定性。 ####
31 6
|
21天前
|
存储 JSON Java
细谈 Linux 中的多路复用epoll
大家好,我是 V 哥。`epoll` 是 Linux 中的一种高效多路复用机制,用于处理大量文件描述符(FD)事件。相比 `select` 和 `poll`,`epoll` 具有更高的性能和可扩展性,特别适用于高并发服务器。`epoll` 通过红黑树管理和就绪队列分离事件,实现高效的事件处理。本文介绍了 `epoll` 的核心数据结构、操作接口、触发模式以及优缺点,并通过 Java NIO 的 `Selector` 类展示了如何在高并发场景中使用多路复用。希望对大家有所帮助,欢迎关注威哥爱编程,一起学习进步。
|
20天前
|
消息中间件 存储 Linux
|
1月前
|
Linux C++
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
24 0
Linux C/C++之IO多路复用(poll,epoll)
|
3月前
|
存储 缓存 编译器
Linux源码阅读笔记06-RCU机制和内存优化屏障
Linux源码阅读笔记06-RCU机制和内存优化屏障
|
2月前
|
存储 监控 安全
探究Linux操作系统的进程管理机制及其优化策略
本文旨在深入探讨Linux操作系统中的进程管理机制,包括进程调度、内存管理以及I/O管理等核心内容。通过对这些关键组件的分析,我们将揭示它们如何共同工作以提供稳定、高效的计算环境,并讨论可能的优化策略。
48 0
|
4月前
|
缓存 监控 关系型数据库
深入理解Linux操作系统的内存管理机制
【7月更文挑战第11天】在数字时代的浪潮中,Linux操作系统凭借其强大的功能和灵活性,成为了服务器、云计算以及嵌入式系统等领域的首选平台。内存管理作为操作系统的核心组成部分,对于系统的性能和稳定性有着至关重要的影响。本文将深入探讨Linux内存管理的基本原理、关键技术以及性能优化策略,旨在为读者提供一个全面而深入的理解视角,帮助开发者和系统管理员更好地优化和管理Linux系统。