Unity网络开发基础 (3) Socket入门 TCP同步连接 与 简单封装练习

news2025/5/10 17:51:35

               本文章不作任何商业用途 仅作学习与交流 教程来自Unity唐老狮

        关于练习题部分是我观看教程之后自己实现 所以和老师写法可能不太一样

        唐老师说掌握其基本思路即可,因为前端程序一般不需要去写后端逻辑

1.认识Socket的重要API

Socket是什么

   Socket(套接字)是计算机网络编程中用于实现网络通信的一种机制,它提供了不同主机之间进行数据交换的接口。通过 Socket,程序可以在网络上发送和接收数据,实现客户端与服务器之间的通信。在 .NET 框架中,Socket 类位于 System.Net.Sockets 命名空间,它封装了底层的网络通信细节,使得开发者能够方便地进行网络编程

创建Socket

Socket serverTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream,ProtocolType.Tcp);
参数含义常见取值及说明
AddressFamily网络地址类型InterNetwork:IPv4;InterNetworkV6:IPv6
SocketType通信方式Stream:面向连接,用 TCP;Dgram:无连接,用 UDP
ProtocolType传输协议Tcp:配合 Stream 实现可靠通信;Udp:配合 Dgram 实现低延迟通信

Socket常用属性

检查接收缓冲区可用字节数

Socket.Available

获取服务端本地绑定的地址信息

 IPEndPoint localEp = clientSocket.LocalEndPoint as IPEndPoint;
        Console.WriteLine($"服务端本地地址:{localEp.Address}, 端口:{localEp.Port}");
属性名称定义与作用返回值类型使用说明
Available获取当前接收缓冲区中可读取的字节数,用于判断当前有多少网络数据已到达且可被读取int直接返回可用字节数,如 int availableBytes = socketTcp.Available;,辅助优化数据读取逻辑
LocalEndPoint获取套接字绑定的本地网络端点(包含本地 IP 地址和端口号)object需强制转换为 IPEndPoint 使用,如:
IPEndPoint localIpEndPoint = socketTcp.LocalEndPoint as IPEndPoint;
转换后可通过 .Address 取 IP,.Port 取端口

Socket常用方法

服务端

步骤说明关键代码
1-1绑定 IP 和端口,为服务端指定通信地址与端口IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);
socketTcp.Bind(ipPoint);
1-2设置允许同时连接的客户端最大数量,控制服务端连接队列长度socketTcp.Listen(10);
1-3阻塞等待客户端连接,成功连接后返回与客户端通信的专属套接字socketTcp.Accept();

客户端

步骤说明关键代码
客户端发起连接让客户端套接字与目标服务端(通过 IP 地址和端口标识)建立网络连接,构建通信链路socketTcp.Connect(IPAddress.Parse("118.12.123.11"), 8080);

通用

分类步骤说明关键代码
客户端服务端通用操作1释放连接并关闭 Socket,需先调用 ShutdownsocketTcp.Shutdown(SocketShutdown.Both);
2关闭连接,释放所有 Socket 关联资源socketTcp.Close();
3同步发送和接收数据

通常如 socketTcp.Send(字节数组); 

socketTcp.Receive(字节数组);

4异步发送和接收数据通常涉及 BeginSend BeginReceive 等异步方法,如 socketTcp.BeginReceive(回调, 状态对象);

2.TCP连接(UDP同理)

服务端: 注意 服务端创建了两个套接字

一个是服务端本体用于绑定、监听客户端连接

一个是通道 用于对 客户端 收发数据

        当然只是一个客户端的情况 如果有多个客户端 你可以将channel直接命名为对应客户端的名字

如图所示:

127.0.0.1是特殊地址 也就是 回环地址 可以不需要网络 就让本机成为服务端

using System.Net;
using System.Net.Sockets;
using System.Text;
//服务器端套接字流程

//1 创建服务端套接字
Socket serverTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream,ProtocolType.Tcp);

//2 服务端绑定门牌号 并 监听请求客户端连接 "127.0.0.1"是回环地址
IPEndPoint sIpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"),8080);
serverTcp.Bind(sIpoint);
serverTcp.Listen(1024);
Console.WriteLine("等待客户端连接");

//3 建立连接和等待返回  通道套接字 (三次握手以后,所以该处会阻塞线程)
Socket channelSocket = serverTcp.Accept();
Console.WriteLine("未建立连接时 不会打印这句话");

//4 通过中间套接字 进行数据收发 
channelSocket.Send(Encoding.UTF8.GetBytes("服务端:欢迎连接服务端"));

//服务端接收时需要有容器
byte[] result = new byte[1024]; 
int receiveNums = channelSocket.Receive(result);//返回接收的字节数
Console.WriteLine($"获取的客户端Ip和端口{channelSocket.RemoteEndPoint}," +
    $"接受到的消息为:{Encoding.UTF8.GetString(result, 0, receiveNums)}");

//5 释放连接
channelSocket.Shutdown(SocketShutdown.Both);
//6 关闭套接字
channelSocket.Close();

关于同步阻塞线程问题 

客户端:客户端只需要自己的套接字即可

using System.Net;
using System.Net.Sockets;
using System.Text;
using UnityEngine;

public class Socatct : MonoBehaviour
{
    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        //Socket clientUdp = new Socket(AddressFamily.InterNetwork,SocketType.Dgram,ProtocolType.Udp);
        //Socket clientTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        //1 创建客户端套接字
        Socket clientTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        //2 通过IPEndPoint确定服务端ip和端口
        IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);

        //3 尝试连接
        clientTcp.Connect(ipPoint);

        //4 客户端接受服务端数据 和 发送数据  
        byte[] receiveBytes = new byte[1024];
        int resultNum = clientTcp.Receive(receiveBytes);

        clientTcp.Send(Encoding.UTF8.GetBytes("这句话是客户端发来的"));

        //打印服务端来的信息
        Debug.Log(Encoding.UTF8.GetString(receiveBytes,0, resultNum));

        //5 释放连接和关闭套接字
        clientTcp.Shutdown(SocketShutdown.Both);
        clientTcp.Close();
    }
}

图解

3.服务端练习题

问题1:

using System.Net;
using System.Net.Sockets;
using System.Text;


Server server = new Server();

server.Init(true,false);
server.CloseAllCannel();
//服务端类
class Server
{
    public Socket serverSocket;
    public List<Socket> cannelSockets = new List<Socket>();
    private Thread? acceptThread;
    private Thread? getMsgThread;
    private readonly object _lockObject = new object();

    /// <summary>
    /// 初始化 选择TCP还是UDP连接
    /// </summary>
    /// <param name="tcp"></param>
    /// <param name="udp"></param>
    public void Init(bool tcp, bool udp)
    {
        if (tcp)
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        if (udp)
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

        BindIPEndPoint();
        InitThread();
    }

    /// <summary>
    /// 绑定IP地址和端口号
    /// </summary>
    /// <param name="ip"></param>
    /// <param name="port"></param>
    private void BindIPEndPoint(string ip = "127.0.0.1", int port = 8080)
    {
        IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse(ip), port);

        serverSocket.Bind(iPEndPoint);

        serverSocket.Listen(1024);

        Console.WriteLine("等待客户端连接");

    }
    private void InitThread()
    {
        acceptThread = new Thread(AcceptClient);
        acceptThread.Start();
        getMsgThread = new Thread(GetMsgFromClient);
        getMsgThread.Start();
    }
    /// <summary>
    /// 等待客户端连接
    /// </summary>
    private void AcceptClient()
    {
        while (true)
        {
            Socket cannelSocket = serverSocket.Accept();
            // 默认发送消息
            cannelSocket.Send(Encoding.UTF8.GetBytes("欢迎来到服务器"));
            lock (_lockObject)
            {
                cannelSockets.Add(cannelSocket);
            }
        }
    }

    private void GetMsgFromClient()
    {
        //容器 1024 * 1Kb = 1Mb
        byte[] buffer = new byte[1024 * 1024];
        int length;

        while (true)
        {
            List<Socket> socketsCopy;
            lock (_lockObject)
            {
                socketsCopy = new List<Socket>(cannelSockets);
            }
            foreach (Socket cannelSocket in socketsCopy)
            {
                if (cannelSocket.Available > 0) //有数据可读
                {
                    length = cannelSocket.Receive(buffer);
                    //消息处理交给新线程
                    ThreadPool.QueueUserWorkItem(HandleMsg, (cannelSocket, Encoding.UTF8.GetString(buffer, 0, length)));
                }
            }
        }

    }
    private void HandleMsg(object msg)
    {
        (Socket s, string srt) info = ((Socket s, string srt))msg;
        Console.WriteLine($"客户端IP以及端口号=>{info.s.RemoteEndPoint}" +
            $"发送消息:{info.srt}");
    }

    public void CloseAllCannel()
    {
        string input = Console.ReadLine();
        while (true)
        {
            if (input == "Quit")
            {
                lock (_lockObject)
                {
                    foreach (Socket cannelSocket in cannelSockets)
                    {
                        cannelSocket.Shutdown(SocketShutdown.Both);
                        cannelSocket.Close();
                    }
                    cannelSockets.Clear();
                    Console.WriteLine("已关闭所有连接");
                }
                break;
            }
        }
    }
}

问题2 

就是把服务端的Channel(也就是对应Client的通道 封装成一个类) 

using System.Net;
using System.Net.Sockets;
using System.Text;

Server server = new Server();
server.Init(true, false);
server.CloseAllCannel();

// 通道类,封装channelSocket相关操作
class Channel
{
    private readonly Socket _socket;
    private readonly byte[] _buffer = new byte[1024 * 1024];

    public Channel(Socket socket)
    {
        _socket = socket;
    }

    // 发送消息
    public void SendMessage(string message)
    {
        byte[] data = Encoding.UTF8.GetBytes(message);
        _socket.Send(data);
    }

    // 接收消息(异步处理更合适,这里简化示例)
    public string? ReceiveMessage()
    {
        if (_socket.Available > 0)
        {
            int length = _socket.Receive(_buffer);
            return Encoding.UTF8.GetString(_buffer, 0, length);
        }
        return null;
    }

    // 关闭通道
    public void Close()
    {
        _socket.Shutdown(SocketShutdown.Both);
        _socket.Close();
    }

    public IPEndPoint RemoteEndPoint => (IPEndPoint)_socket.RemoteEndPoint!;
}

// 服务端类
class Server
{
    public Socket serverSocket;
    private List<Channel> channelList = new List<Channel>();
    private Thread? acceptThread;
    private Thread? getMsgThread;
    private readonly object _lockObject = new object();

    /// <summary>
    /// 初始化 选择TCP还是UDP连接
    /// </summary>
    /// <param name="tcp"></param>
    /// <param name="udp"></param>
    public void Init(bool tcp, bool udp, string ip = "127.0.0.1", int port = 8080)
    {
        if (tcp)
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        if (udp)
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

        BindIPEndPoint(ip, port);
        InitThread();
    }

    /// <summary>
    /// 绑定IP地址和端口号
    /// </summary>
    /// <param name="ip"></param>
    /// <param name="port"></param>
    public void BindIPEndPoint(string ip, int port)
    {
        IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse(ip), port);
        serverSocket.Bind(iPEndPoint);
        serverSocket.Listen(1024);
        Console.WriteLine("等待客户端连接");
    }

    private void InitThread()
    {
        acceptThread = new Thread(AcceptClient);
        acceptThread.Start();
        getMsgThread = new Thread(GetMsgFromClient);
        getMsgThread.Start();
    }

    /// <summary>
    /// 等待客户端连接
    /// </summary>
    private void AcceptClient()
    {
        while (true)
        {
            Socket channelSocket = serverSocket.Accept();
            Channel channel = new Channel(channelSocket);
            channel.SendMessage("欢迎来到服务器");
            lock (_lockObject)
            {
                channelList.Add(channel);
            }
        }
    }

    private void GetMsgFromClient()
    {
        while (true)
        {
            List<Channel> channelsCopy;
            lock (_lockObject)
            {
                channelsCopy = new List<Channel>(channelList);
            }
            foreach (Channel channel in channelsCopy)
            {
                string? msg = channel.ReceiveMessage();
                if (msg != null)
                {
                    // 消息处理交给新线程
                    ThreadPool.QueueUserWorkItem(HandleMsg, (channel, msg));
                }
            }
        }
    }

    private void HandleMsg(object state)
    {
        (Channel channel, string msg) info = ((Channel channel, string msg))state;
        Console.WriteLine($"客户端IP以及端口号=>{info.channel.RemoteEndPoint} 发送消息:{info.msg}");
    }

    public void CloseAllCannel()
    {
        string input = Console.ReadLine();
        while (true)
        {
            if (input == "Quit")
            {
                lock (_lockObject)
                {
                    foreach (Channel channel in channelList)
                    {
                        channel.Close();
                    }
                    channelList.Clear();
                    Console.WriteLine("已关闭所有连接");
                }
                break;
            }
        }
    }
}

4.客户端 练习题

问题:

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;

public class ClientManager
{
    private Socket clientTcp;
    private Thread receiveThread;
    private bool isRunning;

    public void Connect(string ip, int port)
    {
        try
        {
            // 1 创建客户端套接字
            clientTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            // 2 通过 IPEndPoint 确定服务端 ip 和端口
            IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse(ip), port);

            // 3 尝试连接
            clientTcp.Connect(ipPoint);

            isRunning = true;
            // 启动接收线程
            receiveThread = new Thread(ReceiveData);
            receiveThread.Start();
        }
        catch (Exception e)
        {
            Debug.LogError($"连接失败: {e.Message}");
        }
    }

    private void ReceiveData()
    {
        try
        {
            while (isRunning)
            {
                byte[] receiveBytes = new byte[1024];
                int resultNum = clientTcp.Receive(receiveBytes);

                if (resultNum > 0)
                {
                    // 打印服务端来的信息
                    string message = Encoding.UTF8.GetString(receiveBytes, 0, resultNum);
                    Debug.Log($"接收到服务端消息: {message}");
                }
            }
        }
        catch (Exception e)
        {
            Debug.LogError($"接收数据出错: {e.Message}");
        }
    }

    public void SendMessage(string message)
    {
        try
        {
            if (clientTcp != null && clientTcp.Connected)
            {
                clientTcp.Send(Encoding.UTF8.GetBytes(message));
            }
        }
        catch (Exception e)
        {
            Debug.LogError($"发送消息出错: {e.Message}");
        }
    }

    public void Disconnect()
    {
        isRunning = false;
        if (receiveThread != null && receiveThread.IsAlive)
        {
            receiveThread.Join();
        }

        if (clientTcp != null && clientTcp.Connected)
        {
            // 5 释放连接和关闭套接字
            clientTcp.Shutdown(SocketShutdown.Both);
            clientTcp.Close();
        }
    }
}

使用:

using UnityEngine;

public class UseSocketClient练习题 : MonoBehaviour
{
    ClientManager clientManager;
    void Start()
    {
         clientManager = new ClientManager();
        // 修改连接的 IP 地址和端口号
        clientManager.Connect("127.0.0.1",8080);
        // 修改发送的消息内容
        clientManager.SendMessage("这是修改后的消息");
    }

    void OnDestroy()
    {
        clientManager.Disconnect();
    }
}

 测试

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

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

相关文章

做题记录:和为K的子数组

来自leetcode 560 前言 自己只会暴力&#xff0c;这里就是记录一下前缀和哈希表的做法&#xff0c;来自灵神的前缀和哈希表&#xff1a;从两次遍历到一次遍历&#xff0c;附变形题 正文 首先&#xff0c;这道题无法使用滑动窗口&#xff0c;因为滑动窗口需要满足单调性&am…

VMware虚拟机卡顿、CPU利用率低、编译Linux内核慢,问题解决与实验对比

目录 一、总结在前面&#xff08;节约时间就只看这里&#xff09;0 环境说明1 遇到的问题&#xff1a;2 问题的原因&#xff1a;3 解决办法&#xff1a;4 实验验证&#xff1a;5 关于虚拟机内核数量设置6 关于强行指定Vm能用的CPU内核 二、管理员启动&#xff0c;实验对比实验1…

【7】数据结构的队列篇章

目录标题 队列的定义顺序队列的实现初始化入队出队顺序队列总代码与调试 循环队列的实现初始化入队出队获取队首元素循环队列总代码与调试 链式队列的实现链式队列的初始化入队出队获取队首元素链式队列总代码与调试 队列的定义 定义&#xff1a;队列&#xff08;Queue&#x…

颜色归一化操作

当我们不太关注图像具体细节&#xff0c;只关注图像大致的内容时&#xff0c;为了避免光照角度、光照强度对图像的影响&#xff0c;可以采用下面进行归一化操作。这种颜色系统具有通道对表面方向、照明方向具有鲁棒性的特性&#xff0c;适用于图像分割等领域&#xff0c;在机器…

深度学习处理文本(6)

理解词嵌入 重要的是&#xff0c;进行one-hot编码时&#xff0c;你做了一个与特征工程有关的决策。你向模型中注入了有关特征空间结构的基本假设。这个假设是&#xff1a;你所编码的不同词元之间是相互独立的。事实上&#xff0c;one-hot向量之间都是相互正交的。对于单词而言…

STL-vector的使用

1.STL-vector 向量是可以改变其大小的线性序列容器。向量使用连续的空间存储元素&#xff0c;表明向量可以像数组通过下标来访问元素&#xff0c;但是向量的大小可以动态变化。向量的容量可能大于其元素需要的实际容量&#xff0c;向量通过消耗更多的内存来换取存储管理效率。…

MySQL深入

体系结构 连接层&#xff1a;主要处理客户端的连接进行授权认证、校验权限等相关操作 服务层&#xff1a;如sql的接口、解析、优化在这里完成&#xff0c;所有跨存储引擎的操作在这里完成 引擎层&#xff1a;索引是在存储引擎层实现的&#xff0c;所以不同的存储引擎他的索引…

Genspark:重新定义搜索体验的AI智能体引擎

关于我们 飞书-华彬智融知识库 由前百度高管景鲲&#xff08;Eric Jing&#xff09;和朱凯华&#xff08;Kay Zhu&#xff09;联合创立的AI搜索引擎Genspark&#xff0c;正以革命性的技术架构和用户导向的设计理念&#xff0c;为全球用户带来一场搜索体验的范式革命。本文将基…

从零实现Json-Rpc框架】- 项目实现 - 服务端主题实现及整体封装

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…

AI助力PPT制作,让演示变得轻松高效

AI助力PPT制作&#xff0c;让演示变得轻松高效&#xff01;随着科技的进步&#xff0c;AI技术早已渗透到各行各业&#xff0c;特别是在办公领域&#xff0c;AI制作PPT已不再是未来的梦想&#xff0c;而是现实的工具。以前你可能需要花费数小时来制作一个完美的PPT&#xff0c;如…

React-01React创建第一个项目(npm install -g create-react-app)

1. React特点 JSX是javaScript语法的扩展&#xff0c;React开发不一定使用JSX。单向响应的数据流&#xff0c;React实现单向数据流&#xff0c;减少重复代码&#xff0c;比传统数据绑定更简单。等等 JSX是js的语法扩展&#xff0c;允许在js中编写类似HTML的代码 const …

C++学习笔记之内存管理

仅用于记录学习理解 选择题答案及解析 globalVar&#xff1a;C&#xff08;数据段 (静态区)&#xff09; 解析&#xff1a;全局变量存放在数据段&#xff08;静态区&#xff09;&#xff0c;生命周期从程序开始到结束&#xff0c;程序运行期间一直存在。 staticGlobalVar&…

【MyBatis】深入解析 MyBatis XML 开发:增删改查操作和方法命名规范、@Param 重命名参数、XML 返回自增主键方法

增删改查操作 接下来&#xff0c;我们来实现一下用户的增加、删除和修改的操作。 增( Insert ) UserInfoMapper接口&#xff1a; 我们写好UserInfoMapper接口后&#xff0c;自动生成 XML 代码&#xff1b; UserInfoMapper.xml实现&#xff1a; 增删改查方法命名规范 如果我们…

使用Python构建Kafka示例项目

新建项目 mkdir python-kafka-test cd python-kafka-test 安装依赖 pip install confluent_kafka 创建配置文件 # Kafka配置文件# Kafka服务器配置 KAFKA_CONFIG {bootstrap.servers: localhost:9092,# 生产者特定配置producer: {client.id: python-kafka-producer,acks:…

本地化部署DeepSeek-R1蒸馏大模型:基于飞桨PaddleNLP 3.0的实战指南

目录 一、飞桨框架3.0&#xff1a;大模型推理新范式的开启1.1 自动并行机制革新&#xff1a;解放多卡推理1.2 推理-训练统一设计&#xff1a;一套代码全流程复用 二、本地部署DeepSeek-R1-Distill-Llama-8B的实战流程2.1 机器环境说明2.2 模型与推理脚本准备2.3 启动 Docker 容…

VBA 64位API声明语句第008讲

跟我学VBA&#xff0c;我这里专注VBA, 授人以渔。我98年开始&#xff0c;从源码接触VBA已经20余年了&#xff0c;随着年龄的增长&#xff0c;越来越觉得有必要把这项技能传递给需要这项技术的职场人员。希望职场和数据打交道的朋友&#xff0c;都来学习VBA,利用VBA,起码可以提高…

Linux信号——信号的保存(2)

关于core和term两种终止方式 core是什么&#xff1f; 将进程在内存中的核心数据&#xff08;与调试有关&#xff09;转存到磁盘中形成core,core.pid的文件。 core dump&#xff1a;核心转储。 core与term的区别&#xff1a; term只是普通的终止&#xff0c;而core终止方式还要…

【蓝桥杯嵌入式——学习笔记一】2016年第七届省赛真题重难点解析记录,闭坑指南(文末附完整代码)

在读题过程中发现本次使用的是串口2&#xff0c;需要配置串口2。 但在查看产品手册时发现PA14同时也是SWCLK。 所以在使用串口2时需要拔下跳线帽去连接CH340。 可能是用到串口2的缘故&#xff0c;在烧录时发现报了一个错误。这时我们要想烧录得按着复位键去点击烧录&#xff0c…

基础常问 (概念、代码)

读源码 代码题 Void方法 &#xff0c;也可以提前rerun;结束 RandomAccessFile类&#xff08;随机访问文件&#xff09; 在 Java 中&#xff0c;可以使用RandomAccessFile类来实现文件指针操作。RandomAccessFile提供了对文件内容的随机访问功能&#xff0c;它的文件指针可以通…

J1 ResNet-50算法实战与解析

&#x1f368; 本文為&#x1f517;365天深度學習訓練營 中的學習紀錄博客&#x1f356; 原作者&#xff1a;K同学啊 | 接輔導、項目定制 一、理论知识储备 1. 残差网络的由来 ResNet主要解决了CNN在深度加深时的退化问题&#xff08;梯度消失与梯度爆炸&#xff09;。 虽然B…