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

《Linux 网络编程四:TCP 并发服务器:构建模式、原理及关键技术(以select )》

一、TCP 并发服务器相关知识梳理

单循环服务器与并发服务器概念

  1. 单循环服务器
    服务端在同一时刻仅能处理一个客户端的任务。
  2. 并发服务器
    服务端在同一时刻能够处理多个客户端的任务。

TCP 并发服务器构建方式

        多进程方式

                资源开销大,但安全性高。

        多线程方式

                相较于进程,线程资源开销小。在相同资源环境下,并发量比进程大。

        线程池方式

                解决多线程或多进程模型中,服务器运行过程里频繁创建和销毁线程(进程)所带来的时间消耗问题。基于生产者和消费者编程模型,以及任务队列等,构建一套多线程框架。

        IO 多路复用方式

                原理:对多个文件描述符的读写操作可复用一个进程。在不创建新进程和线程的前提下,利用一个进程实现对多个文件读写的同时监测。例如,阻塞 IO 模式下,多个任务呈现同步效果。

IO 多路复用具体实现

select
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
  • 创建文件描述符集合 fd_set
  • 使用 FD_SET() 函数添加关注的文件描述符到集合。
  • 通过 select() 函数将集合表传递给内核,内核开始监测事件。
  • 当内核监测到事件时,应用层的 select 将解除阻塞,并获取相关事件结果。
  • 根据 select 返回的结果进行不同的任务处理。
相关函数
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);
select 函数详情
  • 功能:传递文件描述符结合表给内核并等待获取事件结果。
  • 参数
    • nfds:关注的最大文件描述符 +1。
    • readfds:读事件的文件描述符集合。
    • writefds:写事件的文件描述符集合。
    • exceptfds:其他事件的文件描述符集合。
    • timeout:设置超时时间,若为 NULL,则无超时。
  • 返回值
    • 成功:返回事件个数。
    • 失败:返回 -1。
    • 0:超时且无事件发生。
poll 和 epoll
  • poll:基于事件驱动,支持更多文件描述符。
  • epoll:高效管理大量连接,适用于高并发场景。

构建方式对比

构建方式资源开销安全性并发量解决问题
多进程较低/
多线程较高/
线程池较高减少创建销毁开销
IO 多路复用依赖场景单进程监测多文件

二、代码

head.h

#ifndef __HEAD_H__
#define __HEAD_H__#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <pthread.h>
#include <sys/select.h>/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>#endif
1.并发服务器(进程)
#include "head.h"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(50001);seraddr.sin_addr.s_addr = inet_addr("192.168.0.192");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){return -1;}pid_t pid = fork();if(pid > 0){}else if(0 == pid){char buff[1024] = {0};while(1){memset(buff, 0, sizeof(buff));size_t cnt = recv(connfd, buff, sizeof(buff), 0);if(cnt < 0){return -1;}else if(0 == cnt){printf("[%s : %d] : offline\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));break;}printf("[%s : %d] : %s\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port), buff);strcat(buff, "-------ok");cnt = send(connfd, buff, strlen(buff), 0);if(cnt < 0){return -1;}}close(connfd);}}close(sockfd);return 0;
}
2.并发服务器(线程)
#include "head.h"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(50001);seraddr.sin_addr.s_addr = inet_addr("192.168.0.192");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));size_t cnt = recv(connfd, buff, sizeof(buff), 0);if(cnt < 0){break;}else if(0 == cnt){printf("offline\n");break;}printf("%s\n",  buff);strcat(buff, "-------ok");cnt = send(connfd, buff, strlen(buff), 0);if(cnt < 0){break;}}close(connfd);
}int main(int argc, char const *argv[])
{  pthread_t tid; 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){return -1;}pthread_create(&tid, NULL, task, &connfd);pthread_detach(tid);}close(sockfd);return 0;
}
3.select实现并发服务器
#include "head.h"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(50001);seraddr.sin_addr.s_addr = inet_addr("192.168.0.192");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();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;}if (FD_ISSET(sockfd, &rdfdstmp)){int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);if (connfd < 0){perror("accept error");return -1;}FD_SET(connfd, &rdfds);maxfd = maxfd > connfd ? maxfd : connfd;}for (int i = sockfd+1; i <= maxfd; i++){if (FD_ISSET(i, &rdfdstmp)){memset(buff, 0, sizeof(buff));ssize_t cnt = recv(i, buff, sizeof(buff), 0);if (cnt < 0){perror("recv error");FD_CLR(i, &rdfds);close(i);continue;}else if (0 == cnt){FD_CLR(i, &rdfds);close(i);continue;}printf("%s\n", buff);strcat(buff, "--->ok");cnt = send(i, buff, strlen(buff), 0);if (cnt < 0){perror("send error");FD_CLR(i, &rdfds);close(i);continue;}}}}close(sockfd);return 0;
}

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

相关文章:

  • Linux 软件编程(十二)网络编程:TCP 并发服务器构建与 IO 多路复用
  • PPT处理控件Aspose.Slides教程:在.NET中开发SVG到EMF的转换器
  • 爬虫基础学习 - Xpath
  • 设计模式与设计原则简介——及其设计模式学习方法
  • 优选算法-常见位运算总结
  • uniapp中 ios端 scroll-view 组件内部子元素z-index失效问题
  • 基于 Node.js 的淘宝 API 接口开发:快速构建异步数据采集服务
  • 汽车电气系统的发展演进为测试带来了哪些影响?
  • DeFi协议Lombard能突破比特币生态原生叙事困境吗?
  • 图表可视化地理趋势-Telerik WPF Chart
  • 【Day 35】Linux-主从复制的维护
  • (LeetCode 面试经典 150 题 ) 637. 二叉树的层平均值(深度优先搜索dfs)
  • 亚马逊广告关键词排名提升的五大核心策略解析
  • java简单ssm(spring+springmvc+mybatis)框架结构demo
  • 大模型重构建筑“能耗基因“:企业如何用物联中台打响能源革命?
  • 手写MyBatis第36弹:MyBatis执行流程中SQL命令类型解析
  • 登录业务——密码重置与强制修改初始密码实现思路
  • 【微信小程序】分别解决H5的跨域代理问题 和小程序正常不需要代理问题
  • Coze用户账号设置修改用户名-后端源码
  • map|math
  • 腾讯位置商业授权微信小程序路线规划
  • 【开源工具】基于Flask与Socket.IO的跨平台屏幕监控系统实战(附完整源码)
  • 前端性能优化:从指标监控到全链路落地(2024最新实战指南)
  • 论文阅读:Gorilla: Large Language Model Connected with Massive APIs
  • 深度学习入门:神经网络基础知识
  • lesson47:Linux常用软件使用指南:远程连接、远程拷贝、Vim与Nginx
  • VESA时序检测模块设计verilog实现
  • Ubuntu 24 Server 如何设置无线网络
  • imx6ull-驱动开发篇45——Linux 下 SPI 驱动框架简介
  • d435i相机读取镜头内参和相对之间的外参