2025-03-25 Unity 网络基础4——TCP同步通信

news2025/4/21 8:20:16

文章目录

  • 1 Socket
    • 1.1 Socket 类型
    • 1.2 构造 Socket
    • 1.3 常用属性
    • 1.4 常用方法
  • 2 TCP 通信
    • 2.1 服务端配置
    • 2.2 客户端配置
    • 2.3 进行通信
    • 2.4 多设备通信
  • 3 区分消息

1 Socket

​ Socket 是 C# 提供的网络通信类(其它语言也有对应的 Socket 类),是支持 TCP/IP 网络通信的基本操作单位。

  • 类名:Socket
  • 命名空间:System.Net.Sockets

​ 一个套接字对象包含以下关键信息:

  1. 本机的 IP 地址和端口。
  2. 对方主机的 IP 地址和端口。
  3. 双方通信的协议信息。

​ 一个 Sccket 对象表示一个本地或者远程套接字信息,可被视为一个数据通道,连接与客户端和服务端,数据的发送和接受均通过这个通道进行。

​ 一般长连接游戏会使用 Socket 套接字作为通信方案。

1.1 Socket 类型

​ Socket 套接字有 3 种不同的类型:

  1. 流套接字

    主要用于实现 TCP 通信,提供面向连接、可靠的、有序的、数据无差错且无重复的数据传输服务。

  2. 数据报套接字

    主要用于实现 UDP 通信,提供无连接的通信服务,数据包长度不能大于 32KB,不提供正确性检查,不保证顺序,可能出现重发、丢失等情况。

  3. 原始套接字(不常用)

    主要用于实现 IP 数据包通信,用于直接访问协议的较低层,常用于侦听和分析数据包。

1.2 构造 Socket

public Socket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType);

  • 参数 1:AddressFamily

    网络寻址 枚举类型,决定寻址方案。

    • InterNetwork:IPv4 寻址(常用)
    • InterNetwork6:IPv6 寻址(常用)
    • UNIX:UNIX 本地到主机地址
    • ImpLink:ARPANETIMP 地址
    • Ipx:IPX 或 SPX 地址
    • Iso:ISO 协议的地址
    • Osi:OSI 协议的地址
    • NetBios:NetBios 地址
    • Atm:本机 ATM 服务地址
  • 参数 2:SocketType

    套接字枚举类型,决定使用的套接字类型。

    • Dgram:支持数据报,最大长度固定的无连接、不可靠的消息(常用,主要用于 UDP 通信)
    • Stream:支持可靠、双向、基于连接的字节流(常用,主要用于 TCP 通信)
    • Raw:支持对基础传输协议的访问
    • Rdm:支持无连接、面向消息、以可靠方式发送的消息
    • Seqpacket:提供排序字节流的面向连接且可靠的双向传输
  • 参数 3:ProtocolType

    协议类型枚举类型,决定套接字使用的通信协议。

    • TCP:TCP 传输控制协议(常用)
    • UDP:UDP 用户数据报协议(常用)
    • IP:IP 网际协议
    • Icmp:Icmp 网际消息控制协议
    • Igmp:Igmp 网际组管理协议
    • Ggp:网关到网关协议
    • IPv4:Internet 协议版本 4
    • Pup:PARC 通用数据包协议
    • Idp:Internet 数据报协议
    • Raw:原始 IP 数据包协议
    • Ipx:Internet 数据包交换协议
    • Spx:顺序包交换协议
    • IcmpV6:用于 IPv6 的 Internet 控制消息协议

参数 2、3 的常用搭配:

  1. SocketType.Dgram + ProtocolType.Udp = UDP 协议通信(常用)
  2. SocketType.Stream + ProtocolType.Tcp = TCP 协议通信(常用)
  3. SocketType.Raw + ProtocolType.Icmp = Internet 控制报文协议
  4. SocketType.Raw + ProtocolType.Raw = 简单 IP 包通信
// TCP 流套接字
Socket socketTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

// UDP 数据报套接字
Socket socketUdp = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

1.3 常用属性

// 1.套接字的连接状态
if (socketTcp.Connected)
{ }

// 2.获取套接字的类型
print(socketTcp.SocketType);

// 3.获取套接字的协议类型
print(socketTcp.ProtocolType);

// 4.获取套接字的寻址方案
print(socketTcp.AddressFamily);

// 5.从网络中获取准备读取的数据数据量
print(socketTcp.Available);

// 6.获取本机 EndPoint 对象(IPEndPoint 继承 EndPoint)
// socketTcp.LocalEndPoint as IPEndPoint

// 7.获取远程 EndPoint 对象
// socketTcp.RemoteEndPoint as IPEndPoint

1.4 常用方法

  1. 主要用于服务端
// 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();
  1. 主要用于客户端
// 2-1: 连接远程服务端
socketTcp.Connect(IPAddress.Parse("118.12.123.11"), 8080);
  1. 客户端服务端都会用
// 3-1: 同步发送和接收数据
// socketTcp.Send(...);
// socketTcp.Receive(...);

// 3-2: 异步发送和接收数据
// socketTcp.SendAsync(...);
// socketTcp.ReceiveAsync(...);

// 3-3: 释放连接并关闭 Socket,先于 Close 调用
socketTcp.Shutdown(SocketShutdown.Both);

// 3-4: 关闭连接,释放所有Socket关联资源
socketTcp.Close();

2 TCP 通信

2.1 服务端配置

​ 以 Rider IDE 为例,创建控制台程序。

image-20250321155739669
  1. 创建 TCP 套接字
// 创建一个TCP套接字
var socketTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
    // 创建一个IP地址和端口号的终结点
    var ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080); // 填写服务器本机的 IP 地址和端口号
    // 绑定套接字到指定的终结点
    socketTcp.Bind(ipPoint);
}
catch (Exception e)
{
    // 如果绑定失败,输出错误信息
    Console.WriteLine("绑定报错:" + e);
    return;
}
  1. 连接客户端
// 开始监听连接
socketTcp.Listen(1024);
Console.WriteLine("服务器已启动,等待客户端连接");

// 接受客户端连接
var socketClient = socketTcp.Accept(); // Accept() 会同步等待连接
Console.WriteLine("客户端已连接");
  1. 发送消息并等待回复
// 向客户端发送消息
socketClient.Send(Encoding.UTF8.GetBytes("你好,客户端!"));

// 接收客户端发送的消息
var result = new byte[1024];
var receiveLength = socketClient.Receive(result);
  1. 输出接受内容并中断连接
// 输出客户端发送的消息
Console.WriteLine($"客户端 {socketClient.RemoteEndPoint} 发送的消息:" + Encoding.UTF8.GetString(result, 0, receiveLength));

// 关闭套接字
socketClient.Shutdown(SocketShutdown.Both);
socketClient.Close();

Console.WriteLine("按任意键退出");
Console.ReadKey();

2.2 客户端配置

​ 进入 Unity,创建脚本并挂载到场景上。

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

public class Lesson6 : MonoBehaviour
{
    private void Start()
    {
        ... // 编写代码
    }
}
image-20250321164944693
  1. 与服务器建立连接
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

try // 使用 try 块包裹防止连接失败报错
{
    socket.Connect("127.0.0.1", 8080); // 填写服务器的 ip 地址和端口号
}
catch (SocketException e) // 网络通信异常
{
    if (e.ErrorCode == 10061) // 10061 错误码表示服务器拒绝连接
    {
        Debug.Log("服务器拒绝连接");
    }
    else
    {
        Debug.Log("连接失败");
    }

    return;
}
  1. 接受与发送消息
var receiveBytes = new byte[1024];
var receiveLength = socket.Receive(receiveBytes); // 接收一条消息后才继续工作
print("接收到数据:" + System.Text.Encoding.UTF8.GetString(receiveBytes, 0, receiveLength));

socket.Send(Encoding.UTF8.GetBytes("Hello World!"));
  1. 断开连接
socket.Shutdown(SocketShutdown.Both);
socket.Close();

2.3 进行通信

​ 首先运行服务器。

image-20250321164715953

​ 进入 Unity,点击运行,可看到通信结果。

image-20250321165031472

​ 服务器端输出结果如下:

image-20250321165111589

2.4 多设备通信

Socket.Accept() 方法会阻塞当前线程,直至接收到设备通信为止。因此,上述方法只能与一台指定设备进行通信。

​ 为实现多设备通信,需创建新线程监听客户端的连接。

class Program
{
    // TCP 套接字
    private static Socket _SocketTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

    // 客户端套接字列表
    private static List<Socket> _ClientSockets = new List<Socket>();

    // 运行标志
    private static bool _Running = false;
    
    ...
}

​ Main 函数的处理步骤包括以下 4 步:

  1. 绑定 IP 和端口,开始监听
var ipAddress = IPAddress.Parse("127.0.0.1");
var port = 8080;
var endPoint = new IPEndPoint(ipAddress, port);
_SocketTcp.Bind(endPoint);
_SocketTcp.Listen(1024);
_Running = true;
  1. 启动线程等待客户端连接
var acceptThread = new Thread(AcceptClientThread);
acceptThread.Start();
  1. 启动线程接收客户端消息
var receiveThread = new Thread(ReceiveMessageThread);
receiveThread.Start();
  1. 主线程处理用户输入

    这里规定,输入“exit”退出服务器,输入“send”向所有客户端发送消息。

while (true)
{
    var input = Console.ReadLine();
    if (input == "exit") // 输入命令关闭服务器
    {
        _Running = false;
        for (int i = 0; i < _ClientSockets.Count; i++)
        {
            _ClientSockets[i].Shutdown(SocketShutdown.Both);
            _ClientSockets[i].Close();
        }
        _ClientSockets.Clear();
        break;
    }
    else if (input == "send") // 输入命令向所有客户端发送消息
    {
        for (int i = 0; i < _ClientSockets.Count; i++)
        {
            _ClientSockets[i].Send("Hello, client!"u8.ToArray());
            Console.WriteLine("Send Hello");
        }
    }
}

​ 连接客户端 AcceptClientThread、接受消息 ReceiveMessage 的线程工作如下:

private static void AcceptClientThread()
{
    while (_Running)
    {
        var clientSocket = _SocketTcp.Accept();
        _ClientSockets.Add(clientSocket);
        clientSocket.Send("Welcome to the server!"u8.ToArray()); // 由于客户端规定为接收一条消息后才继续工作,因此这里需要发送一条消息
    }
}

// 接收客户端消息的线程
private static void ReceiveMessageThread()
{
    var buffer = new byte[1024 * 1024];
    int receiveLength;
    Socket clientSocket;

    while (_Running)
    {
        for (int i = 0; i < _ClientSockets.Count; i++)
        {
            clientSocket = _ClientSockets[i];

            // 判断是否有可接收的消息
            if (clientSocket.Available > 0)
            {
                receiveLength = clientSocket.Receive(buffer);

                // 使用线程池处理接收到的消息,而不是立即处理
                // 防止用户等待时间过长
                ThreadPool.QueueUserWorkItem(ReceiveMessage, (clientSocket, Encoding.UTF8.GetString(buffer, 0, receiveLength)));
            }
        }
    }
}

​ 在 ReceiveMessage 中,使用线程池处理接收到的消息,而不是立即处理,防止用户等待时间过长。接收消息后的工作通过 ReceiveMessage 方法定义:

// 处理接收到的消息
private static void ReceiveMessage(object? state)
{
    if (state == null) return;

    (Socket socket, string str) info = ((Socket socket, string str)) state;
    Console.WriteLine($"Receive message from client {info.socket}: {info.str}");
}

3 区分消息

​ 数据对象序列化后是长度不同的字节数组,将它们发送出去后,对方如何区分是什么消息?如何选择对应的数据类进行反序列化?

解决方案

​ 为发送的信息添加标识 ID。

​ 例如,选用 int 类型作为消息 ID 类型,前 4 个字节为消息 ID,后面的字节为数据类的内容。每次收到消息时,先把前 4 个字节取出来解析为消息 ID,再根据 ID 进行消息反序列化。

实践

  1. 定义消息接口。

    public interface INetMessage
    {
        int MessageId { get; }
    
        int BytesLength { get; }
    
        byte[] ToBytes();
    
        int FromBytes(byte[] bytes, int index);
    }
    
  2. 创建消息类型

    public class PlayerMessage : INetMessage
    {
        public int    PlayerId;
        public string Name;
        public int    Atk;
        public int    Lev;
    
        public int MessageId { get => 1001; }
    
        public int BytesLength
        {
            get => this.GetBytesLength(MessageId) + // 消息长度
                   this.GetBytesLength(PlayerId) +
                   this.GetBytesLength(Name) +
                   this.GetBytesLength(Atk) +
                   this.GetBytesLength(Lev);
        }
    
        public byte[] ToBytes()
        {
            var bytes = new byte[BytesLength];
            var index = 0;
            index = this.Write(bytes, index, MessageId);
            index = this.Write(bytes, index, PlayerId);
            index = this.Write(bytes, index, Name);
            index = this.Write(bytes, index, Atk);
            index = this.Write(bytes, index, Lev);
            return bytes;
        }
    
        public int FromBytes(byte[] bytes, int index)
        {
            // 反序列化不需要解析 Id,在此之前应解析 Id 从而使用该方法
            index = this.Read(bytes, index, ref PlayerId);
            index = this.Read(bytes, index, ref Name);
            index = this.Read(bytes, index, ref Atk);
            index = this.Read(bytes, index, ref Lev);
            return index;
        }
    
        public override string ToString()
        {
            return $"PlayerMessage: {PlayerId}, {Name}, {Atk}, {Lev}";
        }
    }
    

    其中的 GetBytesLengthWriteRead 方法均由拓展类提供:

    public static class ByteLengthExtension
    {
        public static int GetBytesLength(this INetMessage message, int value)
        {
            return sizeof(int);
        }
    
        public static int GetBytesLength(this INetMessage message, string value)
        {
            return sizeof(int) + Encoding.UTF8.GetByteCount(value);
        }
    
        public static int GetBytesLength(this INetMessage message, bool value)
        {
            return sizeof(bool);
        }
    
        public static int GetBytesLength(this INetMessage message, float value)
        {
            return sizeof(float);
        }
    }
    
    public static class INetMessageExtension
    {
        public static int Write(this INetMessage message, byte[] bytes, int index, int value)
        {
            BitConverter.GetBytes(value).CopyTo(bytes, index);
            return index + sizeof(int);
        }
    
        public static int Read(this INetMessage message, byte[] bytes, int index, ref int value)
        {
            value = BitConverter.ToInt32(bytes, index);
            return index + sizeof(int);
        }
    
        public static int Write(this INetMessage message, byte[] bytes, int index, string value)
        {
            var strBytes = Encoding.UTF8.GetBytes(value);
    
            BitConverter.GetBytes(strBytes.Length).CopyTo(bytes, index);
            index += sizeof(int);
    
            strBytes.CopyTo(bytes, index);
            return index + strBytes.Length;
        }
    
        public static int Read(this INetMessage message, byte[] bytes, int index, ref string value)
        {
            int length = BitConverter.ToInt32(bytes, index);
            index += sizeof(int);
    
            value = Encoding.UTF8.GetString(bytes, index, length);
            return index + length;
        }
    
        public static int Write(this INetMessage message, byte[] bytes, int index, bool value)
        {
            BitConverter.GetBytes(value).CopyTo(bytes, index);
            return index + sizeof(bool);
        }
    
        public static int Read(this INetMessage message, byte[] bytes, int index, ref bool value)
        {
            value = BitConverter.ToBoolean(bytes, index);
            return index + sizeof(bool);
        }
    
        public static int Write(this INetMessage message, byte[] bytes, int index, float value)
        {
            BitConverter.GetBytes(value).CopyTo(bytes, index);
            return index + sizeof(float);
        }
    
        public static int Read(this INetMessage message, byte[] bytes, int index, ref float value)
        {
            value = BitConverter.ToSingle(bytes, index);
            return index + sizeof(float);
        }
    
        public static int Write(this INetMessage message, byte[] bytes, int index, INetMessage value)
        {
            value.ToBytes().CopyTo(bytes, index);
            return index + value.BytesLength;
        }
    
        public static int Read(this INetMessage message, byte[] bytes, int index, ref INetMessage value)
        {
            value.FromBytes(bytes, index);
            return index + value.BytesLength;
        }
    }
    
  3. 创建消息类型 PlayerMessage

    public class PlayerMessage : INetMessage
    {
        public int    PlayerId;
        public string Name;
        public int    Atk;
        public int    Lev;
    
        public int MessageId { get => 1001; }
    
        public int BytesLength
        {
            get => this.GetBytesLength(MessageId) + // 消息长度
                   this.GetBytesLength(PlayerId) +
                   this.GetBytesLength(Name) +
                   this.GetBytesLength(Atk) +
                   this.GetBytesLength(Lev);
        }
    
        public byte[] ToBytes()
        {
            var bytes = new byte[BytesLength];
            var index = 0;
            index = this.Write(bytes, index, MessageId);
            index = this.Write(bytes, index, PlayerId);
            index = this.Write(bytes, index, Name);
            index = this.Write(bytes, index, Atk);
            index = this.Write(bytes, index, Lev);
            return bytes;
        }
    
        public int FromBytes(byte[] bytes, int index)
        {
            // 反序列化不需要解析 Id,在此之前应解析 Id 从而使用该方法
            index = this.Read(bytes, index, ref PlayerId);
            index = this.Read(bytes, index, ref Name);
            index = this.Read(bytes, index, ref Atk);
            index = this.Read(bytes, index, ref Lev);
            return index;
        }
    
        public override string ToString()
        {
            return $"PlayerMessage: {PlayerId}, {Name}, {Atk}, {Lev}";
        }
    }
    
  4. 进行通信。

    客户端:

    public class Lesson6 : MonoBehaviour
    {
        private void Start()
        {
            // 创建一个 Socket 对象,指定地址族、套接字类型和协议类型
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    
            try // 使用 try 块包裹防止连接失败报错
            {
                socket.Connect("127.0.0.1", 8080); // 填写服务器的 ip 地址和端口号
            }
            catch (SocketException e) // 网络通信异常
            {
                if (e.ErrorCode == 10061) // 10061 错误码表示服务器拒绝连接
                {
                    Debug.Log("服务器拒绝连接");
                }
                else
                {
                    Debug.Log("连接失败");
                }
    
                return;
            }
    
            var receiveBytes = new byte[1024];
            var receiveLength = socket.Receive(receiveBytes); // 接收一条消息后才继续工作
    
            // 解析 Id
            var id = BitConverter.ToInt32(receiveBytes, 0);
            switch (id)
            {
                case 1001:
                    var playerMsg = new PlayerMessage();
                    playerMsg.FromBytes(receiveBytes, sizeof(int));
                    Debug.Log(playerMsg);
                    break;
            }
    
            // print("接收到数据:" + System.Text.Encoding.UTF8.GetString(receiveBytes, 0, receiveLength));
    
            socket.Send(Encoding.UTF8.GetBytes("Hello World!"));
    
            socket.Shutdown(SocketShutdown.Both);
            socket.Close();
        }
    }
    

    服务端:

    // See https://aka.ms/new-console-template for more information
    
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using Exercise;
    
    // 创建一个TCP套接字
    var socketTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    try
    {
        // 创建一个IP地址和端口号的终结点
        var ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080); // 填写服务器本机的 IP 地址和端口号
        // 绑定套接字到指定的终结点
        socketTcp.Bind(ipPoint);
    }
    catch (Exception e)
    {
        // 如果绑定失败,输出错误信息
        Console.WriteLine("绑定报错:" + e);
        return;
    }
    
    // 开始监听连接
    socketTcp.Listen(1024);
    Console.WriteLine("服务器已启动,等待客户端连接");
    
    // 接受客户端连接
    var socketClient = socketTcp.Accept();
    Console.WriteLine("客户端已连接");
    
    // 向客户端发送消息
    // socketClient.Send(Encoding.UTF8.GetBytes("你好,客户端!"));
    var playerMsg = new PlayerMessage()
    {
        PlayerId = 1,
        Name = "zheliku",
        Atk = 5,
        Lev = 10,
    };
    socketClient.Send(playerMsg.ToBytes());
    
    // 接收客户端发送的消息
    var result = new byte[1024];
    var receiveLength = socketClient.Receive(result);
    
    // 输出客户端发送的消息
    Console.WriteLine($"客户端 {socketClient.RemoteEndPoint} 发送的消息:" + Encoding.UTF8.GetString(result, 0, receiveLength));
    
    // 关闭套接字
    socketClient.Shutdown(SocketShutdown.Both);
    socketClient.Close();
    
    Console.WriteLine("按任意键退出");
    Console.ReadKey();
    

​ 先运行服务器,后运行 Unity,可看到通信成功:

image-20250325041231533

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

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

相关文章

C++进阶(一)

个人主页&#xff1a;PingdiGuo_guo 收录专栏&#xff1a;C干货专栏 前言 本篇博客是讲解函数的重载以及引用的知识点的。 文章目录 前言 1.函数重载 1.1何为函数重载 1.2函数重载的作用 1.3函数重载的实现 2.引用 2.1何为引用 2.2定义引用 2.3引用特性 2.4常引用 2…

深度解读DeepSeek:开源周(Open Source Week)技术解读

深度解读DeepSeek&#xff1a;开源周&#xff08;Open Source Week&#xff09;技术解读 深度解读DeepSeek&#xff1a;源码解读 DeepSeek-V3 深度解读DeepSeek&#xff1a;技术原理 深度解读DeepSeek&#xff1a;发展历程 文章目录 一、开源内容概览Day1&#xff1a;FlashMLAD…

AI Agent开发与应用

AI Agent开发与应用&#xff1a;本地化智能体实践——本地化智能体开发进展与主流框架分析 我要说的都在ppt里面了&#xff0c;相关复现工作请参考ai agent开发实例 OpenManus Dify Owl 第二个版本更新了对话的框架&#xff0c;通过gradio做了一个全新的界面 只测试了阿里云…

石斛基因组-文献精读122

A chromosome-level Dendrobium moniliforme genome assembly reveals the regulatory mechanisms of flavonoid and carotenoid biosynthesis pathways 《染色体水平的石斛基因组组装揭示了黄酮类和胡萝卜素生物合成途径的调控机制》 摘要 石斛&#xff08;Dendrobium monil…

javaSE.多维数组

1 final 引用类型 final int[] arr 继承Object 的引用类型&#xff0c;不能改变引用的对象 存的其实是引用 数组类型数组&#xff0c;其实存的是引用 int [][] arr new int[][] { {1,2,3}, {4,5,6} };int [] a arr[0]; int [] b arr[1];

Python条件处理,新手入门到精通

Python条件处理&#xff0c;新手入门到精通 对话实录 **小白**&#xff1a;&#xff08;崩溃&#xff09;我写了if x 1:&#xff0c;为什么Python会报错&#xff1f; **专家**&#xff1a;&#xff08;推眼镜&#xff09;**是赋值&#xff0c;才是比较**&#xff01;想判断相…

JPA实体类注解缺失异常全解:从报错到防御!!!

&#x1f6a8; JPA实体类注解缺失异常全解&#xff1a;从报错到防御 &#x1f6e1;️ 一、&#x1f4a5; 问题现象速览 // 经典报错示例 Caused by: java.lang.IllegalArgumentException: Not a managed type: class com.example.entity.Product典型症状&#xff1a; &…

【C语言】多进程/多线程

【C语言】多进程/多线程 参考链接多进程/多线程服务器1. 多进程服务器2. 多线程服务器 结语参考链接 参考链接 c 中文网 菜鸟 c 多进程/多线程服务器 多进程和多线程是常用的并发编程技术。它们都允许程序同时执行多个任务&#xff0c;提高了系统的资源利用率和程序的运行效率…

模糊数学 | 模型 / 集合 / 关系 / 矩阵

注&#xff1a;本文为来自 “模糊数学 | 模型及其应用” 相关文章合辑。 略作重排。 如有内容异常&#xff0c;请看原文。 模糊数学模型&#xff1a;隶属函数、模糊集合的表示方法、模糊关系、模糊矩阵 wamg 潇潇 于 2019-05-06 22:35:21 发布 1.1 模糊数学简介 1965 年&a…

QinQ项展 VLAN 空间

随着以太网技术在网络中的大量部署&#xff0c;利用 VLAN 对用户进行隔离和标识受到很大限制。因为 IEEE802.1Q 中定义的 VLAN Tag 域只有 12 个比特&#xff0c;仅能表示 4096 个 VLAN&#xff0c;无法满足城域以太网中标识大量用户的需求&#xff0c;于是 QinQ 技术应运而生。…

数据结构—树(java实现)

目录 一、树的基本概念1.树的术语2.常见的树结构 二、节点的定义三、有关树结构的操作1.按照数组构造平衡 二叉搜索树2.层序遍历树3.前、中、后序遍历树(1).前序遍历树(2).中序遍历树(3).后序遍历树(4).各种遍历的情况的效果对比 4.元素添加5.元素删除1.删除叶子节点2.删除单一…

S32K144外设实验(七):FTM输出多路互补带死区PWM

文章目录 1. 概述1.1 时钟系统1.2 实验目的2. 代码的配置2.1 时钟配置2.2 FTM模块配置2.3 输出引脚配置2.4 API函数调用1. 概述 互补对的PWM输出是很重要的外设功能,尤其应用再无刷电机的控制。 1.1 时钟系统 笔者再墨迹一遍时钟的设置,因为很重要。 FTM的CPU接口时钟为SY…

[网鼎杯 2020 白虎组]PicDown1 [反弹shell] [敏感文件路径] [文件描述符]

常见读取路径 /etc/passwd一些用户和权限还有一些乱七八糟的 /proc/self/cmdline包含用于开始当前进程的命令 /proc/self/cwd/app.py当前工作目录的app.py /proc/self/environ包含了可用进程的环境变量 /proc/pid/exe 包含了正在进程中运行的程序链接&#xff1b; /proc/pid…

单纯形法之大M法

1. 问题背景与标准化 在求解某些线性规划问题时&#xff0c;往往难以直接找到初始的基本可行解。特别是当约束中存在等式或 “≥” 类型的不等式时&#xff0c;我们需要引入人工变量来构造一个初始可行解。 考虑如下标准形式问题&#xff08;假设为最大化问题&#xff09;&am…

各类神经网络学习:(四)RNN 循环神经网络(下集),pytorch 版的 RNN 代码编写

上一篇下一篇RNN&#xff08;中集&#xff09;待编写 代码详解 pytorch 官网主要有两个可调用的模块&#xff0c;分别是 nn.RNNCell 和 nn.RNN &#xff0c;下面会进行详细讲解。 RNN 的同步多对多、多对一、一对多等等结构都是由这两个模块实现的&#xff0c;只需要将对输入…

DeepSeek 发布DeepSeek-V3-0324 版本 前端与网页开发能力、推理与多任务能力提升

DeepSeek 发布 DeepSeek-V3-0324 版本 DeepSeek 发布 DeepSeek-V3-0324 版本&#xff0c;在其前代模型 DeepSeek-V3 的基础上进行了显著升级。 该模型专注于中文和多语言文本生成、推理、代码编写等综合能力的提升&#xff0c;支持 Function Calling&#xff08;函数调用&…

传输层安全协议 SSL/TLS 详细介绍

传输层安全性协议TLS及其前身安全套接层SSL是一种安全传输协议&#xff0c;目前TLS协议已成为互联网上保密通信的工业标准&#xff0c;在浏览器、邮箱、即时通信、VoIP等应用程序中得到广泛的应用。本文对SSL和TLS协议进行一个详细的介绍&#xff0c;以便于大家更直观的理解和认…

CentOS8 安装 Docker-CE

如果之前安装过docker,请先卸载旧版本: yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine 安装所需的软件包: yum install -y yum-utils 添加软件源信息(设置存储库)…

【Docker系列八】使用 Docker run 命令部署 Nginx

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

基于 PHP 内置类及函数的免杀 WebShell

前言 PHP 作为广泛使用的服务端语言&#xff0c;其灵活的内置类&#xff08;如 DOMDocument&#xff09;和文件操作机制&#xff08;.ini、.inc 的自动加载&#xff09;&#xff0c;为攻击者提供了天然的隐蔽通道。通过 动态函数拼接、反射调用、加密混淆 和 伪命名空间 等手法…