C-S模式之实现一对一聊天

news2025/4/8 3:11:41

天天开心!!!

文章目录

  • 一、如何实现一对一聊天?
    • 1. 服务器设计
    • 2. 客户端设计
    • 3. 服务端代码实现
    • 4. 客户端代码实现
    • 5. 实现说明
    • 6.实验结果
  • 二、改进
    • 常见的服务器高并发方案
    • 1. 多线程/多进程模型
    • 2. I/O多路复用
    • 3. 异步I/O(Asynchronous I/O)
    • 4. 事件驱动框架
    • 5. Reactor模式
    • 6. Proactor模式
    • 7. 协程(Coroutine)
  • 三、使用epoll改进Server服务端代码
    • 1. epoll基本工作流程
    • 2. 服务端的实现思路
    • 3. 改良后的具体实现代码
    • 实验结果:
    • 4. 实现说明
    • 5. 优势


一、如何实现一对一聊天?

在C++的Socket编程中,实现一对一聊天的基本思路是构建一个客户端(Client)和一个服服务端(Server),并让每个客户端之间通过服务器进行消息的转发,具体步骤如下:

1. 服务器设计

服务器愮接受多个客户端的连接,并为每对用户建立专属的通信通道,实现流程如下:

  • 服务端启动并监听某个端口
  • 每当有客户端连接时,服务端接受连接并创建一个独立的线程或使用I/O多路复用(如select、epoll)来处理客户端请求。
  • 服务端维护一个客户端的连接表(这里我们使用map来存储),当两个客户端匹配时,将彼此的消息进行转发
  • 实现聊天消息的收发和转发逻辑

2. 客户端设计

客户端需要与服务器保持连接,并能够持续地发送和接收消息。实现流程如下:

  • 客户端启动后,连接到服务端指定的IP和端口
  • 客户端可以发送消息给服务端,服务端将消息转发给目标用户
  • 客户端持续接收从服务器发送来的消息,显示在用户界面或控制台

3. 服务端代码实现

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <thread>
#include <map>



#define PORT 8080
#define BUFFER_SIZE 1024

std::map<int,int> client_map; //存储客户端的配对关系

//处理客户端通信
void handle_client(int client_socket){
    char buffer[BUFFER_SIZE];
    int target_socket=client_map[client_socket];  //获取配对的客户端socket
    while(true){
        memset(buffer,0,sizeof(buffer));//清空缓冲区
        ssize_t bytes_received=recv(client_socket,buffer,BUFFER_SIZE,0);//接收数据的长度
        if(bytes_received<=0){//如果接收失败,则关闭连接
            std::cerr<<"Error receiving data from client"<<std::endl;//输出错误信息
            close(client_socket);
            return;
        }
        std::cout<<"收到消息:"<<buffer<<std::endl;
        //将消息转发给目标客户端
        if(client_map.find(target_socket)!=client_map.end()){
            send(target_socket,buffer, strlen(buffer),0);
        }else{
            std::cerr<<"Error sending data to target client"<<std::endl;
        }
    }
}

int main()
{
    int server_socket;
    struct sockaddr_in server_addr;

    //创建服务器套接字
    server_socket=socket(AF_INET,SOCK_STREAM,0);
    if(server_socket==0){
        std::cerr<<"Error creating server socket"<<std::endl;
        return -1;
    }
    //初始化地址结构
    server_addr.sin_family=AF_INET;
    server_addr.sin_addr.s_addr=INADDR_ANY;
    server_addr.sin_port=htons(PORT);

    //绑定地址和端口
    if(bind(server_socket,(struct sockaddr*)&server_addr,sizeof(server_addr))<0){
        std::cerr<<"Error binding server socket"<<std::endl;
        return -1;
    }
    //监听客户端连接
    if(listen(server_socket,3)<0){
        std::cerr<<"Error listening for connections"<<std::endl;
        return -1;
    }
    std::cout<<"等待客户端连接..."<<std::endl;

    while(true){
        struct sockaddr_in client_addr;
        socklen_t addr_len=sizeof(client_addr);
        int new_socket=accept(server_socket,(struct sockaddr*)&client_addr,&addr_len);

        if(new_socket<0){
            std::cerr<<"Error accepting connection"<<std::endl;
            continue;
        }
        std::cout<<"新客户端连接"<<inet_ntoa(client_addr.sin_addr)<<std::endl;

        //这里为了简化,我们直接假设是两客户端配对,client_map存储配对关系
        if(client_map.empty()){
            client_map[new_socket]=-1;//第一个客户端,暂时没有配对
        }else{
            for(auto &pair:client_map){
                if(pair.second==-1){
                    client_map[new_socket]=pair.first;//第二个客户端与第一个客户端配对
                    client_map[pair.first]=new_socket;//第一个客户端与第二个客户端配对
                    std::cout<<"客户端配对成功"<<std::endl;
                    break;
                }
            }
        }

        //创建线程处理新客户端
        std::thread client_thread(handle_client,new_socket);
        client_thread.detach();//线程分离,主线程不阻塞

    }

    close(server_socket);
    return 0;
}

4. 客户端代码实现

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


#define PORT 8080
#define BUFFER_SIZE 1024

//处理服务器消息
void receive_message(int socket){
    char buffer[BUFFER_SIZE];

    while(true){
        memset(buffer, 0, sizeof(buffer));//清空缓冲区
        ssize_t bytes_received = recv(socket, buffer, BUFFER_SIZE, 0);

        if(bytes_received <= 0){
            std::cout << "服务器断开连接..." << std::endl;
            close(socket);
            return;
        }
        std::cout<<"收到消息:"<<buffer<<std::endl;
    }
}


int main()
{
    int client_socket;//客户端套接字
    struct sockaddr_in server_addr;

    //创建客户端套接字
    client_socket=socket(AF_INET, SOCK_STREAM, 0);

    if(client_socket<0){
        std::cout<<"创建套接字失败"<<std::endl;
        return -1;
    }
    //初始化服务器地址
    server_addr.sin_family=AF_INET;
    server_addr.sin_port=htons(PORT);
    server_addr.sin_addr.s_addr=inet_addr("127.0.0.1");//服务器IP

    //连接到服务器
    if(connect(client_socket,(struct sockaddr*)&server_addr,sizeof(server_addr))<0){
        std::cerr<<"连接服务器失败"<<std::endl;
        return -1;
    }
    std::cout<<"连接到服务器成功"<<std::endl;

    //创建线程接受服务器消息
    std::thread receive_thread(receive_message,client_socket);//创建线程(函数,函数的形参)
    receive_thread.detach();//线程分离,主线程结束后,子线程也结束

    //发送消息给服务器
    char message[BUFFER_SIZE];

    while(true){
        std::cout<<"请输入消息:";
        std::cin.getline(message,BUFFER_SIZE);
        send(client_socket,message,strlen(message),0);
    }

    close(client_socket);
    return 0;
}

5. 实现说明

  • 服务端:服务器监听客户端连接并维护一个客户端配对表client_map;当两个客户端配对后,消息就可以在它们之间转发。这里使用了多线程来处理每个客户端的通信
  • 客户端:客户端连接到服务器并启动一个线程用于接收来自服务器的消息,用户可以输入消息并发送给服务器,服务器负责转发消息给配对的客户端

6.实验结果

在这里插入图片描述

二、改进

我们可以使用epoll或者select替代多线程处理,提高服务器的并发性能。也可以增加心跳机制来检测客户端是否断开连接。如果要完善,还可以增加实现身份验证和聊天室功能,使得用户可以自由选择与谁聊天。

常见的服务器高并发方案

1. 多线程/多进程模型

每个连接由一个独立的线程或进程处理,能够比较简单的实现并发处理

  • 优点:代码易于理解,编写较为简单
  • 缺点:线程或进程的开销较大,在高并发场景下,大量的线程/进程会带来系统资源消耗和性能瓶颈,特别是在数千甚至数万个连接的时候

2. I/O多路复用

I/O多路复用可通过少量的线程处理大量并发连接,常用的方法包括:

  • select:通过一个文件描述符集合监视多个文件描述符是否有I/O事件
    (1)优点:简单、易用、跨平台支持好
    (2) 缺点:性能不佳,处理大量连接时,每次调用select都要遍历整个描述符集合,效率很低

  • poll:与select类似,但没有文件描述符限制
    (1)优点:避免了select的文件描述符限制
    (2) 缺点:与select雷系,性能仍然不高,遍历整个描述符集合

  • epoll(Linux专用):epoll是Linux特有的I/O多路复用机制,性能更好,适合处理大量并发连接
    (1)优点:不会遍历所有文件描述符,性能优异,适用于高并发场景
    (2) 缺点:仅限于Linux系统

3. 异步I/O(Asynchronous I/O)

异步I/O通过事件驱动机制,程序不需要等待I/O操作的完成,而是注册事件,事件触发时进行处理。常见的异步I/O实现包括:

  • Windows:使用IOCP(I/O Completion Port)实现异步I/O处理
  • Linux:可以使用libaio或者基于epoll实现的异步I/O
  • 优点:真正的异步,无需阻塞等待I/O操作,性能高,适合高并发
  • 缺点:编写异步代码比较复杂,调试很困难

4. 事件驱动框架

利用现成的事件驱动框架来处理高并发来凝结,常见的库包括

  • libevent:基于事件的异步I/O库,支持epoll、kqueue等多种I/O多路复用机制,适合处理大量并发连接
  • libuv:跨平台异步I/O库,Node.js就是基于libuv实现的
  • Moduo:C++高性能网络库,基于epoll和线程池,适用于Linux下的高并发场景
  • 优点:封装好、使用方便,能够提高并发效率
  • 缺点:引入了额外的依赖,性能调优相对不够灵活

5. Reactor模式

Reactor模式是I/O多路复用的一种常见的实现模式,它通过注册I/O事件,将事件分发给事件处理器

  • 典型实现:使用epoll或select监听事件,再结合事件处理回调函数进行处理
  • 优点:能够较好地处理大量并发连接,灵活性高
  • 缺点:编写和理解较为复杂,需要维护事件循环和回调函数

6. Proactor模式

Proactor模式是异步I/O的常见实现,区别于Reactor,Proactor是I/O操作完成后再进行回调

  • 典型实现:Windows上的ICP就是Proactor模式实现的
  • 优点:异步操作更加彻底。I/O操作由操作系统处理,减少了用户态的干预
  • 缺点:实现较为复杂,调试难

7. 协程(Coroutine)

协程是一种轻量级的线程,能够在用户态进行切换,使用协程可以避免线程切换的开销,同时实现高并发。可以结合I/O多路复用技术,如epoll,来实现高效的协程并发

  • 典型框架:如Boost.Asio支持协程、libgo协程库等
  • 优点:切换开销小、性能高,代码易于理解
  • 缺点:调试比较复杂,尤其实在上下文切换时容易出现问题

三、使用epoll改进Server服务端代码

使用epoll实现一对一聊天的服务端,可以大大提高服务器的并发处理能力,相比于传统的多线程或select,epoll更适合处理大量客户端的连接,尤其是在高并发场景下。

1. epoll基本工作流程

  • 创建epoll文件描述符:使用epoll_create创建epoll实例
  • 注册事件:通过epoll_ctl将套接字添加到epoll实例中,并设置要监听的事件(如EPOLLIN,表示有数据可读)
  • 等待事件:使用epoll_wait等待事件发生
  • 处理事件:一旦事件发生,处理相应的客户端读写操作

2. 服务端的实现思路

  • 启动epoll实例:监听客户端的连接请求
  • 当有新的客户端连接时,将其注册到epoll实例中
  • 维护客户端配对关系:服务端为每个客户端建立配对表
  • 消息的收发和转发:当接收到某个客户端的消息时,通过配对表找到对应的目标客户端,并将消息转发过去

3. 改良后的具体实现代码

#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <map>
#include <sys/epoll.h>  // epoll头文件

#define PORT 8080
#define BUFFER_SIZE 1024
#define MAX_EVENTS 10

std::map<int,int> client_map;//客户端配对表

int main(){
    int server_socket,epoll_fd;// 服务器套接字,epoll文件描述符
    struct sockaddr_in server_addr; // 服务器地址结构体

    //创建服务器套接字
    server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if(server_socket <0){
        std::cerr << "创建服务器套接字失败" << std::endl;
        return -1;
    }
    //设置地址复用
    int opt=1;
    setsockopt(server_socket,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));//设置地址复用

    //初始化服务器地址
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);

    //绑定地址到服务器套接字
    if(bind(server_socket,(struct sockaddr*)&server_addr,sizeof(server_addr))<0){
        std::cerr << "绑定地址到服务器套接字失败" << std::endl;
        return -1;
    }
    //监听端口
    if(listen(server_socket,10)<0){
        std::cerr << "监听端口失败" << std::endl;
        return -1;
    }
    //创建epoll实例
    epoll_fd = epoll_create1(0);
    if(epoll_fd ==-1){
        std::cerr<<"创建epoll实例失败"<<std::endl;
        return -1;
    }

    //将服务器套接字加入到epoll实例中
    struct epoll_event event;
    event.events = EPOLLIN;
    event.data.fd = server_socket;

    epoll_ctl(epoll_fd,EPOLL_CTL_ADD,server_socket,&event);

    std::cout<<"服务器启动成功,等待客户端连接...."<<std::endl;

    struct epoll_event events[MAX_EVENTS];  // epoll事件数组, 用于存储就绪事件

    while(true){
        //无限等待事件
        int event_count=epoll_wait(epoll_fd,events,MAX_EVENTS,-1);

        for(int i=0;i<event_count;i++){
            if(events[i].data.fd==server_socket){
                //处理新的客户端连接
                struct sockaddr_in client_addr;
                socklen_t clinet_len=sizeof(client_addr);
                int client_socket=accept(server_socket,(struct sockaddr*)&client_addr,&clinet_len);//接受客户端连接
                if(client_socket<0){
                    std::cerr<<"接受客户端连接失败"<<std::endl;
                    continue;
                }
                std::cout<<"新的客户端连接:"<<inet_ntoa(client_addr.sin_addr)<<std::endl;

                //将新客户端连接加入到epoll监听中
                event.events=EPOLLIN;
                event.data.fd=client_socket;
                epoll_ctl(epoll_fd,EPOLL_CTL_ADD,client_socket,&event);

                //客户端配对处理
                if(client_map.empty()){
                    client_map[client_socket]=-1;//第一个客户端,暂时没有配对
                }else{
                    for(auto &pair:client_map){
                        if(pair.second==-1){
                            client_map[client_socket]=pair.first;//找到第一个没有配对的客户端,进行配对
                            client_map[pair.first]=client_socket;//更新第一个没有配对的客户端的配对信息
                            break;
                        }
                    }
                }
            }else{
                //处理客户端的消息转发
                int client_socket=events[i].data.fd;
                char buffer[BUFFER_SIZE];
                memset(buffer,0,sizeof(buffer));//清空缓冲区
                ssize_t bytes_received=recv(client_socket,buffer,sizeof(buffer),0);//接收客户端消息
                if(bytes_received<=0){
                    std::cerr<<"客户端断开连接"<<std::endl;
                    epoll_ctl(epoll_fd,EPOLL_CTL_DEL,client_socket,NULL);//从epoll中删除该客户端
                    close(client_socket);
                    continue;
                }

                std::cout<<"收到的消息:"<<buffer<<std::endl;

                //转发消息给配对的客户端
                int target_socket=client_map[client_socket];
                if(target_socket!=-1){
                    send(target_socket,buffer, strlen(buffer), 0);
                }else{
                    std::cerr<<"目标客户端未连接"<<std::endl;
                }
            }
        }
    }
    close(server_socket);

    return 0;
}

实验结果:

在这里插入图片描述

4. 实现说明

  • epoll创建:通过epoll_create创建一个epoll实例,epoll_ctl用于将服务器套接字添加到epoll监听中
  • 事件处理:使用epoll_wait等待客户端连接事件和消息事件,一旦有事件发生,处理新连接或消息收发
  • 客户端配对:与之前的多线程版本类似,使用client_map维护客户端之间的配对关系

5. 优势

  • 高并发支持:epoll适合大量客户端并发连接,比select或多线程处理效率更高
  • 事件驱动:基于事件通知的机制,而不是轮询,降低了CPU使用率
  • 资源高效:无需为每个连接创建独立线程,减少了上下文切换的开销

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

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

相关文章

[Deep-ML]Transpose of a Matrix(矩阵的转置)

Transpose of a Matrix&#xff08;矩阵的转置&#xff09; 题目链接&#xff1a; Transpose of a Matrix&#xff08;矩阵的转置&#xff09;https://www.deep-ml.com/problems/2 题目描述&#xff1a; 难度&#xff1a; easy&#xff08;简单&#xff09;。 分类&#…

智慧节能双突破 强力巨彩谷亚VK系列刷新LED屏使用体验

当前全球节能减排趋势明显&#xff0c;LED节能屏作为显示技术的佼佼者&#xff0c;正逐渐成为市场的新宠。强力巨彩谷亚万境VK系列节能智慧屏凭借三重技术保障、四大智能设计以及大师臻彩画质&#xff0c;在实现节能效果的同时&#xff0c;更在智慧显示领域树立新的标杆。   …

html 给文本两端加虚线自适应

效果图&#xff1a; <div class"separator">文本 </div>.separator {width: 40%;border-style: dashed;display: flex;align-items: center;color: #e2e2e2;font-size: 14px;line-height: 20px;border-color: #e2e2e2;border-width: 0; }.separator::bef…

leetcode4.寻找两个正序数组中的中位数

思路源于 LeetCode004-两个有序数组的中位数-最优算法代码讲解 基本思路是将两个数组看成一个数组&#xff0c;然后划分为两个部分&#xff0c;若为奇数左边部分个数多1&#xff0c;若为偶数左边部分等于右边部分个数。i表示数组1划分位置&#xff08;i为4是索引4也表示i的左半…

0101安装matplotlib_numpy_pandas-报错-python

文章目录 1 前言2 报错报错1&#xff1a;ModuleNotFoundError: No module named distutils报错2&#xff1a;ERROR:root:code for hash blake2b was not found.报错3&#xff1a;**ModuleNotFoundError: No module named _tkinter**报错4&#xff1a;UserWarning: Glyph 39044 …

OSCP - Proving Grounds- SoSimple

主要知识点 wordpress 插件RCE漏洞sudo -l shell劫持 具体步骤 依旧是nmap 起手&#xff0c;只发现了22和80端口&#xff0c;但80端口只能看到一张图 Nmap scan report for 192.168.214.78 Host is up (0.46s latency). Not shown: 65533 closed tcp ports (reset) PORT …

C语言求3到100之间的素数

一、代码展示 二、运行结果 三、感悟思考 注意: 这个题思路他是一个试除法的一个思路 先进入一个for循环 遍历3到100之间的数字 第二个for循环则是 判断他不是素数 那么就直接退出 这里用break 是素数就打印出来 在第一个for循环内 第二个for循环外

【2025】物联网发展趋势介绍

目录 物联网四层架构感知识别层网络构建层管理服务层——**边缘存储**边缘计算关键技术&#xff1a;综合应用层——信息应用 物联网四层架构 综合应用层&#xff1a;信息应用 利用获取的信息和知识&#xff0c;支持各类应用系统的运转 管理服务层&#xff1a;信息处理 对数据进…

如何查看 MySQL 的磁盘空间使用情况:从表级到数据库级的分析

在日常数据库管理中&#xff0c;了解每张表和每个数据库占用了多少磁盘空间是非常关键的。这不仅有助于我们监控数据增长&#xff0c;还能为性能优化提供依据。 Google Gemini中国版调用Google Gemini API&#xff0c;中国大陆优化&#xff0c;完全免费&#xff01;https://ge…

汇编学习之《移位指令》

这章节学习前需要回顾之前的标志寄存器的内容&#xff1a; 汇编学习之《标志寄存器》 算数移位指令 SAL (Shift Arithmetic Left)算数移位指令 : 左移一次&#xff0c;最低位用0补位&#xff0c;最高位放入EFL标志寄存器的CF位&#xff08;进位标志&#xff09; OllyDbg查看…

Nature Communications上交、西湖大学、复旦大学研发面向机器人多模式运动的去电子化刚弹耦合高频自振荡驱动单元

近年来&#xff0c;轻型仿生机器人因其卓越的运动灵活性与环境适应性受到国际机器人领域的广泛关注。然而&#xff0c;现有气动驱动器普遍受限于低模量粘弹性材料的回弹滞后效应与能量耗散特性&#xff0c;加之其"非刚即柔"的二元结构设计范式&#xff0c;难以同时满…

对备忘录模式的理解

对备忘录模式的理解 一、场景1、题目【[来源](https://kamacoder.com/problempage.php?pid1095)】1.1 题目描述1.2 输入描述1.3 输出描述1.4 输入示例1.5 输出示例 2、理解需求 二、不采用备忘录设计模式1、代码2、问题3、错误的备忘录模式 三、采用备忘录设计模式1、代码1.1 …

【数据结构】图的基本概念

图的定义 通俗来说一堆顶点被一堆线连在一起&#xff0c;这一坨顶点与线的集合 目录 图的定义 术语 有向图与无向图 简单图与多重图 度、入度与出度 路径与回路 路径长度与距离 子图 连通、连通图与连通分量 强连通、强连通图与强连通分量 完全图 生成树与生成森林 权…

激光加工中平面倾斜度的矫正

在激光加工中&#xff0c;加工平面的倾斜度矫正至关重要&#xff0c;直接影响加工精度和材料处理效果。以下是系统的矫正方法和步骤&#xff1a; 5. 验证与迭代 二次测量&#xff1a;加工后重新检测平面度&#xff0c;确认残余误差。 反馈优化&#xff1a;根据误差分布修正补偿…

rdiff-backup备份

目录 1. 服务器备份知识点 1.1 备份策略 1.2 备份步骤和宝塔面板简介 1.3 CentOS7重要目录 2. 备份工具 2.1 tar -g 备份演示 2. rsync 备份演示 3. rdiff-backup 备份演示 4. 差异和优缺点 3. rdiff-backup安装和使用 3.1 备份命令rdiff-backup 3.2 恢复命令--…

PE结构(十五)系统调用与函数地址动态寻找

双机调试 当需要分析一个程序时&#xff0c;这个程序一定是可以调试的&#xff0c;操作系统也不例外。在调试过程中下断点是很重要的 当我们对一个应用程序下断点时&#xff0c;应用程序是挂起的。但当我们对操作系统的内核程序下断点时&#xff0c;被挂起的不是内核程序而是…

webrtc 本地运行的详细操作步骤 1

前言 选修课的一个课程设计&#xff0c;需要我们本地运行这个开源项目&#xff0c;给我的压力非常大&#xff0c;因为确实不是很熟练这种操作。但是还是得做。谨以此文&#xff0c;纪念这个过程。 之前自己在 github 上面看到有代码仓库&#xff0c;但是比较复杂&#xff0c;在…

kali——httrack

目录 前言 使用教程 前言 HTTrack 是一款运行于 Kali Linux 系统中的开源网站镜像工具&#xff0c;它能将网站的页面、图片、链接等资源完整地下载到本地&#xff0c;构建出一个和原网站结构相似的离线副本。 使用教程 apt install httrack //安装httrack工具 httrac…

【计算机网络】Linux配置SNAT/DNAT策略

什么是NAT&#xff1f; NAT 全称是 Network Address Translation&#xff08;网络地址转换&#xff09;&#xff0c;是一个用来在多个设备共享一个公网 IP上网的技术。 NAT 的核心作用&#xff1a;将一个网络中的私有 IP 地址&#xff0c;转换为公网 IP 地址&#xff0c;从而…

AI安全:构建负责任且可靠的系统

AI已成为日常生活中无处不在的助力&#xff0c;随着AI系统能力和普及性的扩展&#xff0c;安全因素变得愈发重要。从基础模型构建者到采用AI解决方案的企业&#xff0c;整个AI生命周期中的所有相关方都必须共同承担责任。 为什么AI安全至关重要&#xff1f; 对于企业而言&…