Linux网络编程(多路IO复用poll)

简介: Linux网络编程(多路IO复用poll)

前言

上篇文章我们讲解了使用select进行IO复用,这篇文章我们来讲解使用poll函数来进行多路IO复用。

一、poll函数讲解

poll() 函数是在网络编程中常用的一个系统调用函数,用于监视多个文件描述符的状态,以确定是否有文件描述符准备好进行读取、写入或出现异常。

以下是 poll() 函数的基本用法:

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

fds 参数是一个指向 pollfd 结构体数组的指针,每个结构体描述了一个文件描述符及其关注的事件。

nfds 参数是 fds 数组中元素的数量。

timeout 参数是超时时间,以毫秒为单位。指定 timeout 的值可以控制 poll() 函数的阻塞行为。

pollfd 结构体的定义如下:

struct pollfd {
    int fd;       // 文件描述符
    short events; // 感兴趣的事件
    short revents; // 实际发生的事件
};

fd 是被监视的文件描述符。

events 是要监视的事件的掩码,可以是以下值之一或它们的组合:

POLLIN:有数据可读。

POLLOUT:可写入数据。

POLLERR:发生错误。

POLLHUP:连接关闭。

POLLNVAL:文件描述符非法。

revents 是 poll() 函数填充的实际发生的事件。

poll() 函数的返回值表示有几个文件描述符准备好了,即满足所关心的事件。返回 值的三种情况如下:

返回值大于 0:表示准备好的文件描述符的数量。

返回值等于 0:表示在指定的超时时间内没有文件描述符准备好。

返回值小于 0:表示执行发生错误。

使用 poll() 函数的步骤如下:

1.设置 pollfd 数组中每个文件描述符的 fd 和 events 字段。

2.调用 poll() 函数,并传递 pollfd 数组、元素数量和超时时间。

3.检查返回值,根据 revents 字段判断具体发生的事件。

二、使用poll函数完成并发服务器

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <poll.h>
#define MAX_CLIENT  1024//最大可连接客户端的数量
int main()
{
    int server = 0;
    struct sockaddr_in saddr = {0};
    int client = 0;
    struct sockaddr_in caddr = {0};
    socklen_t asize = 0;
    int len = 0;
    char buf[32] = {0};
    int maxfd;
    int ret = 0;
    int i = 0;
    server = socket(PF_INET, SOCK_STREAM, 0);
    if( server == -1 )
    {
        printf("server socket error\n");
        return -1;
    }
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = htonl(INADDR_ANY);
    saddr.sin_port = htons(8888);
    if( bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == -1 )
    {
        printf("server bind error\n");
        return -1;
    }
    if( listen(server, 128) == -1 )
    {
        printf("server listen error\n");
        return -1;
    }
    printf("server start success\n");
  /*将fds中的fd全部置为-1*/
    struct pollfd fds[MAX_CLIENT];
    for(i = 0; i < MAX_CLIENT; i++)
    {
        fds[0].fd = -1;
    }
    /*将服务端套接字添加到fds数组中*/
    fds[0].fd = server;
    fds[0].events = POLLIN;
    while( 1 )
    {        
        ret = poll(fds, MAX_CLIENT, -1);
        if(ret < 0)
        {
            printf("poll err\n");
            return -1;
        }
        if(fds[0].revents & POLLIN)
        {
            /*有客户端连接上来了*/
            asize = sizeof(caddr);  
            client = accept(server, (struct sockaddr*)&caddr, &asize);
            printf("client is connect\n");
            /*将新连接添加到fds数组中*/
            for(i = 1; i < MAX_CLIENT; i++)
            {
                if(fds[i].fd == -1)
                {
                    fds[i].fd = client;
                    fds[i].events = POLLIN;
                    break;
                }
            }            
        }
        /*遍历现有连接进行读取和处理数据*/
        for(i = 1; i < MAX_CLIENT; i++)
        {            
            int clientfd = fds[i].fd;
            if(clientfd > 0 && (fds[i].revents & POLLIN))
            {
                printf("process data\n");
                len = read(clientfd, buf, 1024);
                if(len == 0)
                {
                    /*客户端断开连接,关闭客户端文件描述符*/
                    close(clientfd);
                    fds[i].fd = -1;
                    printf("client is disconnect\n");
                }
                else
                {
                    printf("read buf : %s\n", buf);
                    write(clientfd, buf, len);
                }
            }
        }
    }
    close(server);
    return 0;
}

三、poll的优点缺点

优点:

1.简单易用:相对于低级别的系统调用如 select,poll 提供了更简单的 API,更易于使用和理解。

2.没有文件描述符数量限制:poll 没有预定义的文件描述符数量限制,可以适应更大规模的并发连接。

3.支持文件描述符数组:相对于 select 的位图方式,poll 使用了文件描述符数组,可以更方便地进行管理和操作。

4.高效:poll 采用轮询的方式监视文件描述符的状态变化,只有活动的文件描述符才会返回,减少了无用的轮询过程,提高了效率。

5.支持非阻塞IO:与 select 类似,poll 也支持非阻塞IO模式,可以在等待期间继续处理其他任务。

缺点:

1.每次调用都需要遍历整个文件描述符数组:即使只有少数文件描述符活跃,poll 在每次调用时都需要遍历整个文件描述符数组,这会带来性能上的开销。

2.没有提供超时精度控制:poll 的超时参数是以毫秒为单位的,无法提供更高的精度,因此在需要更精确超时的情况下,不太适用。

3.不可移植性:poll 是一个相对较新的系统调用,不是所有的操作系统都提供该接口,因此在编写跨平台代码时,需要考虑兼容性。

4.没有对信号处理的支持:与 select 不同,poll 不提供对信号处理的支持,因此无法直接处理信号事件。

总结

本篇文章主要讲解到了poll函数的使用方法并且使用poll实现了一个并发服务器,这个大家可以结合上篇文章的select函数进行对比思考。


相关文章
|
7月前
|
存储 网络协议 算法
从HPACK到多路复用,揭秘HTTP/2如何终结网络拥堵
HTTP/2通过HPACK压缩头部冗余信息,提升传输效率;并利用多路复用技术,在单个TCP连接上并行处理多个请求,避免队头阻塞,显著提升性能。同时支持服务器推送和流优先级设置,优化资源加载体验。
428 7
|
Ubuntu 网络协议 Unix
02理解网络IO:实现服务与客户端通信
网络IO指客户端与服务端通过网络进行数据收发的过程,常见于微信、QQ等应用。本文详解如何用C语言实现一个支持多客户端连接的TCP服务端,涉及socket编程、线程处理及通信流程,并分析“一消息一线程”模式的优缺点。
478 0
|
10月前
|
Linux C语言 网络架构
Linux的基础IO内容补充-FILE
而当我们将运行结果重定向到log.txt文件时,数据的刷新策略就变为了全缓冲,此时我们使用printf和fwrite函数打印的数据都打印到了C语言自带的缓冲区当中,之后当我们使用fork函数创建子进程时,由于进程间具有独立性,而之后当父进程或是子进程对要刷新缓冲区内容时,本质就是对父子进程共享的数据进行了修改,此时就需要对数据进行写时拷贝,至此缓冲区当中的数据就变成了两份,一份父进程的,一份子进程的,所以重定向到log.txt文件当中printf和fwrite函数打印的数据就有两份。此时我们就可以知道,
242 0
|
10月前
|
存储 Linux Shell
Linux的基础IO
那么,这里我们温习一下操作系统的概念我们在Linux平台下运行C代码时,C库函数就是对Linux系统调用接口进行的封装,在Windows平台下运行C代码时,C库函数就是对Windows系统调用接口进行的封装,这样做使得语言有了跨平台性,也方便进行二次开发。这就是因为在根本上操作系统确实像银行一样,并不完全信任用户程序,因为直接开放底层资源(如内存、磁盘、硬件访问权限)给用户程序会带来巨大的风险。所以就向银行一样他的服务是由工作人员隔着一层玻璃,然后对顾客进行服务的。
138 0
|
11月前
|
监控 应用服务中间件 Linux
掌握并发模型:深度揭露网络IO复用并发模型的原理。
总结,网络 I/O 复用并发模型通过实现非阻塞 I/O、引入 I/O 复用技术如 select、poll 和 epoll,以及采用 Reactor 模式等技巧,为多任务并发提供了有效的解决方案。这样的模型有效提高了系统资源利用率,以及保证了并发任务的高效执行。在现实中,这种模型在许多网络应用程序和分布式系统中都取得了很好的应用成果。
294 35
|
存储 网络协议 安全
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
568 23
|
机器学习/深度学习 API Python
Python 高级编程与实战:深入理解网络编程与异步IO
在前几篇文章中,我们探讨了 Python 的基础语法、面向对象编程、函数式编程、元编程、性能优化、调试技巧、数据科学、机器学习、Web 开发和 API 设计。本文将深入探讨 Python 在网络编程和异步IO中的应用,并通过实战项目帮助你掌握这些技术。
|
安全 Java 网络安全
Java网络编程:高级应用与安全性探讨
Java网络编程:高级应用与安全性探讨
|
网络协议 安全 Java
Java中的网络编程:Socket编程详解
Java中的网络编程:Socket编程详解
|
网络协议 Java
深入理解Java中的网络编程模型
深入理解Java中的网络编程模型