网络编程(六)TCP并发服务器

文章目录

    • (一)概念
    • (二)TCP并发服务器
    • (三)使用多线程实现TCP并发服务器
      • 1. 思路
      • 2. 注意点
      • 3. 代码实现
    • (四)使用多进程实现TCP并发服务器
      • 1. 思路
      • 2. 注意点
      • 3. 代码实现
      • 4. 关于子进程结束后的资源回收问题
    • (五)使用IO复用实现TCP并发服务器
      • 1. 思路
      • 2. 代码实现

(一)概念

循环服务器:同一时刻只能处理一个客户端的请求。

并发服务器:可以同时处理多个客户端的请求 相互之间不影响。

并发和并行的区别:
并发:并发是指两个或多个事件在 同一时间间隔 发生,把任务在不同的时间点交给处理器进行处理。在同一时间点,任务并不会同时运行。
并行(多核CPU):并行是指两个或者多个事件在 同一时刻同时 发生,把每一个任务分配给每一个处理器独立完成。在同一时间点,任务一定是同时运行。

(二)TCP并发服务器

方式1:使用多线程实现
方式2:使用多进程实现
方式3:使用多路IO复用

(三)使用多线程实现TCP并发服务器

1. 思路

主线程负责等待客户端连接(accept);
一旦有客户端连接成功,就创建一个子线程,专门用来和该客户端通信

2. 注意点

  1. 使用多线程一定要注意线程间的同步和互斥问题

3. 代码实现

server.c

#include <my_head.h>sem_t sem;typedef struct _Msg{int acceptfd;struct sockaddr_in clientaddr;
}msg_t;void *task_func(void *msg){pthread_detach(pthread_self());//标记为分离态msg_t client_msg=*(msg_t *)msg;sem_post(&sem);char buff[128]={0};int nbytes=0;while(1){printf("开始通信  acceptfd = %d\n", client_msg.acceptfd);if(-1 == (nbytes = recv(client_msg.acceptfd,buff,sizeof(buff),0))){//出错close(client_msg.acceptfd);pthread_exit(NULL);}else if(0 == nbytes){//recv接收到的是0//客户端断开close(client_msg.acceptfd);pthread_exit(NULL);}if(!strcmp(buff,"quit")){close(client_msg.acceptfd);pthread_exit(NULL);}//正常接收数据printf("线程[%ld]接收到数据[%s]\n",pthread_self(),buff);strcat(buff,"--zyx");if(-1 == send(client_msg.acceptfd,buff,sizeof(buff),0)){close(client_msg.acceptfd);pthread_exit(NULL);}}
}int main(int argc, char const *argv[])
{if(3 != argc){printf("Usage:%s IPv4 port\n",argv[0]);exit(-1);}//创建套接字int sockfd=0;if(-1 == (sockfd = socket(AF_INET,SOCK_STREAM,0))){ERR_LOG("sock error");}//填充结构体struct sockaddr_in serveraddr;serveraddr.sin_family=AF_INET;serveraddr.sin_addr.s_addr=inet_addr(argv[1]);serveraddr.sin_port=htons(atoi(argv[2]));socklen_t serveraddrlen = sizeof(serveraddr);//绑定结构体信息if (-1 == bind(sockfd,(struct sockaddr *)&serveraddr,serveraddrlen)){ERR_LOG("bind error");}//设置为监听状态if(-1 == listen(sockfd, 5))ERR_LOG("listen error");//客户端结构体保存网络信息struct sockaddr_in clientaddr;socklen_t clientaddrlen = sizeof(clientaddr);msg_t msg;int acceptfd = 0;pthread_t tid=0; //无名信号量sem_init(&sem,0,1);while(1){printf("已就绪,等待连接..\n");//主线程等待连接if (-1 == (acceptfd = accept(sockfd,(struct sockaddr *)&clientaddr,&clientaddrlen))){ERR_LOG("accept error");}printf("有客户端连接\n");//如果主线程连接到了客户端,就创建子线程用来与客户端通信sem_wait(&sem);msg.acceptfd = acceptfd;msg.clientaddr = clientaddr;if(0 != pthread_create(&tid,NULL,task_func, &msg)){perror("pthread_create error");}}close(sockfd);return 0;
}

client.c

#include <my_head.h>int main(int argc, char const *argv[])
{if(3 != argc){printf("Usage:%s Ipv4 port\n",argv[0]);exit(-1);}//创建套接字int sockfd=0;if(-1 ==(sockfd = socket(AF_INET,SOCK_STREAM,0))){ERR_LOG("socket error");}//填充结构体struct sockaddr_in serveraddr;serveraddr.sin_family=AF_INET;serveraddr.sin_addr.s_addr=inet_addr(argv[1]);serveraddr.sin_port=htons(atoi(argv[2]));socklen_t serverlen = sizeof(serveraddr);//连接服务器if(-1 == connect(sockfd,(struct sockaddr *)&serveraddr,serverlen)){ERR_LOG("connect error");}printf("连接成功\n");char buff[128]={0};while(1){scanf("%s",buff);if(-1 == send(sockfd,buff,sizeof(buff),0)){ERR_LOG("send error");}printf("数据[%s]已发送\n",buff);if(!strcmp(buff,"quit")){break;}memset(buff,0,sizeof(buff));if(-1 == recv(sockfd,buff,sizeof(buff),0)){ERR_LOG("recv error");}printf("接收到数据[%s]\n",buff);}close(sockfd);return 0;
}

(四)使用多进程实现TCP并发服务器

1. 思路

父进程负责accept等待客户端连接;
一旦有客户端连接成功,就创建一个子进程,专门用来和该客户端通信

2. 注意点

多进程要注意子进程资源的回收问题,在服务器程序中如果不及时回收子进程资源,子进程会成为僵尸进程,浪费系统资源

3. 代码实现

#include <my_head.h>//信号处理函数
void sig_fun(int signum){if(SIGCHLD == signum){wait(NULL);}
}int main(int argc, char const *argv[])
{if(3 != argc){printf("Usage:%s IPv4 port\n",argv[0]);exit(-1);}//创建套接字int sockfd=0;if(-1 == (sockfd = socket(AF_INET,SOCK_STREAM,0))){ERR_LOG("sock error");}//填充结构体struct sockaddr_in serveraddr;serveraddr.sin_family=AF_INET;serveraddr.sin_addr.s_addr=inet_addr(argv[1]);serveraddr.sin_port=htons(atoi(argv[2]));socklen_t serveraddrlen = sizeof(serveraddr);//绑定结构体信息if (-1 == bind(sockfd,(struct sockaddr *)&serveraddr,serveraddrlen)){ERR_LOG("bind error");}//设置为监听状态if(-1 == listen(sockfd, 5))ERR_LOG("listen error");//客户端结构体保存网络信息struct sockaddr_in clientaddr;socklen_t clientaddrlen = sizeof(clientaddr);int acceptfd = 0;pid_t pid=0; //捕捉SIGCHLD信号signal(SIGCHLD,sig_fun);while(1){printf("已就绪,等待连接..\n");//主线程等待连接if (-1 == (acceptfd = accept(sockfd,(struct sockaddr *)&clientaddr,&clientaddrlen))){ERR_LOG("accept error");}printf("有客户端连接\n");if(-1 == (pid=fork())){//出错ERR_LOG("fork error");}else if(0 == pid){//子进程//执行完fork之后,父进程和子进程都各自有一个acceptfd和一个sockfdclose(sockfd);char buff[128]={0};int nbytes=0;while(1){printf("开始通信\n");if(-1 == (nbytes = recv(acceptfd,buff,sizeof(buff),0))){//出错break;}else if(0 == nbytes){//recv接收到的是0,客户端断开break;}if(!strcmp(buff,"quit")){break;}//正常接收数据printf("进程[%d]接收到数据[%s]\n",getpid(),buff);strcat(buff,"--zyx");if(-1 == send(acceptfd,buff,sizeof(buff),0)){break;}}//子进程退出close(acceptfd);exit(0);}else if(0 < pid){//父进程close(acceptfd);//关闭父进程acceptfd,回收文件描述符资源}}close(sockfd);return 0;
}

4. 关于子进程结束后的资源回收问题

方式1:父进程退出时,子进程都变成孤儿,被init回收资源 但是我们父进程的是服务器进程 不会退出
方式2:使用wait阻塞的方式回收 --不推荐用 因为又多了一个阻塞的函数
方式3:使用waitpid非阻塞方式回收 --不推荐用 因为需要轮询 占用CPU
方式4:比较好的处理方式是 子进程退出时给父进程发信号 父进程接到信号后再去回收子进程资源(可以通过捕捉SIGCHLD SIGUSR1 SIGUSR2)

(五)使用IO复用实现TCP并发服务器

1. 思路

TCP的服务器默认不支持并发,原因是两类阻塞的函数 accept 和 recv之间相互影响
也就是说,本质上就是因为 sockfd 和 acceptfd 两类文件描述符的缓冲区中没有内容
就会阻塞,而且多个阻塞之间相互影响。

使用IO复用来监视文件描述符,当sockfd就绪,就说明有新的客户端连接;acceptfd就绪,就说明有已连接的客户端发送数据。

2. 代码实现

#include <my_head.h>int main(int argc, char const *argv[])
{if(3 != argc){printf("Usage:%s IPv4 port\n",argv[0]);exit(-1);}//创建套接字int sockfd=0;if(-1 == (sockfd = socket(AF_INET,SOCK_STREAM,0))){ERR_LOG("sock error");}//填充结构体struct sockaddr_in serveraddr;serveraddr.sin_family=AF_INET;serveraddr.sin_addr.s_addr=inet_addr(argv[1]);serveraddr.sin_port=htons(atoi(argv[2]));socklen_t serveraddrlen = sizeof(serveraddr);//绑定结构体信息if (-1 == bind(sockfd,(struct sockaddr *)&serveraddr,serveraddrlen)){ERR_LOG("bind error");}//设置为监听状态if(-1 == listen(sockfd, 5))ERR_LOG("listen error");int acceptfd = 0;pid_t pid=0; //创建集合fd_set readfds;     //母本FD_ZERO(&readfds);  //清空母本fd_set tempfds;     //备份FD_ZERO(&tempfds);  //清空备份int max_fd=0;       //缓存最大的文件描述符//首先将sockfd添加到监听队列中FD_SET(sockfd,&readfds);max_fd = max_fd > sockfd ? max_fd : sockfd; //更新最大的文件描述符int ret=0;          //缓存就绪文件描述符的个数int i=0;            //遍历变量int nbytes=0;       //缓存接收到的数据字节数char buff[128]={0}; //缓存数据while(1){//将母本复制给temp,对所有文件描述符进行监听tempfds = readfds;//开始监听,没有文件描述符准备就绪时就阻塞if(-1 == (ret = select(max_fd+1,&tempfds,NULL,NULL,NULL))){ERR_LOG("select error");}//有fd准备就绪,遍历处理for(i = 3; i < max_fd+1 && ret != 0; i++){//判断i是否就绪if(FD_ISSET(i, &tempfds)){ret--;//判断就绪的i是否是sockfd,如果是说明有客户端连接if(sockfd == i){if(-1 == (acceptfd = accept(i,NULL,NULL))){ERR_LOG("accept error");}printf("客户端[%d]连接\n",acceptfd);FD_SET(acceptfd, &readfds);//将新的acceptfd添加到集合中max_fd = max_fd > acceptfd ? max_fd : acceptfd;//更新最大的文件描述符}else{//说明有已经连接的客户端发来数据了if(-1 == (nbytes = recv(i,buff,sizeof(buff),0))){ERR_LOG("recv error");}else if(0 == nbytes){ //说明有客户端断开连接printf("客户端[%d]断开链接\n",i);FD_CLR(i, &readfds);//将其移除队列close(i);//关闭其文件描述符continue;//跳过本次for循环}if(!strcmp(buff,"quit")){ //说明有客户端退出printf("客户端[%d]退出\n",i);FD_CLR(i, &readfds);//将其移除队列close(i);//关闭其文件描述符continue;//跳过本次for循环}//正常接收数据printf("接收到数据:[%s]\n",buff);strcat(buff,"--zyx");if(-1 == send(i,buff,sizeof(buff),0)){ERR_LOG("send error");}}}}}close(sockfd);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/31033.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2)如何去构建记忆宫殿辅助记忆

构建记忆宫殿 记忆方式构建记忆宫殿后记 记忆方式 记忆方法多种多样&#xff0c;旨在帮助人们更有效地编码、储存和回忆信息。以下是一些常用和有效的记忆方法&#xff1a; 1.联想记忆法&#xff1a;通过将新信息与已知信息或图像进行创意性联想来加深记忆。例如&#xff0c;将…

机器学习——RNN、LSTM

RNN 特点&#xff1a;输入层是层层相关联的&#xff0c;输入包括上一个隐藏层的输出h1和外界输入x2&#xff0c;然后融合一个张量&#xff0c;通过全连接得到h2&#xff0c;重复 优点&#xff1a;结构简单&#xff0c;参数总量少&#xff0c;在短序列任务上性能好 缺点&#x…

姿态识别论文复现(一)安装包+下载数据

Lite-HRNet&#xff1a;轻量级高分辨率网络 简介&#xff1a;高分辨率网络Lite-HRNet&#xff0c;用于人体姿态估计 环境配置&#xff1a;该代码是在 Ubuntu 16.04 上使用 python 3.6 开发的。需要 NVIDIA GPU。使用 8 个 NVIDIA V100 GPU 卡进行开发和测试。其他平台或 GPU …

CheckPoint 防火墙 CVE-2024-24919 VPN 漏洞修复说明

1. 漏洞说明 在 2024 年 5 月 27 日进行安全更新后&#xff0c;Check Point 的专门工作组将继续调查未经授权访问我们客户使用的 VPN 产品的企图。 2024 年 5 月 28 日&#xff0c;我们在远程访问 VPN 社区和移动访问软件刀片中使用 IPsec VPN 的安全网关中发现了一个漏洞 (…

搜维尔科技:「案例」NBA新科冠军与Xsens运动捕捉的缘分

北京时间昨日&#xff0c;凯尔特人在主场106比88击败独行侠&#xff0c;以总比分4比1获胜&#xff0c;夺得队史第18冠&#xff0c;超越湖人队&#xff08;17冠&#xff09;成为历史上夺冠次数最多的球队。凯尔特人队上一次夺冠还是在2007-2008赛季。 凯尔特人队主力Jayson Tat…

Api Post快速上手教程

Apipost快速上手教程可以按照以下步骤进行&#xff1a; 一、准备工作 访问Apipost官网下载并安装Apipost客户端&#xff0c;或者选择使用其Web端版本。注册并登录Apipost账号&#xff0c;以便更好地管理和使用你的项目。官网&#xff1a;Apipost-API 文档、设计、调试、自动化…

Day1:基础语法

今日目标&#xff1a;理解什么是变量、掌握常用的数据类型、学会数据类型转换 一、JavaScript 介绍 1. JavaScript 基础知识 主要讲解 &#xff1a;JavaScript 是什么、书写位置、注释、结束符、输入和输出语法、字面量。 1.1 JavaScript 是什么 是一种运行在客户端(浏览器…

Python数据科学 | 是时候跟Conda说再见了

本文来源公众号“Python数据科学”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;是时候跟Conda说再见了 1 简介 conda作为Python数据科学领域的常用软件&#xff0c;是对Python环境及相关依赖进行管理的经典工具&#xff0c;通…

ChatGPT提效:告别CRUD

前言 随着AIGC的发展以及大语言模型的成熟&#xff0c;各种AI应用眼花缭乱&#xff0c;以至于我们看到各种新奇的应用都会产生焦虑&#xff0c;我有一天会不会被淘汰&#xff1f;且看后文分析。AIGC的发展与逐渐成熟已经是无可逆转的局势&#xff0c;既然我们打不过为何不加入…

在 macOS 上安装 Docker

在 macOS 上安装 Docker 可以通过以下步骤完成&#xff1a; 1. 检查系统要求 确保你的 macOS 版本符合 Docker 的系统要求。Docker Desktop for Mac 需要 macOS 10.15 或更高版本。 2. 下载 Docker Desktop 打开你的浏览器&#xff0c;访问 Docker 官方网站。点击“Downloa…

java的有参构造方法

java的有参构造方法和无参构造方法类似&#xff0c;区别是构造方法名称里后面跟着一个括号&#xff0c;括号里是参数的定义 示例代码如下 class student4{private String name;private int age;public student4(String n,int a) {namen;agea;System.out.println("调用了…

软件构造 | Equality in ADT and OOP

软件构造 | Equality in ADT and OOP &#x1f9c7;1 Three ways to regard equality 1.1 Using AF to define the equality ADT是对数据的抽象&#xff0c; 体现为一组对数据的操作 抽象函数AF&#xff1a;内部表示→抽象表示 基于抽象函数AF定义ADT的等价操作&#xff0…

vscode结合GitHub Copilot编码

已集成工具 Azure Data StudioJetBrains IDEsVim/NeovimVisual StudioVisual Studio Code 目录 GitHub Copilot & Visual Studio Code 前提条件 Getting code suggestions Showing alternative suggestions Showing multiple suggestions in a new tab Accepting pa…

海南聚广众达电子商务咨询有限公司可信吗?

在数字化浪潮席卷全球的今天&#xff0c;电商行业已成为推动经济增长的重要力量。而在这个领域中&#xff0c;海南聚广众达电子商务咨询有限公司凭借其专业、精准的服务&#xff0c;在抖音电商领域独树一帜&#xff0c;成为行业的佼佼者。 海南聚广众达电子商务咨询有限公司自…

07-appium常用操作

一、press_keycode 1&#xff09;方法说明 press_keycode方法是appium的键盘相关函数&#xff0c;可以实现键盘的相关操作&#xff0c;比如返回、按键、音量调节等等。也可以使用keyevent方法&#xff0c;功能与press_keycode方法类似。 # KeyCode&#xff1a;各种操作对应的…

【fastapi】定时任务管理

在FastApi框架搭建的WBE系统中如何实现定时任务的管理&#xff1f; Python中常见的定时任务框架包括Celery、APScheduler和Huey。以下是每个框架的简单对比和示例代码。 1.Celery: 分布式任务队列&#xff0c;适合处理长时间运行的任务。 # 安装celery # pip install celery# …

【Axure教程】移动端多选图片上传

在移动端应用中&#xff0c;提供多选图片上传功能对于用户体验和功能性具有重要意义&#xff0c;尤其是在像微信、微博等社交媒体平台上。 例如用户可以快速上传多张图片进行分享&#xff0c;发布相册或创建图文并茂的动态&#xff1b;卖家可以一次性上传多个产品图片&#xf…

劲爆!Kimi月之暗面可以接入微信,智能升级, 打造个性多Agent(二)

前言 在当今这个快速发展的AI时代&#xff0c;抖音推出了一个名为“扣子Coze”的工具&#xff0c;帮助用户快速、低门槛地搭建属于自己的AI机器人。本文将详细介绍如何使用扣子Coze配置自己的AI Agent&#xff0c;并展示其在多个平台上的应用。 如何使用多个Agent 搭建更加智…

chatgpt: linux 下用纯c 编写一按钮,当按钮按下在一新窗口显示hello world

用这个程序模板&#xff0c;就可以告别只能在黑框框的终端中编程了。 在 Linux 环境下使用纯 C 语言编写一个按钮&#xff0c;当按钮按下时&#xff0c;在一个新窗口显示 "Hello World"。我们可以使用 GTK 库来实现这个功能。GTK 是一个用于创建图形用户界面的跨平台…

鸿蒙开发组件:【FA模型的Context】

FA模型的Context FA模型下只有一个Context。Context中的所有功能都是通过方法来提供的&#xff0c;它提供了一些featureAbility中不存在的方法&#xff0c;相当于featureAbility的一个扩展和补全。 接口说明 FA模型下使用Context&#xff0c;需要通过featureAbility下的接口…