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

Linux--->网络编程(TCP并发服务器构建:[ 多进程、多线程、select ])

TCP并发服务器构建

一、服务器

    单循环服务器:服务端同一时刻只能处理一个客户端的任务(TCP)
并发服务器:服务端同一时刻可以处理多个客户端的任务(UDP)

二、TCP服务端并发模型

1、多进程

     进程资源开销大,安全性高。

     以下是一个实现多进程TCP服务端代码:

#define SER_PORT 62300
#define SER_IP "192.168.0.165"int init_tcp_ser()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd < 0){perror("socket error");return 0;}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){printf("bind error");return -1;}int cnt = listen(sockfd, 100);if(cnt < 0){perror("listen error");return -1;}return sockfd;
}void wait_handler(int signo)
{wait(NULL);
}int main(int argc, char *argv[])
{signal(SIGCHLD, wait_handler);struct sockaddr_in cliaddr;socklen_t clilen = sizeof(cliaddr);int sockfd = init_tcp_ser();if(sockfd < 0){perror("init_tcp_ser error");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));ssize_t ret = recv(connfd, buff, sizeof(buff), 0);if(ret < 0){perror("send error");return -1;}else if(ret == 0){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");ssize_t cnt = send(connfd, buff, strlen(buff), 0);if(cnt < 0){perror("send error");break;}}   close(connfd);}}close(sockfd);return 0;
}

2、多线程

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

     以下是一个实现多线程TCP服务端代码:

#include "head.h"#define SER_PORT 62300
#define SER_IP "192.168.0.165"int init_tcp_ser()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd < 0){perror("socket error");return 0;}//允许绑定处于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(SER_PORT);seraddr.sin_addr.s_addr = inet_addr(SER_IP);int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));if(ret < 0){printf("bind error");return -1;}int cnt = listen(sockfd, 100);if(cnt < 0){perror("listen error");return -1;}return sockfd;
}void *task(void *sug)
{int connfd = *(int *)sug;char buff[1024] = {0};while(1){memset(buff, 0, sizeof(buff));ssize_t ret = recv(connfd, buff, sizeof(buff), 0);if(ret < 0){perror("send error");break;}else if(ret == 0){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");ssize_t cnt = send(connfd, buff, strlen(buff), 0);if(cnt < 0){perror("send error");break;}}   close(connfd);
}int main(int argc, char *argv[])
{struct sockaddr_in cliaddr;socklen_t clilen = sizeof(cliaddr);int sockfd = init_tcp_ser();if(sockfd < 0){perror("init_tcp_ser error");return -1;}while(1){int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen); if(connfd < 0){perror("accept error");return -1;}pthread_t tid;int fd = pthread_create(&tid, NULL, task, &connfd);if(fd < 0){perror("thread create error");return -1;}  pthread_detach(tid);}close(sockfd);return 0;
}

3、线程池

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

4.  IO多路复用

    1)概念

     对多个文件描述符的读写可以复用一个进程。
在不创建新的进程和线程的前提下,使用一个进程实现对多个文件读写的同时监测。
阻塞IO模式:

    2)方式

        实现方式有 select、poll、epoll 三种。

5、select 实现IO多路复用

    1)实现步骤

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

    2)select 的宏

        将集合清0:void FD_ZERO(fd_set *set);
向集合中添加文件描述符:void FD_SET(int fd, fd_set *set);
从集合中移除文件描述符:void FD_CLR(int fd, fd_set *set);
检查文件描述符是否在集合中:int  FD_ISSET(int fd, fd_set *set);

    3)select() 函数接口

 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

    4)应用示例

        (1)通过select函数构建多路复用,实现管道和终端的读

        管道向服务端写的代码:

int main(void)
{mkfifo("./myfifo", 0664);int fd = open("./myfifo", O_WRONLY);if(fd < 0){perror("open error");return -1;}while(1){write(fd, "hello world", 11);sleep(1);}close(fd);return 0;
}

        读的代码:

int main(void)
{char buff[1024] = {0};mkfifo("./myfifo", 0664);int fifofd = open("./myfifo", O_RDONLY);if(fifofd < 0){perror("open 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))//检查文件描述符0是否在集合中{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;
}

        (2)使用select实现TCP服务端IO多路复用代码

#define SER_PORT  50000
#define SER_IP    "192.168.0.165"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;
}

【END】

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

相关文章:

  • 动态规划:青蛙跳台阶
  • 红外对管模块传感器讲解
  • Spring Boot 多环境配置
  • HTML5入门学习
  • Linux下解压RAR文件到指定目录的完整指南
  • 学习设计模式《二十四》——访问者模式
  • 当传统金融遇上AI智能:一个开源AIStock系统的深度技术解析
  • net::ERR_CONTENT_LENGTH_MISMATCH 206 (Partial Content) nginx转发记录
  • 核心篇(下):Transformer 架构详解(程序员视角・实战版)
  • 从源码看浏览器弹窗消息机制:SetDefaultView 的创建、消息转发与本地/在线页通用实践
  • 2.渗透-.WEB运行原理-ZBlog安装(进一步理解数据库)
  • 深度学习——优化函数
  • Fast R-CNN论文研读整理记录
  • Lambda 表达式
  • 【菜狗学三维重建】TSDF三维重建隐式表达详细解释——20250413
  • pandas常用方法
  • 人工智能和机器学习如何改善机器人技术
  • 【VSCode】VSCode为Java C/S项目添加图形用户界面
  • YOLOv8-SMOT:一种高效鲁棒的实时小目标跟踪框架:基于切片辅助训练与自适应关联
  • 腾讯云 CVM 上的 SpringBoot 应用避免非法访问
  • Redis实战-优惠券秒杀解决方案总结大全
  • 开疆智能Profinet转EtherCAT网关连接TR-Electronic传感器配置案例
  • 部署网页在服务器(公网)上笔记 infinityfree 写一个找工作单html文件的网站
  • 分享 HTML 邮件开发的 15 个踩坑实录
  • Ubuntu 切换 SOCKS5代理 和 HTTP 代理并下载 Hugging Face 模型
  • 树莓派装的Ubuntu Server连接不上WIFI
  • Day14——JavaScript 核心知识全解析:变量、类型与操作符深度探秘
  • DeFi协议Lombard能突破比特币生态原生叙事困境吗?
  • 鸿蒙ArkUI 基础篇-06-组件基础语法-Column/Row/Text
  • 主键索引和普通索引的区别