基于udp协议的网络通信(windows客户端版+简易聊天室版),重定向到终端

news2024/9/22 21:24:17

目录

和windows通信

引入

思路 

WSADATA

代码

运行情况

简单的聊天室

思路

重定向

代码

terminal.hpp -- 重定向函数

服务端

客户端

运行情况


和windows通信

引入

linux和windows都需要联网,虽然他们系统设计不同,但网络部分一定是相同的,所以套接字也是一样的

  • 这里我们只需要写出windows风格的客户端即可,服务端仍然在linux上跑
  • 当然,除去套接字的部分,他们使用的接口和规则肯定是有区别的

思路 

套接字的部分不变,处理一下头尾即可

首先要引入winsock2.h头文件,并引入库文件

定义一个WSADATA结构并初始化(不同版本,看到的接口+底层代码也不同) 

WSADATA

  • 用于在 Windows 操作系统上开发网络应用程序时管理套接字(sockets)库的初始化和配置
  • 包含了关于 Winsock 环境的信息,例如 Winsock 版本、所支持的特性等
  • 使用WSAStartup初始化,WSACleanup来释放资源并并终止 Winsock 环境

修改完之后,就可以让linux和windows通信了

代码

这里用的是vs2019,加了两个define,防止报错(vs太安全了,汗)

可以看出来,中间的socket+收发数据绝大部分都是一样的,只有那么一两个类型的命名不同:

#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS


#include <stdio.h>
#include <winsock2.h>
#include <Windows.h>

#include<iostream>
#include<string>

#pragma comment(lib,"ws2_32.lib") //引入库文件

int main()
{
	//初始化网络环境
	WSADATA wsa;
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
	{
		printf("WSAStartup failed\n");
		return -1;
	}
	//建立一个udp的socket
	SOCKET socked = socket(AF_INET, SOCK_DGRAM, 0);
	if (socked == INVALID_SOCKET)
	{
		printf("create socket failed\n");
		return -1;
	}

	int port = 8080;
	std::string ip = "47.108.135.233";

	//创建结构体
	sockaddr_in addr = { 0 };
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.S_un.S_addr = inet_addr(ip.c_str());

	std::string info;
	char buffer[1024];
	memset(buffer, 0, sizeof(buffer));

	//收发数据
	while (true) {
		std::cout << "Please enter:";
		std::getline(std::cin, info);
		//发送数据
		int n = sendto(socked, info.c_str(), info.size(), 0, (SOCKADDR*)&addr, sizeof(SOCKADDR));
		if (n == 0)
		{
			printf("send failed\n");
			return -1;
		}
		sockaddr_in t = { 0 };
		int len = sizeof(sockaddr_in);
		// 接收数据
		n = recvfrom(socked, buffer, sizeof(buffer) - 1, 0, (SOCKADDR*)&t, &len);
		buffer[n] = 0;

		std::cout << buffer << std::endl;
		memset(buffer, 0, sizeof(buffer));
	}

	//关闭SOCKET连接
	closesocket(socked);
	//清理网络环境
	WSACleanup();
	return 0;
}

运行情况

我们成功在windows终端上与在linux下的服务端进行通信:

简单的聊天室

前面写的echo版就已经有聊天室的影子了,聊天软件的服务器的作用也就是向用户转发消息

这里我们修改一下就差不多了

思路

这里以ip地址和端口号作为每个人的标识(类似于用户名的作用),在echo版里其实就已经实现过了

但是echo版每个客户端的消息都是独立的

  • 聊天室的话,每个人在自己的客户端上都可以看见彼此发出的消息
  • 就需要我们将每条消息发送给所有运行起来的客户端
  • 可以考虑创建一个在线用户表(ip,结构体对象) -- 每收到一个消息,就转发给所有注册在表中的用户
  • 如果有登录功能的话,应该是登录后转发
  • 这里稍微模拟一下登录过程 -- 当客户端运行起来后,有一句打印,且直接将该条打印语句发送给服务器,并且直接注册在表中(简易版嘛)

服务器修改好后,客户端就出现问题了

  • 还记得我们的客户端吗,它的第一个函数就是getline
  • 如果不发送消息的话,就会卡在那里不会往下走,也就无法调用下面的recvfrom函数,也就无法看见其他用户发送的数据
  • 而udp协议是全双工的(它支持边读边写)
  • 所以我们可以将客户端修改为多线程,一个读,一个写,这样就互不干扰了

虽然解决了收发消息的问题,但是客户端仅有一个窗口,这样直接打印的话,会导致输入消息和输出的消息混在一块

  • 而聊天室一般是分为上下两部分,上面是所有人发送的消息,下面是自己的输入框
  • 综合我们是在终端上显示,可以开俩终端,拼接在一起作为我们的界面,输入和输出在不同终端上工作
  • 实现的话 -- /dev/pts里是终端文件
  • 当我们打开xshell:
  • 如果再开一个会话:

重定向

  • 我们如果试着将数据重定向(dup2函数)到终端文件里,就可以看见自己的终端显示出了数据:

  • 这样,我们就可以通过重定向,先确定当前终端属于哪个文件

  • 然后就可以利用这个(也就是将数据重定向到终端文件里,而不是显示器),实现聊天室的分块显示

  • 但如果输出数据时将fd=1的显示器重定向到终端文件1里,那么输入数据时/其他时候的打印,都会到那个终端文件里,而不会像我们预想的那样分成两个模块

  • 所以,我们将标准错误重定向到其中一个终端文件里,另一个终端运行客户端,这样cout时会默认打印到当前终端里,就不会互相影响了

  • 重定向既可以在代码中使用dup2函数,也可以直接在命令行中重定向(a>b)

代码

terminal.hpp -- 重定向函数

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

std::string terminal = "/dev/pts/2";

void my_dup()
{
    int fd = open(terminal.c_str(), O_WRONLY);
    if (fd < 0)
    {
        perror("open");
        exit(1);
    }
    dup2(fd, 2);
    close(fd);
}

服务端

这里增加了用户表和chat函数(聊天室专用启动函数)

#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <strings.h>
#include <cstring>

#include <string>
#include <functional>
#include <map>
#include <iostream>

#include "Log.hpp"

extern std::string get_time();

Log lg;

const int buff_size = 1024;
using func_t = std::function<std::string(const std::string &)>;

enum
{
    SOCKET_ERR = 1,
    BIND_ERR = 2
};

// 启动服务器时,传入ip地址和端口号
// 手动启动

class udp_server
{
public:
    udp_server(const uint16_t port = 8080, const std::string ip = "0.0.0.0")
        : ip_(ip), port_(port), sockfd_(0)
    {
    }
    void run(func_t func)
    {
        init();
        // 开始收发数据
        char buffer[buff_size];
        std::string message;
        while (true)
        {
            memset(buffer, 0, sizeof(buffer));

            struct sockaddr_in src_addr;
            socklen_t src_len = sizeof(src_addr);

            // 获取数据
            ssize_t n = recvfrom(sockfd_, buffer, sizeof(buffer) - 1, 0, reinterpret_cast<struct sockaddr *>(&src_addr), &src_len);
            if (n < 0)
            {
                lg(WARNING, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));
                continue;
            }
            buffer[n] = 0;

            std::string id = generate_id(inet_ntoa(src_addr.sin_addr), ntohs(src_addr.sin_port));
            message = id + "sever recvfrom success";
            lg(INFO, message.c_str());

            // 处理数据
            std::string echo_info = func(buffer);

            // 响应给发送端
            sendto(sockfd_, echo_info.c_str(), echo_info.size(), 0, reinterpret_cast<const struct sockaddr *>(&src_addr), src_len);

            message = id + "sever sendto success";
            lg(INFO, message.c_str());
        }
    }
    void chat()
    {
        init();
        // 开始收发数据
        char buffer[buff_size];
        memset(buffer, 0, sizeof(buffer));
        std::string message;

        while (true)
        {
            struct sockaddr_in src_addr;
            socklen_t src_len = sizeof(src_addr);

            // 获取数据
            ssize_t n = recvfrom(sockfd_, buffer, sizeof(buffer) - 1, 0, reinterpret_cast<struct sockaddr *>(&src_addr), &src_len);
            char ip[30];
            // std::cout << inet_ntop(AF_INET, &(src_addr.sin_addr), ip, sizeof(ip) - 1)<<std::endl;

            if (n < 0)
            {
                lg(WARNING, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));
                continue;
            }
            buffer[n] = 0;
            // std::cout << buffer << std::endl;
            usr_[src_addr.sin_addr.s_addr] = src_addr; // 注册用户表
            // for (auto it : usr_)
            // {
            //     std::cout << inet_ntop(AF_INET, &((it.second).sin_addr), ip, sizeof(ip) - 1) << std::endl;
            // }

            std::string id = generate_id(inet_ntoa(src_addr.sin_addr), ntohs(src_addr.sin_port));
            message = id + "sever recvfrom success";
            lg(INFO, message.c_str());

            // 处理数据
            std::string time_stamp = get_time();
            std::string echo_info = id + time_stamp + buffer;
            memset(buffer, 0, sizeof(buffer));

            // 响应给所有用户端
            send_all(echo_info);

            message = id + "sever sendto success";
            lg(INFO, message.c_str());
        }
    }
    ~udp_server()
    {
        if (sockfd_ > 0)
        {
            close(sockfd_);
        }
    }
    static std::string get_id()
    {
        udp_server obj;
        return obj.generate_id(obj.ip_, obj.port_);
    }

private:
    std::string generate_id(const std::string ip, const uint16_t port)
    {
        return "[" + ip + ":" + std::to_string(port) + "]";
    }
    void init()
    {
        // 创建套接字文件
        sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd_ < 0)
        {
            lg(FATAL, "socket create error, sockfd : %d,%s", sockfd_, strerror(errno));
            exit(SOCKET_ERR);
        }

        // 创建sockaddr结构
        struct sockaddr_in addr;
        socklen_t len = sizeof(addr);
        bzero(&addr, len);

        addr.sin_addr.s_addr = inet_addr(ip_.c_str());
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port_);

        // 绑定套接字信息
        int res = bind(sockfd_, reinterpret_cast<const struct sockaddr *>(&addr), len);
        if (res < 0)
        {
            lg(FATAL, "bind error, sockfd : %d,%s", sockfd_, strerror(errno));
            exit(BIND_ERR);
        }
        lg(INFO, "bind success, sockfd : %d", sockfd_);
    }
    void send_all(const std::string &echo_info)
    {
        char ip[30];
        for (auto it : usr_)
        {
            // std::cout << inet_ntop(AF_INET, &((it.second)->sin_addr), ip, sizeof(ip) - 1)<<std::endl;
            sendto(sockfd_, echo_info.c_str(), echo_info.size(), 0, reinterpret_cast<const struct sockaddr *>(&(it.second)), sizeof(it.second));
        }
    }

private:
    int sockfd_;
    std::string ip_;
    uint16_t port_;
    std::map<in_addr_t, struct sockaddr_in> usr_; //不能是指针,这样下次循环时,指针就换成新的客户端了
};

客户端

分出了写函数和读函数,chat函数中创建两个线程,让他们运行

#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <functional>
#include <strings.h>
#include <cstring>

#include <string>
#include <iostream>

#include <pthread.h>

#include "Log.hpp"
#include "terminal.hpp"

const int buff_size = 1024;

Log lg;

enum
{
    SOCKET_ERR = 1,
    BIND_ERR = 2
};

// 客户端需要提前知道服务端的套接字地址信息
// 日常生活中,我们一般直接通过网址进入,网址就是ip地址,且它会直接和端口号绑定
// 所以,这里我们只能自己手动提供服务端的ip和端口号

// 客户端不需要手动创建套接字,os会自动为我们提供(在首次发送数据时)
struct data
{
    int sockfd_;
    struct sockaddr_in *paddr_;
    socklen_t len_;
};

class udp_client
{
public:
    udp_client(const uint16_t port = 8080, const std::string ip = "47.108.135.233")
        : ip_(ip), port_(port), sockfd_(0)
    {
    }
    void run()
    {
        data *d = init();

        std::string info;
        char buffer[buff_size];
        memset(buffer, 0, sizeof(buffer));
        while (true)
        {
            std::cout << "Please enter:";
            std::getline(std::cin, info);

            // 将消息发送给服务器
            sendto(d->sockfd_, info.c_str(), info.size(), 0, reinterpret_cast<const struct sockaddr *>(d->paddr_), d->len_);
            info.clear();

            struct sockaddr_in addr; // 仅用于填充参数,拿到自己的地址信息没啥意义
            socklen_t len = sizeof(addr);

            // 获取数据
            ssize_t n = recvfrom(d->sockfd_, buffer, sizeof(buffer) - 1, 0, reinterpret_cast<struct sockaddr *>(&addr), &len);
            if (n < 0)
            {
                lg(WARNING, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));
                continue;
            }
            buffer[n] = 0;

            std::cout << buffer << std::endl;
            memset(buffer, 0, sizeof(buffer));
        }
    }
    void chat()
    {
        data *d = init();

        pthread_t r = 0, w = 0;
        pthread_create(&r, nullptr, input, d);
        pthread_create(&w, nullptr, output, d);

        pthread_join(r, nullptr);
        pthread_join(w, nullptr);
    }

private:
    data *init()
    {
        // 创建套接字文件
        int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd < 0)
        {
            lg(FATAL, "socket create error, sockfd : %d", sockfd);
            exit(SOCKET_ERR);
        }

        // 创建sockaddr结构
        struct sockaddr_in *svr_paddr = new sockaddr_in;
        socklen_t svr_len = sizeof(*svr_paddr);
        bzero(svr_paddr, svr_len);

        inet_aton(ip_.c_str(), &(svr_paddr->sin_addr));
        svr_paddr->sin_family = AF_INET;
        svr_paddr->sin_port = htons(port_);

        return new data({sockfd, svr_paddr, svr_len});
    }
    static void *input(void *args)
    {
        data *d = reinterpret_cast<data *>(args);

        char ip[30];
        inet_ntop(AF_INET, &((d->paddr_)->sin_addr), ip, sizeof(ip) - 1);

        std::string welcome = "comming...";

        sendto(d->sockfd_, welcome.c_str(), welcome.size(), 0, reinterpret_cast<const struct sockaddr *>(d->paddr_), d->len_);

        std::string info;
        while (true)
        {
            std::cout << "Please enter:";
            std::getline(std::cin, info);

            // 将消息发送给服务器
            sendto(d->sockfd_, info.c_str(), info.size(), 0, reinterpret_cast<const struct sockaddr *>(d->paddr_), d->len_);
            info.clear();
        }
        return nullptr;
    }
    static void *output(void *args)
    {
        data *d = reinterpret_cast<data *>(args);
        // my_dup();

        char buffer[buff_size];
        memset(buffer, 0, sizeof(buffer));
        while (true)
        {
            struct sockaddr_in addr; // 仅用于填充参数,拿到自己的地址信息没啥意义
            socklen_t len = sizeof(addr);

            // 获取数据(所有用户的消息都会获取)
            ssize_t n = recvfrom(d->sockfd_, buffer, sizeof(buffer) - 1, 0, reinterpret_cast<struct sockaddr *>(&addr), &len);
            if (n < 0)
            {
                lg(WARNING, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));
                continue;
            }
            buffer[n] = 0;

            std::cerr << buffer << std::endl;
            memset(buffer, 0, sizeof(buffer));
        }
        return nullptr;
    }

private:
    int sockfd_;
    std::string ip_;
    uint16_t port_;
};

两个cpp函数之间构建相应cs的对象+调用chat函数即可 

运行情况

手动重定向(这个适合在其他主机上运行客户端,因为每个人打开的终端不一定正好有2,测试后进行手动重定向最好)

在代码内重定向:

下图是两个云服务器之间进行通信:

大家也可以下载文件试试,只要有两个执行文件+client文件执行时进行手动重定向(分好两个终端屏幕,确定好各自的编号),就能通信

(也就是说总共需要运行三个终端)

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

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

相关文章

Diffblue生成单元测试

什么是Diffblue Diffblue Cover 是一个强化学习 AI 平台&#xff0c;可自动编写全面的、类似人类的 Java 单元测试&#xff0c;从而节省开发人员时间、提高测试覆盖率并降低回归风险。Cover 以 IntelliJ IDE 插件&#xff08;Cover Plugin&#xff09;、CLI 应用程序&#xff…

中等职业学校人工智能技术与应用(710212)专业解决方案

一、专业背景 1.1人工智能确立为国家战略 近年来&#xff0c;人工智能技术取得了显著进展&#xff0c;不仅在理论层面有所突破&#xff0c;更在实际应用中展现出强大的潜力和价值。其发展趋势日益明显&#xff0c;展现出多方面的特点。“深度学习”与“大数据”的结合成为当前…

QT信号与槽实现方式

1、第一种实现方式 在QT开发工具UI界面先拖入按钮&#xff0c;然后鼠标右键拖入按钮&#xff0c;点击选中槽&#xff0c;在页面选着需要的信号&#xff0c;然后OK&#xff0c;随即将会跳转到类的.cpp文件&#xff0c;&#xff08;这种UI代码结合的方式&#xff0c;会自动去绑定…

力扣101---对称二叉树(简单题)

题目描述&#xff1a; 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true示例 2&#xff1a; 输入&#xff1a;root [1,2,2,null,3,null,3] 输出&#xff1a;false 非递归Java代…

Jenkins cron定时构建触发器

from&#xff1a; https://www.jenkins.io/doc/book/pipeline/syntax/#cron-syntax 以下内容为根据Jenkins官方文档cron表达式部分翻译过来&#xff0c;使用机翻加个人理解补充内容&#xff0c;包括举例。 目录 介绍举例&#xff1a;设置方法方法一&#xff1a;方法二&#xf…

通过简单的案例入门Mybatis~

目录 一.概述 二.JDBC的缺点 三.案例 1.创建测试类 2.加载Mybatis核心配置文件获取SqlSessionFactory 3.获取SqlSession对象 4.执行sql 5.释放资源 一.概述 Mybatis是一款持久层框架&#xff0c;用于简化JDBC开发。所谓框架&#xff0c;就是一个半成品软件&#xff0c;…

Redis管道和发布订阅

解决思路 类似mset打包批量处理 定义&#xff1a;Pipeline是为了解决RTT往返时延&#xff0c;仅仅将命令打包一次性发送&#xff0c;对Redis的执行不造成其他影响 需要把批量执行的命令都放到文件里面 命令解释 上一条命令的结果作为参数 通过Linux命令的管道 传输给管道后面…

Java代码基础算法练习---2024.3.14

其实这就是从我学校的资源&#xff0c;都比较基础的算法题&#xff0c;先尽量每天都做1-2题&#xff0c;练手感。毕竟离我真正去尝试入职好的公司&#xff08;我指的就是中大厂&#xff0c;但是任重道远啊&#xff09;&#xff0c;仍有一定的时间&#xff0c;至少要等我升本之后…

OCR文字识别训练数据整理的关键步骤和注意事项

在开发OCR文字识别产品时&#xff0c;为了确保准确性和可靠性&#xff0c;需要精心整理训练数据。特别是针对合同、证件、票据等文档类型&#xff0c;更需要有针对性地准备文字语料库。以下是针对OCR文字识别训练数据整理的一些建议&#xff1a; ### 整理训练数据的步骤&#…

使用kill()函数向进程发送信号

本片文章的学习记录总结来源于&#xff1a;https://www.bilibili.com/cheese/play/ep182660?csourcecommon_hp_history_null&t11&spm_id_from333.1007.top_right_bar_window_history.content.click 通常在Linux系统中&#xff0c;可以使用 kill or killall 命令向指定…

如何保证Redis和数据库数据一致性

缓存可以提升性能&#xff0c;减轻数据库压力&#xff0c;在获取这部分好处的同时&#xff0c;它却带来了一些新的问题&#xff0c;缓存和数据库之间的数据一致性问题。 想必大家在工作中只要用了咱们缓存势必就会遇到过此类问题 首先我们来看看一致性&#xff1a; 强一致性…

使用SpaceDesk实现iPad成为电脑拓展屏(保姆级教程)

使用SpaceDesk实现iPad成为电脑拓展屏 SpaceDesk是一个开源的软件, 所以说对学生和平民用户非常的友好, 连接后的画质也非常不错, 而且具有无线和有线两种连接方式. 接下来就开始教程: 1. 安装SpaceDesk电脑版 首先我们要下载SpaceDesk电脑版安装好: SpaceDesk官网 注意: …

IBM:《2024年消费者调研:无处不在的人工智能彻底变革零售业》

1月17日&#xff0c;IBM商业价值研究院最近发布了第三份两年一度的消费者调研报告。 这项名为《无处不在的人工智能彻底改变零售业&#xff1a;客户不会等待》的报告&#xff0c;对包含中国在内的全球近20000名消费者进行了调研&#xff0c;相关结果反映了消费者对零售体验的普…

C语言从入门到实战————数组和指针的深入理解

前言 在C语言中&#xff0c;数组和指针有的密切得联系&#xff0c;因为数组名本身就相当于一个指针常量。指针是一个变量&#xff0c;专门用来存储另一个变量的内存地址&#xff0c;通过这个地址可以访问和操作该变量的值&#xff0c;同时也包括数组。数组是一组连续存储的同类…

three.js 入门四:THREE.BoxGeometry 解析

环境&#xff1a; three.js 0.159.0 问题&#xff1a;如何理解BoxGeometry? BoxGeometry在创建的时候, 内部划分成了6个小组, 每个小组就是一个面有4个点, 假设长宽深的segments都是1的话(默认是1), 那么每个小组拥有2个三角面, 如下面创建的代码: var geometry new THRE…

AI智能分析网关V4烟火识别技术在沿街商铺消防安全管理中的应用

一、背景需求 2024年3月13日&#xff0c;廊坊三河市燕郊镇一炸鸡店发生疑似燃气泄漏引发的爆燃事故&#xff0c;应急、消防、卫健等有关部门第一时间赶赴现场全力救援处置。目前现场搜救工作已经结束&#xff0c;事故善后处置、原因调查等工作正在进行中。本次事故也引发了社会…

AI-逻辑回归模型

&#x1f606;&#x1f606;&#x1f606;感谢大家的支持~&#x1f606;&#x1f606;&#x1f606; 逻辑回归的应用场景 逻辑回归&#xff08;Logistic Regression&#xff09;是机器学习中的 一种分类模型 &#xff0c;逻辑回归是一种分类算法&#xff0c;虽然名字中带有回…

家具销售电商平台|基于springboot框架+ Mysql+Java+Tomcat的家具销售电商平台设计与实现(可运行源码+数据库+设计文档)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 目录 前台功能效果图 用户功能模块 管理员功能登录前台功能效果图 系统功能设计 数据库E-R图设计 lunwen…

CXL-Enabled Enhanced Memory Functions——论文阅读

IEEE Micro 2023 Paper CXL论文阅读笔记整理 问题 计算快速链路&#xff08;CXL&#xff09;协议是系统社区的一个重要里程碑。CXL提供了标准化的缓存一致性内存协议&#xff0c;可用于将设备和内存连接到系统&#xff0c;同时保持与主机处理器的内存一致性。CXL使加速器&…

瑞_23种设计模式_策略模式

文章目录 1 策略模式&#xff08;Strategy Pattern&#xff09;★1.1 介绍1.2 概述1.3 策略模式的结构1.4 策略模式的优缺点1.5 策略模式的使用场景 2 案例一2.1 需求2.2 代码实现 3 案例二3.1 需求3.2 代码实现 4 JDK源码解析&#xff08;Comparator&#xff09; &#x1f64a…