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

服务器类型与TCP并发服务器构建(SELECT)

一、服务器核心类型区分

服务器类型核心特点
单循环服务器同一时刻仅能处理一个客户端的任务
并发服务器同一时刻可处理多个客户端的任务

二、TCP并发服务器构建基础

1. TCP连接特性

TCP协议下,服务端与客户端需建立一对一连接,每个客户端(如cli1、cli2、cli3、cli4)会对应服务端的一个连接文件描述符(如connfd1、connfd2、connfd3、connfd4),通过该描述符实现数据交互。

三、TCP服务端并发模型(4种核心方案)

1. 多进程模型

  • 资源开销:进程资源开销大(进程间地址空间独立,切换成本高)。
  • 安全性:安全性高(进程间数据隔离,一个进程异常不影响其他进程)。
  • 代码
#include "head.h"#define SER_PORT 50000
#define SER_ID "192.168.0.164"int init_tcp_ser()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("socket error");return -1;}struct sockaddr_in seraddr;seraddr.sin_family = AF_INET;seraddr.sin_port = htons(50000);seraddr.sin_addr.s_addr = inet_addr("192.168.0.164");int ret = bind(sockfd, (struct  sockaddr *)&seraddr, sizeof(seraddr));if(ret < 0){perror("bind error");return -1;   }ret = listen(sockfd, 10);if(ret < 0){perror("listen error");return -1;}return sockfd;
}void wait_handler(int signo)
{wait(NULL);
}int main(int argc, char const *argv[])
{signal(SIGCHLD_wait_handler);struct sockaddr_in cliaddr;socklen_t clilen = sizeof(cliaddr);int sockfd = init_tcp_ser();if(sockfd < 0){return -1;}while(1){int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);if(connfd < 0){perror("accept error");close (sockfd);return -1;}pid_t pid =fork();if(pid > 0){}else if(pid == 0){char buff[1024] = {0};while (1){memset(buff, 0, sizeof(buff));int cnt = recv(connfd, buff, sizeof(buff), 0);if(cnt < 0){perror("recv error");break;}else if(cnt == 0){printf("[ %s : %d ] : cli offline\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));break;}printf("[ %s : %d ] : buff = %s\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port), buff);strcat(buff, "------> ok");cnt = send(connfd, buff, sizeof(buff), 0);if(cnt < 0){perror("send error");break;}}close(connfd);}}close(sockfd);return 0;
}

2. 多线程模型

  • 资源开销:相对进程开销小(线程共享进程地址空间,切换成本低)。
  • 并发能力:相同资源环境下,并发处理客户端的数量比多进程模型更优。
  • 代码
#include "head.h"#define SER_PORT 50000
#define SER_ID "192.168.0.164"int init_tcp_ser()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("socket error");return -1;}//允许绑定处于TIME_WAIT状态的地址,避免端口占用问题:int optval = 1;setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));struct sockaddr_in seraddr;seraddr.sin_family = AF_INET;seraddr.sin_port = htons(50000);seraddr.sin_addr.s_addr = inet_addr("192.168.0.164");int ret = bind(sockfd, (struct  sockaddr *)&seraddr, sizeof(seraddr));if(ret < 0){perror("bind error");return -1;   }ret = listen(sockfd, 10);if(ret < 0){perror("listen error");return -1;}return sockfd;
}void *task(void *arg)
{int connfd = *(int *)arg;char buff[1024] = {0};while (1){memset(buff, 0, sizeof(buff));int cnt = recv(connfd, buff, sizeof(buff), 0);if(cnt < 0){perror("recv error");break;}else if(cnt == 0){printf(" cli offline\n");break;}printf("buff = %s\n", buff);strcat(buff, "------> ok");cnt = send(connfd, buff, sizeof(buff), 0);if(cnt < 0){perror("send error");break;}}close(connfd);return 0;
}int main(int argc, char const *argv[])
{struct sockaddr_in cliaddr;socklen_t clilen = sizeof(cliaddr);int sockfd = init_tcp_ser();if(sockfd < 0){return -1;}while(1){int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);if(connfd < 0){perror("accept error");close (sockfd);return -1;}pthread_t tid;int ret = pthread_create(&tid, NULL, task, &connfd);if(ret != 0){perror("pthread_create error");return -1;}pthread_detach(tid);}close(sockfd);return 0;
}

3. 线程池模型

  • 设计目的:解决多线程/多进程模型中“频繁创建、销毁线程(或进程)”带来的时间消耗问题。
  • 核心原理:基于生产者-消费者编程模型,结合任务队列实现的多线程框架(提前创建固定数量线程,循环处理队列中的任务,避免频繁启停线程)。

4. IO多路复用模型

  • 核心逻辑:将“IO操作”与“文件描述符(fd)”关联,复用一个进程即可实现对“多个文件描述符读写状态”的同时监测。
  • 关键优势:无需创建新进程或线程,仅用单个进程完成多客户端IO事件处理。
  • 关联概念:阻塞IO模式下,多个任务会呈现“同步执行”效果(一个任务阻塞时,其他任务需等待)。
  • 实现方式:支持3种主流方案,selectpollepoll

四、select实现IO多路复用(详细流程与函数)

1. 核心实现步骤(5步)

  1. 创建文件描述符集合:定义fd_set类型的集合,用于存放需监测的文件描述符。
  2. 添加关注的文件描述符:通过FD_SET()宏,将待监测的fd加入集合。
  3. 内核监测事件:调用select()函数,将fd集合传递给内核,由内核监测fd的读写/异常事件。
  4. 解除阻塞并获取结果:当内核监测到目标事件(如fd可读),select()解除阻塞,应用层获取事件结果。
  5. 处理任务:根据select()返回的结果,对触发事件的fd进行针对性处理(如recv()读取数据)。

2. 关键宏函数(操作fd集合)

宏函数原型功能描述
void FD_CLR(int fd, fd_set *set)将指定fd从fd集合中移除
int FD_ISSET(int fd, fd_set *set)判断指定fd是否在fd集合中(存在则返回非0)
void FD_SET(int fd, fd_set *set)将指定fd添加到fd集合中
void FD_ZERO(fd_set *set)清空fd集合(初始化集合时必须调用)

3. select函数详情

(1)函数原型
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
(2)功能

将fd集合传递给内核,等待内核返回“触发事件的fd结果”,实现对多fd的事件监测。

(3)参数说明
参数名含义
nfds需关注的“最大文件描述符 + 1”(内核通过该值确定监测范围)
readfds监测“读事件”的fd集合(如客户端发送数据,fd变为可读)
writefds监测“写事件”的fd集合(如fd可写入数据,无缓冲区满问题)
exceptfds监测“异常事件”的fd集合(如fd发生错误)
timeout

超时时间设置:

 <br>- NULL:无超时,select()一直阻塞等待事件;

<br>- 非NULL:超时后若无事件,select()解除阻塞

(4)返回值
  • 成功:返回内核监测到的“触发事件的fd个数”。
  • 失败:返回-1(如参数错误、系统调用异常)。
  • 超时:返回0(超时时间到达,且无任何事件触发)。
(5)代码
#include "head.h"#define SER_PORT 50000
#define SER_ID "192.168.0.164"int init_tcp_ser()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("socket error");return -1;}struct sockaddr_in seraddr;seraddr.sin_family = AF_INET;seraddr.sin_port = htons(SER_PORT);seraddr.sin_addr.s_addr = inet_addr(SER_ID);int ret = bind(sockfd, (struct  sockaddr *)&seraddr, sizeof(seraddr));if(ret < 0){perror("bind error");return -1;   }ret = listen(sockfd, 10);if(ret < 0){perror("listen error");return -1;}return sockfd;
}int main(int argc, char const *argv[])
{char buff[1024] = {0};struct sockaddr_in cliaddr;socklen_t clilen = sizeof(cliaddr);int sockfd = init_tcp_ser();if(sockfd < 0){return -1;}// 创建文件描述符集合fd_set rdfds;fd_set rdfdstmp;// 清零FD_ZERO(&rdfds);// 添加关注的文件描述符到集合FD_SET(sockfd, &rdfds);int maxfd = sockfd;while(1){rdfdstmp = rdfds;// 传递集合到内核,并等待返回监测结果int cnt = select(maxfd + 1, &rdfdstmp, NULL, NULL, NULL);if(cnt < 0){perror("select error");return -1;}// 是否有监听套接字事件到达 ----》三次握手已完成,可以acceptif(FD_ISSET(sockfd, &rdfdstmp)){int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);if(connfd < 0){perror("accept error");close (sockfd);return -1;}FD_SET(connfd, &rdfds);    // 加到原始的集合maxfd = maxfd > connfd ? maxfd : connfd;}// 是否有通信套接字事件到达for(int i = sockfd + 1; i <= maxfd; i++){if(FD_ISSET(i, &rdfdstmp))  // 判断i{memset(buff, 0, sizeof(buff));// 接收 ssize_t cnt = recv(i, buff, sizeof(buff), 0);if(cnt < 0){perror("recv error");FD_CLR(i, &rdfds);      // i 错误 ,从集合中删除iclose(i);              // 先删除再关闭continue;               // 不能使用 break   return}else if(cnt == 0){FD_CLR(i, &rdfds);close(i);continue;}printf("%s\n", buff);strcat(buff , "-----> ok");cnt = send(i, buff, sizeof(buff), 0);if(cnt < 0){perror("send error");FD_CLR(i, &rdfds);close(i);continue;}}}}close(sockfd);return 0;
}

http://www.xdnf.cn/news/19059.html

相关文章:

  • 设计模式:桥接模式(Bridge Pattern)
  • 《Linux内存管理:实验驱动的深度探索》【附录】【实验环境搭建 7】【使用buildroot方式构建文件系统】
  • 【开发便利】让远程Linux服务器能够访问内网git仓库
  • 链表-25.k个一组翻转链表-力扣(LeetCode)
  • 深入解析 Flink Function
  • Vue将内容生成为二维码,并将所有二维码下载为图片,同时支持批量下载(下载为ZIP),含解决一次性生成过多时页面崩溃解决办法
  • TCP 并发服务器构建
  • 智芯MCU 勘误文档问题解析
  • 【Java知识】Java线程相关对象全面解析与最佳实践
  • 阿里云——应用交付与负载均衡
  • 数据对话的“通用语法”:SQL与KingbaseES的智能处理艺术
  • 从感知机到大模型:神经网络的全景解析与实践指南
  • ES01-环境安装
  • 盛大启幕!融智兴科技亮相 IOTE 2025 深圳国际物联网展
  • SegEarth-R1: Geospatial Pixel Reasoning via Large Language Model
  • 稀土:从“稀有”到“命脉”的科技核心
  • LeetCode算法日记 - Day 23: 外观数列、数青蛙
  • LeetCode - 155. 最小栈
  • 8.28 模拟
  • rust语言(1.88.0)sqlite数据库rusqlite库(0.37.0)学习笔记
  • 蘑兔音乐:帮你把灵感落地
  • 【新版发布】Apache DolphinScheduler 3.3.1 正式上线:更稳、更快、更安全!
  • 【Django + Pure Admin】基于Django+Vue3的前后端分离管理系统框架设计
  • 预处理详解
  • 【Spring Cloud 微服务】5.架构的智慧枢纽:深度剖析 Nacos 注册中心
  • 《Vuejs设计与实现》第 17 章(编译优化)
  • JMeter 5.3 性能测试:文件下载脚本编写与导出文件接收完整指南
  • 数据结构:堆排序 (Heap Sort)
  • spire.doc在word中生成公式
  • 设计模式理解