【C++网络编程】(三)多线程TCP服务端程序

news2024/11/26 18:26:06

文章目录

  • (三)多线程TCP服务端程序
    • 多线程服务端
    • 客户端

(三)多线程TCP服务端程序

图片来源:https://subingwen.cn/linux/concurrence

主线程负责监听和连接多个客户端,子线程负责和对应的客户端进行通信(完成数据的接收和发送)。

服务端与3个客户端连接并进行通信:
在这里插入图片描述

多线程服务端

server.cpp

#include <iostream>
#include <thread>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <vector>

// SockInfo 结构体,包含文件描述符、线程ID和地址信息
struct SockInfo {
    int fd;                      // 通信文件描述符
    sockaddr_in addr;            // 地址信息
};

// 最大连接数128
std::vector<SockInfo> infos(128);

// 线程工作函数,处理客户端通信
void working(SockInfo* info) {
    while (true) {
            char buf[1024]; // 每一个线程都有一个独立的缓冲区
            int len = recv(info->fd, buf, sizeof(buf), 0);  // 从客户端读取数据
            if (len == 0) {
                  std::cout << "端口号" << ntohs(info->addr.sin_port)<<  "的客户端已经关闭连接..." << std::endl;
                  info->fd = -1;  // 关闭连接,释放资源
                  break;
            } else if (len == -1) {
                  std::cerr << "接收数据失败..." << std::endl;
                  info->fd = -1;
                  break;
            } else {

                  std::cout << "端口号" << ntohs(info->addr.sin_port)<< "的客户端: " << buf << std::endl;  // 打印客户端发送的消息
                  sprintf(buf, "你好, 客户端\n");  // 格式化字符串
                  send(info->fd, buf, strlen(buf), 0);  // 回应客户端
            }
    }
}

int main() {
      // 1. 创建用于监听的套接字
      int fd = socket(AF_INET, SOCK_STREAM, 0);
      if (fd == -1) {
            perror("socket");
            exit(EXIT_FAILURE);
      }

      // 2. 绑定套接字
      sockaddr_in addr;
      addr.sin_family = AF_INET;           // ipv4
      addr.sin_port = htons(10000);         // 设置端口号并转换为网络字节序
      inet_pton(AF_INET, "172.31.108.107", &addr.sin_addr.s_addr); // 指定IP地址
      int ret = bind(fd, (sockaddr*)(&addr), sizeof(addr));
      if (ret == -1) {
            perror("bind");
            exit(EXIT_FAILURE);
      }

      // 3. 设置监听(主线程)
      ret = listen(fd, 100);
      if (ret == -1) {
            perror("listen");
            exit(EXIT_FAILURE);
      }

      // 4. 等待连接请求
      socklen_t len = sizeof(sockaddr);

      // 初始化连接信息
      for (auto& info : infos) {
            info.fd = -1;
      }

      while (true) {
            // 查找一个可用的 SockInfo
            SockInfo* pinfo = nullptr;
            for (auto& info : infos) {
                  if (info.fd == -1) {
                        pinfo = &info;
                        break;
                  }
            }

            if (!pinfo) {
                  std::this_thread::sleep_for(std::chrono::seconds(1));  // 等待空闲连接位
                  continue;
            }

            // 5. 接受客户端连接(主线程)
            int connfd = accept(fd, reinterpret_cast<sockaddr*>(&pinfo->addr), &len);
            std::cout << "parent thread, connfd: " << connfd << std::endl;
            if (connfd == -1) {
                  perror("accept");
                  exit(EXIT_FAILURE);
            }

            pinfo->fd = connfd;  // 保存客户端的文件描述符

            // 6. 创建子线程与客户端进行通信
            std::thread t(working, pinfo);

            // 7. 分离线程
            t.detach();
      }

      // 关闭监听套接字
      close(fd);

      return 0;
}

编译和运行:

g++ server.cpp -o server -pthread
./server

客户端

client.cpp

#include <iostream>     // std::cout, std::cerr
#include <cstdlib>     // std::exit
#include <unistd.h>     // close, sleep
#include <cstring>      // memset, strlen
#include <arpa/inet.h>  // socket, connect, inet_pton, htons

int main()
{
      // 1. 创建通信的套接字
      int fd = socket(AF_INET, SOCK_STREAM, 0);  // 创建一个TCP套接字
      if (fd == -1)
      {
            perror("socket");  // 错误处理
            std::exit(EXIT_FAILURE);
      }

      // 2. 连接服务器
      sockaddr_in addr;  // 用于存储服务器地址信息
      addr.sin_family = AF_INET; // 地址族,IPv4
      addr.sin_port = htons(10000);   // 大端端口转换
      inet_pton(AF_INET, "172.31.108.107", &addr.sin_addr.s_addr); // 将IP地址转换为网络字节顺序

      int ret = connect(fd, (sockaddr*)&addr, sizeof(addr)); // 连接到服务器
      if (ret == -1)
      {
            perror("connect");  // 错误处理
            std::exit(EXIT_FAILURE);
      }

      // 3. 和服务器端通信
      int number = 0;
      while (true)
      {
            // 发送数据
            char buf[1024];  // 数据缓冲区
            sprintf(buf, "你好, 服务器...%d", number++);  // 格式化字符串
            send(fd, buf, strlen(buf), 0);  // 发送数据
            
            // 接收数据
            memset(buf, 0, sizeof(buf));  // 清空缓冲区
            int len = recv(fd, buf, sizeof(buf), 0);  // 从服务器读取数据
            if (len > 0)
            {
                  std::cout << "服务器: " << buf;  // 打印服务器发送的消息
            }
            else if (len == 0)
            {
                  std::cout << "服务器断开了连接..." << std::endl;  // 服务器断开连接
                  break;
            }
            else
            {
                  perror("recv");  // 错误处理
                  break;
            }
            sleep(1);   // 每隔1秒发送一条数据
      }

      close(fd);  // 关闭套接字

      return 0;
}

编译和运行:

g++ client.cpp -o client
./client

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

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

相关文章

vue后台管理系统从0到1搭建(4)各组件的搭建

文章目录 vue后台管理系统从0到1搭建&#xff08;4&#xff09;各组件的搭建Main.vue 组件的初构 vue后台管理系统从0到1搭建&#xff08;4&#xff09;各组件的搭建 Main.vue 组件的初构 根据我们的效果来看&#xff0c;分析一下&#xff0c;我们把左边的区域分为一个组件&am…

如何将本地磁盘镜像包部署到docker中(以mysql5_7.tar.gz为例)

1.复制文件到宿主机 2.找到对应目录&#xff0c;docker load docker images就可以看到该镜像啦

PE结构之导入表

流程图: 文件中\样式 加载到进程中时 加载到进程中时的过程,一张图不够放 续图 整个流程 考虑到 PE32 可执行文件&#xff08;64 位&#xff09;&#xff0c;每个 ILT (导入名称表) 条目总结为&#xff1a; 如果设置了高位&#xff08;位 63&#xff0c;也称为“序号标志”&…

【Spring详解】Maven从安装到应用(Maven Help插件的安装)-国内源的配置(中央仓库及私服的概念)

&#x1f308;个人主页&#xff1a;努力学编程’ ⛅个人推荐&#xff1a; c语言从初阶到进阶 JavaEE详解 数据结构 ⚡学好数据结构&#xff0c;刷题刻不容缓&#xff1a;点击一起刷题 &#x1f319;心灵鸡汤&#xff1a;总有人要赢&#xff0c;为什么不能是我呢 &#x1f42f…

48 C 语言实战项目——客户信息管理系统

目录 1 需求说明 1.1 主菜单 1.2 添加客户 1.3 显示客户列表 1.4 修改客户 1.5 删除客户 1.6 退出 2 流程分析 2.1 总流程图 2.2 添加客户流程图 2.3 显示客户列表流程图 2.4 改客户流程图 2.4.1 修改客户总体流程图 2.4.2 具体执行修改部分的流程图 2.5 删除客…

MySQL-约束Constraint详解

文章目录 约束简介非空约束检查约束唯一约束列级约束与表级约束给约束起名字主键约束 约束简介 约束是我们在创建表的时候, 我们可以给表中的字段添加约束确保我们的数据的完整性和有效性, 比如大家平时上网时注册用户常见的 : 用户名不能为空, 对不起, 用户名已经存在等提示信…

【C++】用红黑树模拟实现set与map

目录 一、红黑树的完善&#xff1a; 1、红黑树节点模版的修改&#xff1a; 2、仿函数在模拟实现中的应用&#xff1a; 3、新增迭代器&#xff1a; 4、红黑树中的迭代器实现&#xff1a; 二、set与map的模拟实现&#xff1a; 1、insert&#xff1a; 2、map的[ ]: 三、测…

无刷直流电机工作原理:【图文讲解】

电动机 (俗称马达) 是机械能与电能之间转换装置的通称。可以分为电动机和发电机.一般称电机时就是指电动机。这个在日常应用中&#xff0c;比较多见&#xff0c;比如机器人&#xff0c;手机&#xff0c;电动车等。 直流电机&#xff1a;分为有刷直流电机&#xff08;BDC&#…

本地ubuntu主机搭建我的世界服务器并免费开启公网映射 结合MESM面板 chmlfrp 保姆级教学

本地ubuntu主机搭建我的世界forge服务器并免费开启公网映射 结合MESM面板 chmlfrp 这是一篇很完成的从ssh命令->配置java环境->安装MCS->部署服务器->开启公网映射的我的世界保姆级开服教程,可以慢慢食用ଘ(੭ˊ꒳ˋ)੭ 。 为什么选择forge服务器进行开服&#x…

【前车之鉴】坑啊~ RestHighLevelClient 超时时间偶尔失效问题解决方案

文章目录 show me code缘起原因分析 几点建议 结论&#xff1a;实际你的配置是生效的&#xff0c;只不过效果不明显而已&#xff0c;通过下面的配置放大直观效果。 show me code 核心代码 public static void main(String[] args) {RestClientBuilder builder RestClient.bu…

【M2TR】M2TR: Multi-modal Multi-scale Transformers for Deepfake Detection

文章目录 M2TR: Multi-modal Multi-scale Transformers for Deepfake Detectionkey points研究贡献方法多尺度变压器频率过滤器跨模态融合损失函数SR-DF数据集实验总结M2TR: Multi-modal Multi-scale Transformers for Deepfake Detection 会议/期刊:ICMR ’22 作者: key …

深入理解栈(Stack)(纯小白进)

目录&#xff1a; 一、栈是什么&#xff1f;1. 栈的概念2.栈的结构选择 二、栈的实现1. 栈结构体的定义2. 栈的初始化3. 栈的销毁4. 入栈5.出栈6. 取栈顶元素7. 栈中元素的个数8. 判断栈是否为空 总结 一、栈是什么&#xff1f; 1. 栈的概念 栈&#xff08;Stack&#xff09;…

游戏开发指南:使用 UOS C# 云函数快速构建与部署服务端逻辑实战教学

零基础的服务端小白&#xff0c;现在也可以使用 Unity 结合 C# 来轻松搞定游戏服务端啦&#xff01; 在本篇文章中&#xff0c;我们将以游戏中的“抽卡”功能为例&#xff0c;展示如何使用 Unity Online Services&#xff08;UOS&#xff09;提供的强大 C# 云函数服务&#xf…

Elasticsearch(二)集成Spring Boot 基本的API操作

目录 一、集成Spring Boot 1、创建项目 2、pom文件 查看springboot集成的依赖 3、增加es的config类 二、索引相关API 1、创建索引 2、获取索引&#xff0c;判断其是否存在 3、删除索引 三、文档相关API 1、添加文档 2、获取文档&#xff0c;判断是否存在 3、获取文档…

Java后端面试----某团一面

美团一面 1.介绍一下你的第一个项目 这个就不多说了&#xff0c;主要是根据自己的简历上面的项目进行一个简短的概括使用的技术栈和什么背景解决了什么问题等等。 2.线程安全的类有哪些&#xff0c;平时有使用过哪些&#xff0c;主要解决什么问题 在Java中线程安全的类比如…

对后端返回的日期属性进行格式化(扩展 Spring MVC 的消息转换器)

格式化之前 格式化之后&#xff1a; 解决方式 方式一 在属性中加上注解&#xff0c;对日期进行格式化 JsonFormat(pattern "yyyy-MM-dd HH:mm:ss")private LocalDateTime createTime;//JsonFormat(pattern &quo…

echarts按需引入解决项目大小问题

背景&#xff1a; 按需加载缩减项目大小&#xff0c;提升项目性能和可用性 实现&#xff1a; 创建echarts.js main.js进行配置 页面中引用 效果 全量导入 按需加载&#xff1a;

Chrome清除nslookup解析记录 - 强制http访问 - 如何禁止chrome 强制跳转https

步骤&#xff1a; 地址栏输入 chrome://net-internals/#hsts在Delete domain 栏的输入框中输入要http访问的域名&#xff0c;然后点击“delete”按钮最后在Query domain 栏中搜索刚才输入的域名&#xff0c;点击“query”按钮后如果提示“Not found”即可&#xff01; 办法来自…

Linux系统:apt upgrade与apt update 命令的作用

一.sudo apt update命令 sudo apt update命令的主要作用是更新本地软件包列表。‌ 它不会下载或安装新的软件包&#xff0c;而是更新本地系统中软件包的列表&#xff0c;以反映远程存储库中的最新可用软件包信息。这确保了软件包管理器&#xff08;APT&#xff09;具有最新的软…

第十六周周报:单发的目标检测系列

目录 摘要 Abstract 一、SSD 1.1 模型结构 1.2 代码 二、YOLO 三、Termius 总结 摘要 本周主要学习单阶段的目标检测算法&#xff0c;如SSD、YOLO模型。详细学习了每个模型的原理&#xff0c;以及SSD和YOLO模型之间的异同。在本篇博客中将展示SSD的PyTorch实现代码&am…