当前位置: 首页 > news >正文

一个基于 select 实现的多路复用 TCP 服务器程序:

/*3 - 使用select实现多路复用 */
#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 <sys/select.h>     // select相关函数和宏
#include <fcntl.h>          // 文件控制函数
#include <sys/stat.h>       // 文件状态相关定义
#include <netinet/in.h>     // 互联网地址族
#include <arpa/inet.h>      // 提供IP地址转换函数
#include <signal.h>         // 信号处理函数#define FD_CNT 1000         // 定义最大文件描述符数量int main(int argc,char *argv[])
{if(argc!=2){                    // 检查命令行参数数量是否正确printf("Usage:%s port\n",argv[0]);  // 输出程序使用方法exit(0);                    // 退出程序}//1.创建socketint sockfd = socket(AF_INET,SOCK_STREAM,0);  // 创建TCP套接字,AF_INET表示IPv4,SOCK_STREAM表示TCPif(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;      // 设置协议族为IPv4addr.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);        // 开始监听连接,最大等待队列长度为10if(res==-1){                    // 检查监听是否成功perror("listen");           // 输出错误信息exit(-1);                   // 异常退出}//准备描述符集合fd_set set;                     // 定义文件描述符集合int *fds = malloc(FD_CNT*sizeof(int));  // 分配存储客户端套接字的数组if(!fds){                       // 检查内存分配是否成功perror("malloc");           // 输出错误信息exit(-1);                   // 异常退出}//设置初始值为-1,表示未使用memset(fds,-1,FD_CNT*sizeof(int));  // 将数组初始化为-1int maxfd,i;                    // maxfd存储最大文件描述符,i用于循环char msg[1024] = {};            // 用于存储接收和发送的消息//4.等待客户端连接while(1){                       // 主循环,持续运行服务器maxfd = sockfd;             // 初始化最大文件描述符为监听套接字FD_ZERO(&set);              // 清空文件描述符集合//监控标准输入FD_SET(0,&set);             // 将标准输入(文件描述符0)加入集合FD_SET(sockfd,&set);        // 将监听套接字加入集合//将所有已连接的客户端描述符加入setfor(i=0;i<FD_CNT;i++){      // 遍历客户端套接字数组if(fds[i]!=-1){         // 如果该位置有有效的套接字FD_SET(fds[i],&set);  // 将其加入文件描述符集合//记录最大描述符值if(fds[i]>maxfd)    // 更新最大文件描述符maxfd = fds[i];}}//调用select,等待活动的文件描述符struct timeval tv;          // 定义超时结构体tv.tv_sec = 2;              // 秒数,设置超时时间为2秒tv.tv_usec = 0;             // 微秒数if(select(maxfd+1,&set,NULL,NULL,&tv)<=0){  // 调用select,监控读事件printf("timeout!\n");   // 超时或出错时输出信息continue;               // 继续下一次循环}//处理活动的描述符//有键盘输入if(FD_ISSET(0,&set)){       // 检查标准输入是否有活动memset(msg,0,sizeof(msg));  // 清空消息缓冲区read(0,msg,sizeof(msg));    // 从标准输入读取数据printf("输入的内容为:%s\n",msg);  // 输出读取到的内容}//1.有客户端连接请求if(FD_ISSET(sockfd,&set)){  // 检查监听套接字是否有活动(新连接)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);           // 异常退出}//将新连接的客户端描述符添加到fds数组中for(i=0;i<FD_CNT;i++){  // 遍历数组寻找空位if(fds[i]==-1)break;}if(i<FD_CNT)            // 如果找到空位fds[i] = newfd;     // 存储新的客户端套接字else{                   // 如果数组已满printf("服务器已达到连接数上线!\n");  // 输出提示信息}printf("%s到此一游!\n",inet_ntoa(cilent_addr.sin_addr));  // 输出客户端IP地址}//2.有客户端发送消息for(i=0;i<FD_CNT;i++){      // 遍历所有客户端套接字if(fds[i]!=-1 && FD_ISSET(fds[i],&set)){  // 如果套接字有效且有活动res = read(fds[i],msg,sizeof(msg));  // 从客户端读取消息if(res<=0){         // 如果读取失败或客户端断开连接close(fds[i]);  // 关闭套接字fds[i] = -1;    // 标记为未使用continue;       // 继续下一次循环}//需要长时间通信可以开多任务printf("%s\n",msg); // 输出接收到的消息if(strcmp(msg,"byebye")==0){  // 如果客户端发送"byebye"close(fds[i]);  // 关闭套接字fds[i] = -1;    // 标记为未使用continue;       // 继续下一次循环}//原路发回消息write(fds[i],msg,res);  // 将接收到的消息回发给客户端}}}free(fds);                      // 释放动态分配的内存close(sockfd);                  // 关闭监听套接字return 0;                       // 程序正常退出
}

程序功能说明:

这是一个基于 select 实现的多路复用 TCP 服务器程序,主要功能如下:

  1. 创建 TCP 服务器

    • 创建套接字、设置地址复用、绑定端口、监听连接
  2. 多路复用处理

    • 使用 select 同时监控多个文件描述符(标准输入、监听套接字、客户端套接字)
    • 实现单进程处理多个客户端连接,避免了多进程 / 多线程的开销
  3. 支持的操作

    • 接受新的客户端连接
    • 读取客户端发送的消息并原样返回(回声服务器功能)
    • 处理标准输入(键盘输入)并显示
    • 当客户端发送 "byebye" 时断开连接
    • 客户端断开连接时清理资源
  4. 连接管理

    • 使用数组存储客户端套接字,最大支持 1000 个连接
    • 自动管理连接状态,释放断开的连接资源

这个程序展示了 select 函数的典型用法,通过多路复用机制,一个进程就能同时处理多个 I/O 事件,适合处理大量并发连接但每个连接的 I/O 操作不频繁的场景。

http://www.dtcms.com/a/318963.html

相关文章:

  • Opencv-管理图片
  • 计算机视觉--opencv(代码详细教程)
  • ansible-playbook之获取服务器IP存储到本地文件
  • Spring事务失效场景?
  • 光纤滑环 – 光纤旋转接头(FORJ)- 杭州驰宏科技
  • 科技云报到:热链路革命:阿卡 CRM 的 GTM 定位突围
  • 芯谷科技--高效噪声降低解决方案压缩扩展器D5015
  • 全球化2.0 | 泰国IT服务商携手云轴科技ZStack重塑云租赁新生态
  • 安全守护,温情陪伴 — 智慧养老产品上新
  • Element Plus实现分页查询
  • 码头岸电系统如何保障供电安全?安科瑞绝缘监测及故障定位方案解析
  • Rust爬虫与代理池技术解析
  • NAS技术在县级融媒体中心的架构设计与安全运维浅析
  • VSCode ssh一直在Setting up SSH Host xxx: Copying VS Code Server to host with scp等待
  • 支付宝小程序商城怎么搭?ZKmall开源商城教你借力蚂蚁生态做增长
  • 【Agent】ReAct:最经典的Agent设计框架
  • 【pytorch(06)】全连接神经网络:基本组件认知,线性层、激活函数、损失函数、优化器
  • Django 表单:深度解析与最佳实践
  • 高性能分布式通信框架:eCAL 介绍与应用
  • 解锁高效开发:AWS 前端 Web 与移动应用解决方案详解
  • 区块链技术原理(2) -数据结构
  • 云平台运维工具 ——AWS 原生工具
  • 告别Cursor!最强AI编程辅助Claude Code安装到使用全流程讲解
  • MySQL面试题及详细答案 155道(061-080)
  • 【图文教程】三步用Cpolar+JuiceSSH实现手机远程连接内网Linux虚拟机
  • 平台服务器被入侵,使用WAF能防范吗?
  • 机器学习——04 逻辑回归
  • LoRaWAN的网络拓扑
  • graalvm初探
  • 急危重症专科智能体”构建新一代急诊、手术与重症中心的AI医疗方向探析