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

select

TCP并发服务器构建:
TCP:
1. 建立连接,一对一

TCP服务端并发模型:

1.  多进程
进程资源开销大;安全性高

在指定的 IP 和端口上创建一个监听套接字。监听客户端连接请求,一旦有客户端连接,使用 fork() 创造一个子进程来处理该连接。子进程循环接收客户端发送的数据,打印收到的内容,并返回原数据拼接字符串“----ok”给客户端。如果客户端断开或发生错误,子进程退出并关闭连接。父进程在循环中继续接收新的连接。

#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>
#define SER_PORT  50000
#define SER_IP    "192.168.0.184"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_IP);int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));if (ret < 0){perror("bind error");return -1;}ret = listen(sockfd, 100);if (ret < 0){perror("listen error");return -1;}return sockfd;
}
int main(int argc, const char *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");return -1;}pid_t pid = fork();if(pid > 0){}else if(pid == 0){char buff[1024] = {0};while(1){memset(buff , 0 , sizeof(buff));size_t cnt = recv(connfd, buff, sizeof(buff), 0);if(cnt < 0){perror("recv error");break;}else if(cnt == 0){printf("[%s:%d]:offline\n" ,inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));}  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){perror("send error");break;}}close(connfd);}
}
close(sockfd);return 0;
}

2.  多线程
线程相对与进程资源开销小,相同资源环境下,并发量比进程大。

#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>
#define SER_PORT  50000
#define SER_IP    "192.168.0.184"struct sockaddr_in cliaddr;
socklen_t clilen = sizeof(cliaddr);
int init_tcp_ser()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);//允许绑定处于TIME_WAIT状态的地址,避免端口占用问题:int optval = 1;setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));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_IP);int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));if (ret < 0){perror("bind error");return -1;}ret = listen(sockfd, 100);if (ret < 0){perror("listen error");return -1;}return sockfd;
}void *recv_msg(void *arg)
{int connfd = *((int *)arg);while(1){char buff[1024] = {0};memset(buff , 0 , sizeof(buff));size_t cnt = recv(connfd, buff, sizeof(buff), 0);if(cnt < 0){perror("recv error");break;}else if (0 == cnt){printf("offline\n");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){perror("send error");break;} }close(connfd);
}
int main(int argc, const char *argv[])
{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");return -1;}pthread_t tid;pthread_create(&tid, NULL, recv_msg, &connfd);pthread_detach(tid);
}
close(sockfd);return 0;
}

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

线程池预先创建一定数量的工作线程,等待任务队列中的任务并由线程逐个执行。任务由生产者提交(例如处理完成的 socket 描述符、客户端请求等),消费者(线程池中的工作线程)从队列取任务并执行。通过复用已有的线程,避免频繁的创建销毁带来的开销,并能更好地控制并发度和系统资源。

4.  IO多路复用
I-->O:fd
对多个文件描述符的读写可以复用一个进程。

fgets(stdin);
recv(connfd);
阻塞IO模式:
1. 多个任务之间是同步的效果

1)select实现IO多路复用:

1. 创建文件描述符集合                  fd_set
2. 添加关注的文件描述符到集合   FD_SET();
3. 使用select传递集合表给内核,内核开始监测事件  select()
4. 当内核监测到事件时,应用层select将解除阻塞,并获得相关的事件结果
5. 根据select返回的结果做不同的任务处理

void FD_CLR(int fd, fd_set *set);

  • 将集合清零,清空所有位。

       int  FD_ISSET(int fd, fd_set *set);

  • 将给定的文件描述符 fd 加入集合中,表示关心该描述符的事件。

       void FD_SET(int fd, fd_set *set);

  • 将给定的文件描述符 fd 从集合中移除,表示不再关注该描述符。

       void FD_ZERO(fd_set *set);

  • 检查指定的描述符是否在集合中。如果在,返回非零值;否则返回0。
  •  int select(int nfds, fd_set *readfds, fd_set *writefds,
    fd_set *exceptfds, struct timeval *timeout);
    功能:传递文件描述符结合表给内核并等待获取事件结果
    参数:
    nfds : 关注的最大文件描述符+1
    readfds:读事件的文件描述符集合
    writefds:写事件的文件描述符集合
    exceptfds:其他事件的文件描述符集合
    timeout:设置select监测时的超时时间
    NULL : 不设置超时时间(select一直阻塞等待)

            返回值:
    成功:返回内核监测的到达事件的个数
    失败:-1
    0 : 超时时间到达,但没有事件发生,则返回0

  • 掉用 mkfifo("./myfifo", 0664) 创建一个命名管道,权限为 0664。声明两个 fd_set:一个是要监听的集合 rdfds,另一个是用于传给 select() 的临时集合 rdfdstmp,因为 select() 会修改它。将标准输入(描述符 0)加入集合。将 FIFO 描述符 fifofd 也加入集合。计算 maxfd,用于 select() 的第一个参数 nfds,这里是两个描述符中的最大值。每次循环将原始集合 rdfds 复制给 rdfdstmp,以便 select() 修改后的集合不影响原集合。

  • 调用 select(maxfd+1, &rdfdstmp, NULL, NULL, NULL),参数:maxfd+1 为待监听描述符的范围上限。读取集合为 rdfdstmp,表示关注可读事件。其他集合设置为 NULL,表示不关注写、异常等事件;超时设为 NULL,表示阻塞直到有事件发生。如果标准输入就绪,执行 fgets(buff, sizeof(buff), stdin),然后输出 STDIN : <输入内容>

    如果 FIFO 就绪,清空 buff,执行 read(fifofd, buff, sizeof(buff)),然后输出 FIFO : <读取的内容>。需要注意:read 可能返回的小于 sizeof(buff) 的值,且返回 0 表示对端已
#include "head.h"int main(int argc, const char *argv[])
{char buff[1024] = {0};mkfifo("./myfifo", 0664);int fifofd = open("./myfifo", O_RDONLY);if (fifofd < 0){perror("open fifo error");return -1;}//1 创建文件描述符集合表fd_set rdfds;fd_set rdfdstmp;//2. 清空文件描述符集合表FD_ZERO(&rdfds);//3. 添加关注的文件描述符到集合中FD_SET(0, &rdfds);int maxfd = 0;FD_SET(fifofd, &rdfds);maxfd = maxfd > fifofd ? maxfd : fifofd;while (1){rdfdstmp = rdfds;//4. 传递集合表给内核并等待返回到达事件的结果int cnt = select(maxfd+1, &rdfdstmp, NULL, NULL, NULL);if (cnt < 0){perror("select error");return -1;}if (FD_ISSET(0, &rdfdstmp)){fgets(buff, sizeof(buff), stdin);  //0printf("STDIN : %s\n", buff);}if (FD_ISSET(fifofd, &rdfdstmp)){memset(buff, 0, sizeof(buff));read(fifofd, buff, sizeof(buff));printf("FIFO : %s\n", buff);}}close(fifofd);return 0;
}

使用select实现TCP并发服务端

#define SER_PORT  50000
#define SER_IP    "192.168.0.179"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_IP);int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));if (ret < 0){perror("bind error");return -1;}ret = listen(sockfd, 100);if (ret < 0){perror("listen error");return -1;}return sockfd;
}int main(int argc, const char *argv[])
{char buff[1024] = {0};struct sockaddr_in cliaddr;socklen_t clilen = sizeof(cliaddr);int sockfd = init_tcp_ser();if (sockfd < 0){return -1;}//1. 创建文件描述符集合fd_set rdfds;fd_set rdfdstmp;FD_ZERO(&rdfds);//2. 添加关注的文件描述符到集合FD_SET(sockfd, &rdfds);int maxfd = sockfd;while (1){rdfdstmp = rdfds;//3. 传递集合到内核,并等待返回监测结果int cnt = select(maxfd+1, &rdfdstmp, NULL, NULL, NULL);if (cnt < 0){perror("select error");return -1;}//4. 是否有监听套接字事件到达 ----》三次握手已完成,可以acceptif (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;}//5. 是否有通讯套接字事件到达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;
}

多进程模型可实现并发但资源开销大,适合小规模并发且注重隔离性的场景。

多线程模型资源开销小于进程,但频繁创建销毁线程影响性能。

线程池通过预先创建线程并复用,减少创建开销,主线程作为生产者分发任务,工作线程作为消费者处理任务。

I/O多路复用(如select)在单进程中监控多个文件描述符,适合高并发、低耗时任务场景。

select适合处理大量短连接或低耗时请求,如简单数据回显服务。对于耗时任务(如音视频编解码),I/O多路复用会阻塞整个进程,导致其他客户端无法及时响应。耗时任务场景下,仍需结合多线程或多进程模型,避免单线程处理瓶颈。不同并发方案各有优劣,应根据实际业务需求(并发量、任务耗时、资源限制)选择合适方案。

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

相关文章:

  • SM4加密算法
  • Karatsuba
  • 前端工程化与AI融合:构建智能化开发体系
  • 4-4.Python 数据容器 - 字典 dict(字典 dict 概述、字典的定义与调用、字典的遍历、字典的常用方法)
  • CPU 虚拟化之Cpu Models
  • 代码随想录刷题Day43
  • 时间轮定时器HashedWheelTimer
  • WSL设置静态IP
  • window程序打包
  • Libvio网站与客户端访问故障排查指南(专业版)
  • 什么是低空经济?
  • JMeter 5.3 性能测试:文件下载脚本编写与导出文件接收完整指南
  • QT鼠标事件中的QMouseEvent :e
  • 深度学习---卷积神经网络CNN
  • PLC_博图系列☞基本指令”S_ODT:分配接通延时定时器参数并启动“
  • HTML5超详细学习内容
  • 程序(进程)地址空间(1)
  • 基于MATLAB/Simulink的单机带负荷仿真系统搭建
  • LeetCode-23day:技巧经典
  • 疯狂星期四文案网第52天运营日记
  • 野火STM32Modbus主机读取寄存器/线圈失败(二)-解决CRC校验错误
  • 让ai写一个类github首页
  • Web前端之JavaScript时间体系全解析、performance、Date、now
  • Go语言循环性能终极对决:for vs range 深度剖析
  • 如何用Postman做接口测试?
  • k8s中的服务(Service),详细列举
  • JavaSE:类和对象2
  • Redis集群介绍——主从、哨兵、集群
  • 单兵图传设备如何接入指挥中心平台?国标GB/T28181协议的20位ID有何含义?如何进行配置?
  • [手写系列]Go手写db — — 第二版