一个基于 poll 实现的多路复用服务器程序,相比之前的 select 版本,增加了对触摸屏设备的支持

发布于:2025-08-11 ⋅ 阅读:(13) ⋅ 点赞:(0)
/*4 - 使用poll实现多路复用 */
#include <stdio.h>          // 标准输入输出函数库
#include <stdlib.h>         // 标准库函数,包含exit等
#include <string.h>         // 字符串处理函数
#include <unistd.h>         // Unix标准函数,包含read, write等
#include <sys/socket.h>     // 套接字相关函数
#include <sys/types.h>      // 基本系统数据类型
#include <poll.h>           // poll相关函数和结构体
#include <fcntl.h>          // 文件控制函数
#include <sys/stat.h>       // 文件状态相关定义
#include <netinet/in.h>     // 互联网地址族
#include <arpa/inet.h>      // 提供IP地址转换函数
#include <signal.h>         // 信号处理函数
#include <linux/input.h>    // Linux输入设备相关定义(如触摸屏)

#define FD_CNT 1000         // 定义最大文件描述符数量

int main(int argc,char *argv[])
{
    if(argc!=2){                    // 检查命令行参数数量是否正确
        printf("Usage:%s port\n",argv[0]);  // 输出程序使用方法:程序名 端口号
        exit(0);                    // 正常退出
    }

    // 打开触摸屏设备文件(设备路径可能因系统而异)
    int tcfd = open("/dev/input/event6",O_RDWR);  // 以读写方式打开触摸屏设备
    if(tcfd==-1){                    // 检查设备打开是否成功
        perror("open");              // 输出错误信息
        exit(-1);                    // 异常退出
    }

    //1.创建socket
    int sockfd = socket(AF_INET,SOCK_STREAM,0);  // 创建TCP套接字,AF_INET表示IPv4,SOCK_STREAM表示TCP
    if(sockfd==-1){                  // 检查socket创建是否成功
        perror("socket");            // 输出错误信息
        exit(-1);                    // 异常退出
    }

    //允许地址复用
    int optval = 1;                  // 选项值,1表示启用
    // 设置套接字选项,允许地址复用,避免端口占用问题
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,&optval, sizeof(optval));

    //2.绑定ip和端口号(自己)
    struct sockaddr_in addr;         // 定义IPv4地址结构体
    addr.sin_family = AF_INET;       // 设置协议族为IPv4
    addr.sin_port = htons(atoi(argv[1]));  // 将命令行参数的端口号转换为网络字节序
    addr.sin_addr.s_addr = INADDR_ANY;  // 绑定到所有可用的网络接口
    // 绑定套接字到指定地址和端口
    int res = bind(sockfd,(struct sockaddr *)&addr,sizeof(addr));
    if(res==-1){                     // 检查绑定是否成功
        perror("bind");              // 输出错误信息
        exit(-1);                    // 异常退出
    }

    //3.监听
    res = listen(sockfd,10);         // 开始监听连接,最大等待队列长度为10
    if(res==-1){                     // 检查监听是否成功
        perror("listen");            // 输出错误信息
        exit(-1);                    // 异常退出
    }

    //准备pollfd数组,用于存储需要监控的文件描述符
    struct pollfd *pfd = malloc(FD_CNT*sizeof(struct pollfd));  // 动态分配数组
    if(!pfd){                        // 检查内存分配是否成功
        perror("malloc");            // 输出错误信息
        exit(-1);                    // 异常退出
    }
    
    struct input_event evt;          // 定义输入事件结构体(用于触摸屏)
    int maxfd,i;                     // maxfd存储最大文件描述符,i用于循环
    char msg[1024] = {};             // 用于存储接收和发送的消息
    // 初始化pollfd数组,将所有文件描述符设为-1(表示未使用)
    for(i=0;i<FD_CNT;i++){
        pfd[i].fd = -1;
    }

    //添加要监控的描述符及其事件
    pfd[0].fd = 0;                   // 标准输入(键盘)的文件描述符
    pfd[0].events = POLLIN;          // 监控读事件(有输入)
    pfd[1].fd = sockfd;              // 服务器监听套接字
    pfd[1].events = POLLIN;          // 监控读事件(有新连接)
    pfd[2].fd = tcfd;                // 触摸屏设备文件描述符
    pfd[2].events = POLLIN;          // 监控读事件(有触摸输入)
    

    //4.等待事件发生(客户端连接、消息或输入)
    while(1){                        // 主循环,持续运行服务器
        //调用poll函数,等待事件发生
        // 参数:监控数组、数组长度、超时时间(-100毫秒,即100ms超时)
        if(poll(pfd,FD_CNT,-100)<=0){
            printf("timeout!\n");    // 超时或出错时输出信息
            continue;                // 继续下一次循环
        }

        //处理活动的描述符(有事件发生的描述符)
        //有键盘输入(标准输入有事件)
        if(pfd[0].revents & POLLIN){ // 检查标准输入是否有读事件
            memset(msg,0,sizeof(msg));  // 清空消息缓冲区
            read(0,msg,sizeof(msg));    // 从标准输入读取数据
            printf("输入的内容为:%s\n",msg);  // 输出读取到的内容
        }

        
        //1.有客户端连接请求(监听套接字有事件)
        if(pfd[1].revents & POLLIN){ // 检查监听套接字是否有读事件(新连接)
            struct sockaddr_in cilent_addr;  // 存储客户端地址
            socklen_t len = sizeof(cilent_addr);  // 地址长度

            // 接受客户端连接,返回新的套接字描述符
            int newfd = accept(sockfd,(struct sockaddr *)&cilent_addr,&len);
            if(newfd==-1){            // 检查连接是否成功
                perror("accept");     // 输出错误信息
                exit(-1);             // 异常退出
            }
            
            //将新连接的客户端描述符添加到pfd数组中
            for(i=3;i<FD_CNT;i++){    // 从索引3开始(0-2已用)寻找空位
                if(pfd[i].fd==-1)
                    break;
            }
            if(i<FD_CNT){             // 如果找到空位
                pfd[i].fd = newfd;    // 存储新的客户端套接字
                pfd[i].events = POLLIN;  // 监控该套接字的读事件
            }
            else{                     // 如果数组已满
                printf("服务器已达到连接数上线!\n");  // 输出提示信息
            }

            // 输出客户端IP地址,表示有新客户端连接
            printf("%s到此一游!\n",inet_ntoa(cilent_addr.sin_addr));
        }

        //有触摸屏输入(触摸屏设备有事件)
        if(pfd[2].revents & POLLIN){ // 检查触摸屏是否有读事件
            // 读取触摸屏事件
            res = read(tcfd,&evt,sizeof(evt));
            if(res<=0){               // 检查读取是否成功
                perror("read");       // 输出错误信息
                exit(-1);             // 异常退出
            }

            // 如果是绝对坐标事件(触摸位置相关)
            if(evt.type == EV_ABS){
                // 输出事件类型、代码和值(触摸坐标等信息)
                printf("type = %#x,code = %#x,value = %#x\n",evt.type,evt.code,evt.value);
            }
        }

        //2.有客户端发送消息(客户端套接字有事件)
        for(i=3;i<FD_CNT;i++){       // 遍历所有客户端套接字(从索引3开始)
            // 如果套接字有效且有读事件
            if(pfd[i].fd!=-1 && (pfd[i].revents & POLLIN)){
                res = read(pfd[i].fd,msg,sizeof(msg));  // 从客户端读取消息
                if(res<=0){           // 如果读取失败或客户端断开连接
                    close(pfd[i].fd); // 关闭套接字
                    pfd[i].fd = -1;   // 标记为未使用
                    continue;         // 继续下一次循环
                }
                //需要长时间通信可以开多任务
                printf("%s\n",msg);  // 输出接收到的消息
                if(strcmp(msg,"byebye")==0){  // 如果客户端发送"byebye"
                    close(pfd[i].fd); // 关闭套接字
                    pfd[i].fd = -1;   // 标记为未使用
                    continue;         // 继续下一次循环
                }
                //原路发回消息(回声功能)
                write(pfd[i].fd,msg,res);  // 将接收到的消息回发给客户端
            }
        }
    }

    close(sockfd);                   // 关闭监听套接字(实际不会执行到这里)
    close(tcfd);                     // 关闭触摸屏设备(实际不会执行到这里)
    return 0;                        // 程序正常退出(实际不会执行到这里)
}

程序功能说明:

这是一个基于 poll 实现的多路复用服务器程序,相比之前的 select 版本,增加了对触摸屏设备的支持,主要功能如下:

  1. 核心功能

    • 使用 poll 机制实现多路复用,同时监控多个 I/O 源
    • 作为 TCP 服务器,可接受多个客户端连接并提供回声服务(收到消息后原样返回)
    • 支持客户端发送 "byebye" 断开连接
  2. 监控的 I/O 源

    • 标准输入(键盘输入):读取并显示键盘输入内容
    • TCP 监听套接字:接受新的客户端连接
    • 触摸屏设备(/dev/input/event6):读取并显示触摸事件信息(坐标等)
    • 已连接的客户端套接字:接收客户端消息并回发
  3. 技术特点

    • 使用 poll 替代 select 进行多路复用,接口更简洁
    • 通过 struct pollfd 数组管理所有需要监控的文件描述符
    • 支持同时处理网络连接、键盘输入和硬件设备(触摸屏)输入
    • 采用动态数组管理客户端连接,最大支持 1000 个并发连接
  4. 适用场景

    • 演示 poll 函数的使用方法
    • 展示如何在单个进程中处理多种类型的 I/O 事件
    • 可作为嵌入式系统中处理网络和硬件输入的基础框架

该程序展示了 poll 相比 select 的一些优势,如无需每次调用都重置文件描述符集合,更适合监控大量文件描述符的场景。同时加入了触摸屏设备的支持,使其更贴近嵌入式系统的实际应用场景。


网站公告

今日签到

点亮在社区的每一天
去签到