Unity-Tcp-网络聊天功能(一): 基本功能

news2024/12/24 8:14:33

Protobuff的效率较高。

TCP用于延迟不高的游戏效果较好,UDP对开发人员友好(对于消息的重发等,降低网络延迟),Websocket在网页端H5端进行通信,

1.TCP相关API介绍与服务端编写

TCP是面向连接的。因此需要创建监听器,监听客户端的连接。当连接成功后,会返回一个TcpClient对象。通过TcpClient可以接收和发送数据。

VS创建C# .net控制台应用

项目中创建文件夹Net,Net 下添加TCPServer.cs类,用来创建TCPListener和Accept客户端连接,实例化一个TCPServcer放在Main函数执行。Client用来管理每一个客户端的接收发送消息。

namespace Server
{
    class Program
    {
        static void Main(string[] args)
        {
            TCPServer tcpServer = new TCPServer();
            tcpServer.Start();

            while(true)
            {
                Console.ReadLine();//监听用户输入,不写这句话运行测试会立刻跳出
            }
        }
    }
}
namespace Server.Net
{
    internal class TCPServer
    {
        TcpListener tcpListener;
        //启动服务器,创建监听器
        public void Start()
        {
            try
            {
                //创建监听器,如果不指定IP会以本机的IP为服务器的IP
                tcpListener = TcpListener.Create(7788);//1-65535
                tcpListener.Start(500);//启动监听,传递最大接收多少客户端连接。

                Console.WriteLine("TCP Server Start");

                //启动服务器后,还要调用接收连接
                Accept();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
            
        }

        //监听客户端连接
        //由于内部使用的方法是AcceptTcpClientAsync异步,因此方法也要是async
        //这样就可以通过await的方式等待
        public async void Accept()
        {
            try
            {
                //监听TCP客户端连接的返回的对象
                TcpClient tcpClient = await tcpListener.AcceptTcpClientAsync();
                Console.WriteLine("客户端已连接:" + tcpClient.Client.RemoteEndPoint);//打印IP
                                                                               //使用构建的Client类来缓存这些TcpClient
                Client client = new Client(tcpClient);
                Accept();//继续接受来自客户端的连接即可
            }
            catch (Exception e)
            {
                Console.WriteLine($"Accept:{e.Message}");
                tcpListener.Stop();
            }
        }
    }
}
namespace Server.Net
{
    //每一个客户端都是一个独立的Client
    internal class Client
    {
        TcpClient client;
        public Client(TcpClient tcpClient)
        {
            client = tcpClient;
            Receive();
        }

        //接收消息
        public async void Receive()
        {
            //处于连接状态就持续接收信息。
            while(client.Connected)
            {
                try
                {
                    byte[] buffer = new byte[4096];//存储接收到的数据,定义初始容量,一般不超过4096或1024
                    //把消息存到buffer中,从第几个字节开始存,存多长,,,,,,返回length表示接收到多少数据。
                    int length = await client.GetStream().ReadAsync(buffer, 0, buffer.Length);

                    if(length > 0)//表示有效信息
                    {
                        //$作用是将{}内容当做表达式
                        Console.WriteLine($"接收到的数据长度:{length}");
                        Console.WriteLine($"接收到的数据内容:{Encoding.UTF8.GetString(buffer, 0, length)}");//将byte数组转换为字符串
                    }
                    else
                    {
                        //客户端关闭了
                        client.Close();
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine($"Receive Error:{e.Message}");
                    client.Close();//出现错误断开客户端连接。
                }
            }            
        }

        //发送消息
        public async void Send(byte[] data)
        {
            try
            {
                //数据写入网络数据流中。就是发送
                await client.GetStream().WriteAsync(data, 0, data.Length);
                Console.WriteLine("发送成功! " + $"发送的消息内容:{Encoding.UTF8.GetString(data, 0, data.Length)}");
            }
            catch (Exception e)
            {
                client.Close();//关闭客户端
                Console.WriteLine($"send error:{e.Message}");
            }
        }
    }
}

运行测试

2.实现客户端和服务器的消息收发

编写Unity客户端,Client项目,创建Script/Net/Client.cs

注意收发消息都必须是异步的async,因为不能阻塞我们的程序。

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

public class Client
{
    private static Client instance = new Client();
    public static Client Instance => instance;//单例模式便于调用
    
    private TcpClient client;//跟服务器通信需要调用client
    
    public  void Start()
    {
        client = new TcpClient();
        Connect();
    }
    
    //连接服务器接口
    public async void Connect()
    {
        try
        {
            await client.ConnectAsync("127.0.0.1", 7788);
            Debug.Log("TCP 连接成功");
            Receive();
        }
        catch (Exception e)
        {
            Debug.Log(e.Message);
        }
    }
    
    //接收接口
    public async void Receive()
    {
        while (client.Connected)
        {
            try
            {
                byte[] buff = new byte[4096];
                int length = await client.GetStream().ReadAsync(buff, 0, buff.Length);
                if (length > 0)
                {
                    Debug.Log($"接收到的数据长度:{length}");
                    Debug.Log($"接收到的数据内容:{Encoding.UTF8.GetString(buff, 0, length)}");
                }
                else
                {
                    client.Close();
                }
            }
            catch (Exception e)
            {
                Debug.Log(e.Message);
                client.Close();
            }
        }
    }
    
    //发送接口
    public async void Send(byte[] data)
    {
        try
        {
            await client.GetStream().WriteAsync(data, 0, data.Length);
            Debug.Log("发送成功! " + $"发送的消息内容:{Encoding.UTF8.GetString(data, 0, data.Length)}");
        }
        catch (Exception e)
        {
            Debug.Log(e.Message);
            client.Close();
        }
    }
}

Unity创建Scripts/GameManager.cs来启动客户端连接服务器

using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;

public class GameManager : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        Client.Instance.Start();
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.A))
        {
            Client.Instance.Send(Encoding.UTF8.GetBytes("login..."));
        }
    }
}
public async void Receive()
{
    //处于连接状态就持续接收信息。
    while(client.Connected)
    {
        try
        {
            byte[] buffer = new byte[4096];//存储接收到的数据,定义初始容量,一般不超过4096或1024
            //把消息存到buffer中,从第几个字节开始存,存多长,,,,,,返回length表示接收到多少数据。
            int length = await client.GetStream().ReadAsync(buffer, 0, buffer.Length);

            if(length > 0)//表示有效信息
            {
                //$作用是将{}内容当做表达式
                Console.WriteLine($"接收到的数据长度:{length}");
                Console.WriteLine($"接收到的数据内容:{Encoding.UTF8.GetString(buffer, 0, length)}");//将byte数组转换为字符串

                Send(Encoding.UTF8.GetBytes("测试返回..."));
            }
            else
            {
                //客户端关闭了
                client.Close();
            }
        }
        catch (Exception e)
        {
            Console.WriteLine($"Receive Error:{e.Message}");
            client.Close();//出现错误断开客户端连接。
        }
    }            
}

启动服务器和客户端显示连接成功,测试收发消息。

注意接收消息成功之后,如果需要返回给客户端消息,必须要返回给特定的客户端,不能搞错。需要通过ClientManager写一个字典管理所有Client,或者直接在Client里面写一个tempClient

public Client tempClient;//缓存客户端发送消息
public async void Accept()
    {
        try
        {
            //监听TCP客户端连接的返回的对象
            TcpClient tcpClient = await tcpListener.AcceptTcpClientAsync();
            Console.WriteLine("客户端已连接:" + tcpClient.Client.RemoteEndPoint);//打印IP
                                                                           //使用构建的Client类来缓存这些TcpClient
            Client client = new Client(tcpClient);
            tempClient = client;
            Accept();//继续接受来自客户端的连接即可
        }
        catch (Exception e)
        {
            Console.WriteLine($"Accept:{e.Message}");
            tcpListener.Stop();
        }
    }
static void Main(string[] args)
{
    TCPServer tcpServer = new TCPServer();
    tcpServer.Start();

    while(true)
    {
        var str = Console.ReadLine();//监听用户输入,不写这句话运行测试会立刻跳出
        tcpServer.tempClient.Send(Encoding.UTF8.GetBytes($"测试主动发送数据:{str}"));
    }
}

3.使用LitJSON更好的组织网络数据

在Server项目的VS中打开NuGet包管理器安装LitJson

在PM输入Install-Package LitJson -Version 0.18.0

每个包都由其所有者许可给你。NuGet 不负责第三方包,也不授予其许可证。一些包可能包括受其他许可证约束的依赖关系。单击包源(源) URL 可确定任何依赖关系。

程序包管理器控制台主机版本 5.10.0.7240

键入 "get-help NuGet" 可查看所有可用的 NuGet 命令。

PM> Install-Package LitJson -Version 0.18.0


正在尝试收集与目标为“.NETFramework,Version=v4.7.2”的项目“Server”有关的包“LitJson.0.18.0”的依赖项信息
收集依赖项信息花费时间 1.81 sec
正在尝试解析程序包“LitJson.0.18.0”的依赖项,DependencyBehavior 为“Lowest”
解析依赖项信息花费时间 0 ms
正在解析操作以安装程序包“LitJson.0.18.0”
已解析操作以安装程序包“LitJson.0.18.0”
从“nuget.org”检索包“LitJson 0.18.0” 
GET https://api.nuget.org/v3-flatcontainer/litjson/0.18.0/litjson.0.18.0.nupkg
OK https://api.nuget.org/v3-flatcontainer/litjson/0.18.0/litjson.0.18.0.nupkg 137 毫秒
已通过内容哈希 zVK/1iUURxvEZH1eiviFpS8Qbh1fZe926ie18vhHuisVYcNbdVjgG85X8BsGt7WBU5Ka3gZvaQXRX6EsoD2hrw== 从 https://api.nuget.org/v3/index.json 安装 LitJson 0.18.0 。
正在将程序包“LitJson.0.18.0”添加到文件夹“E:\graduate\learn\Unity\NetProject\Server\packages”
已将程序包“LitJson.0.18.0”添加到文件夹“E:\graduate\learn\Unity\NetProject\Server\packages”
已将程序包“LitJson.0.18.0”添加到“packages.config”
已将“LitJson 0.18.0”成功安装到 Server
执行 nuget 操作花费时间 8.44 sec
已用时间: 00:00:11.4081423

创建Helper/JsonHelper.cs

using System.Text;

using LitJson;

namespace Server.Helper
{
internal class JsonHelper
    {
        //Object转换为String
        public static string ToJson(object x)//object C#所有类派生于此,所以C#类及自己创建的类都有ToString()
        {
            string str = JsonMapper.ToJson(x);
            return str;
        }

        //String转换为Object
        public static T ToObject<T>(string x)
        {
            return JsonMapper.ToObject<T>(x);
        }

        public static T ToObject<T>(byte[] b)
        {
            string x = Encoding.UTF8.GetString(b, 0, b.Length);
            return ToObject<T>(x);
        }

        public static string GetTestToString()
        {
            JsonTest jsonTest = new JsonTest();
            jsonTest.id = 1;
            jsonTest.name = "jsonTest";
            return ToJson(jsonTest);
        }
    }

    public class JsonTest
    {
        public int id;
        public string name;
    }
}
while(true)
{
    var str = Console.ReadLine();//监听用户输入,不写这句话运行测试会立刻跳出
    //tcpServer.tempClient.Send(Encoding.UTF8.GetBytes($"测试主动发送数据:{str}"));
    var jsonStr = JsonHelper.GetTestToString();
    tcpServer.tempClient.Send(Encoding.UTF8.GetBytes(jsonStr));
}

运行测试,,目前Unity收到的消息还没有反序列化成原本的Object,因为服务器目前发送的是Json格式的String,Unity收到的也是Json格式的String

Unity Client项目创建Scripts/Helper/JsonHelper.cs,然后找到\NetProject\Server\packages\LitJson.0.18.0\lib\net45\LitJson.dll复制进Unity的Plugins文件夹,,,,重开一下脚本就重新编译好了,不会报错LitJson缺失的问题了

using System.Text;
using LitJson;
using UnityEngine;

public class JsonHelper
{
    //Object转换为String
    public static string ToJson(object x)//object C#所有类派生于此,所以C#类及自己创建的类都有ToString()
    {
        string str = JsonMapper.ToJson(x);
        return str;
    }

    //String转换为Object
    public static T ToObject<T>(string x)
    {
        return JsonMapper.ToObject<T>(x);
    }

    public static T ToObject<T>(byte[] b)
    {
        string x = Encoding.UTF8.GetString(b, 0, b.Length);
        return ToObject<T>(x);
    }

    public static string GetTestToString()
    {
        JsonTest jsonTest = new JsonTest();
        jsonTest.id = 1;
        jsonTest.name = "jsonTest";
        var jsonStr = ToJson(jsonTest);

        var jsonTestObj = ToObject<JsonTest>(ToJson(jsonTest));
        Debug.Log($"{jsonTestObj.id}   /  {jsonTestObj.name}");
        return jsonStr;
    }
    
    public class JsonTest
    {
        public int id;
        public string name;
    }
}
//接收接口
public async void Receive()
{
    while (client.Connected)
    {
        try
        {
            byte[] buff = new byte[4096];
            int length = await client.GetStream().ReadAsync(buff, 0, buff.Length);
            if (length > 0)
            {
                Debug.Log($"接收到的数据长度:{length}");
                Debug.Log($"接收到的数据内容:{Encoding.UTF8.GetString(buff, 0, length)}");
                var jsonTest = JsonHelper.ToObject<JsonHelper.JsonTest>(Encoding.UTF8.GetString(buff, 0, length));
                Debug.Log(jsonTest.id + " " + jsonTest.name);
            }
            else
            {
                client.Close();
            }
        }
        catch (Exception e)
        {
            Debug.Log(e.Message);
            client.Close();
        }
    }
}

4.粘包和拆包处理

粘包是因为计算机底层将多条小的数据合并成一条大的发送给另一端,减少网络传输的基本开销,需要拆包将多条数据拆分

用4个字节表示包体大小和消息ID,包体内容可能是账号、密码、业务所需要用到的数据(邮箱、移动(方向轴 目标点))

接收到消息看长度是单条消息没接收完还是一下粘包接受了多条消息。

先看Server端的Client

internal class Client
{
    TcpClient client;
    public Client(TcpClient tcpClient)
    {
        client = tcpClient;
        Receive();
    }

    byte[] data = new byte[4096];//接收消息的缓冲区
    int msgLength = 0;//接收到的消息长度

    //接收消息
    public async void Receive()
    {
        //处于连接状态就持续接收信息。
        while(client.Connected)
        {
            try
            {
                byte[] buffer = new byte[4096];//存储接收到的数据,定义初始容量,一般不超过4096或1024
                //把消息存到buffer中,从第几个字节开始存,存多长,,,,,,返回length表示接收到多少数据。
                int length = await client.GetStream().ReadAsync(buffer, 0, buffer.Length);

                if(length > 0)//表示有效信息
                {
                    //$作用是将{}内容当做表达式
                    Console.WriteLine($"接收到的数据长度:{length}");
                    Console.WriteLine($"接收到的数据内容:{Encoding.UTF8.GetString(buffer, 0, length)}");//将byte数组转换为字符串
                    //Send(Encoding.UTF8.GetBytes("测试返回..."));                    
                    Array.Copy(buffer, 0, data, msgLength, length);//把接收到length长度的消息复制到数据缓冲区中msgLength索引后的位置
                    msgLength += length;//每次收到网络数据都要加上数据的长度
                    Handle();
                }
                else
                {
                    //客户端关闭了
                    client.Close();
                }
            }
            catch (Exception e)
            {
                Console.WriteLine($"Receive Error:{e.Message}");
                client.Close();//出现错误断开客户端连接。
            }
        }            
    }

    private void Handle()
    {
        //包体大小(4) 协议ID(4) 包体(byte[])
        if (msgLength >= 8)
        {
            byte[] _size = new byte[4];
            Array.Copy(data, 0, _size, 0, 4);//把包体大小从第0位缓存4位长度
            int size = BitConverter.ToInt32(_size, 0);//获得包体大小

            //本次要拿的长度
            var _length = 8 + size;//实际完整消息的长度:包体大小(4)+协议ID(4)+包体(byte[])

            if (msgLength>=_length)//判断数据缓冲区的长度是否大于一条完整消息的长度。
            {
                //拿出id
                byte[] _id = new byte[4];
                Array.Copy(data, 4, _id, 0, 4);//把协议ID从第4位缓存4位长度
                int id = BitConverter.ToInt32(_id, 0);//获得协议ID

                //包体
                byte[] body = new byte[size];
                Array.Copy(data, 8, body, 0, size);//把包体从第8位缓存size位长度

                if (msgLength>_length)//如果接收到的数据长度大于这次取出的完整一条数据的长度,说明还有数据
                {
                    for (int i = 0; i < msgLength - _length; i++)
                    {
                        data[i] = data[_length + i];//前面取完一次完整消息了,把后面的消息前挪
                    }
                }
                msgLength -= _length;//减掉已经取完的消息长度
                Console.WriteLine($"收到客户端请求:{id}");
                //根据id进行处理,,实际项目一般使用观察者模式,监听id和Action事件绑定
                switch (id)
                {
                    case 1001://注册请求
                        RegisterMsgHandle(body);
                        break;
                    case 1002://登录业务
                        LoginMsgHandle(body);
                        break;
                    case 1003://聊天业务
                        ChatMsgHandle(body);
                        break;

                }
            }
        }
    }

    //处理注册请求
    private void RegisterMsgHandle(byte[] obj)
    {
        
    }

    //处理登录请求
    private void LoginMsgHandle(byte[] obj)
    {

    }

    //处理聊天请求
    private void ChatMsgHandle(byte[] obj)
    {

    }

    //发送消息
    public async void Send(byte[] data)
    {
        try
        {
            //数据写入网络数据流中。就是发送
            await client.GetStream().WriteAsync(data, 0, data.Length);
            Console.WriteLine("发送成功! " + $"发送的消息内容:{Encoding.UTF8.GetString(data, 0, data.Length)}");
        }
        catch (Exception e)
        {
            client.Close();//关闭客户端
            Console.WriteLine($"send error:{e.Message}");
        }
    }
}

根据id进行处理,,实际项目一般使用观察者模式,监听id和Action事件绑定

Unity客户端添加Scritps/Helper/MessageHelper.cs脚本

public class MessageHelper
{
    private static MessageHelper instance = new MessageHelper();
    public static MessageHelper Instance => instance;//单例
    
    byte[] data = new byte[4096];//接收消息的缓冲区
    int msgLength = 0;//接收到的消息长度

    //client接收到消息时,把buffer的数据复制到data数据缓冲区,数据长度加上接受的新有效数据流长度,handle处理数据
    public void CopyToData(byte[] buffer, int length)
    {
        Array.Copy(buffer, 0, data, msgLength, length);
        msgLength += length;
        Handle();
    }
    
    private void Handle()
    {
        //包体大小(4) 协议ID(4) 包体(byte[])
        if (msgLength >= 8)
        {
            byte[] _size = new byte[4];
            Array.Copy(data, 0, _size, 0, 4);//把包体大小从第0位缓存4位长度
            int size = BitConverter.ToInt32(_size, 0);//获得包体大小

            //本次要拿的长度
            var _length = 8 + size;//实际完整消息的长度:包体大小(4)+协议ID(4)+包体(byte[])

            if (msgLength>=_length)//判断数据缓冲区的长度是否大于一条完整消息的长度。
            {
                //拿出id
                byte[] _id = new byte[4];
                Array.Copy(data, 4, _id, 0, 4);//把协议ID从第4位缓存4位长度
                int id = BitConverter.ToInt32(_id, 0);//获得协议ID

                //包体
                byte[] body = new byte[size];
                Array.Copy(data, 8, body, 0, size);//把包体从第8位缓存size位长度

                if (msgLength>_length)//如果接收到的数据长度大于这次取出的完整一条数据的长度,说明还有数据
                {
                    for (int i = 0; i < msgLength - _length; i++)
                    {
                        data[i] = data[_length + i];//前面取完一次完整消息了,把后面的消息前挪
                    }
                }
                msgLength -= _length;//减掉已经取完的消息长度
                Debug.Log($"收到客户端请求:{id}");
                //根据id进行处理,,实际项目一般使用观察者模式,监听id和Action事件绑定
                switch (id)
                {
                    case 1001://注册请求
                        RigisterMsgHandle(body);
                        break;
                    case 1002://登录业务
                        LoginMsgHandle(body);
                        break;
                    case 1003://聊天业务
                        ChatMsgHandle(body);
                        break;

                }
            }
        }
    }

    //按格式封装消息,发送到服务器
    public void SendToServer(int id, string str)
    {
        Debug.Log("ID:" + id);
        var body = Encoding.UTF8.GetBytes(str);
        byte[] send_buff = new byte[body.Length + 8];

        int size = body.Length;

        var _size = BitConverter.GetBytes(size);
        var _id = BitConverter.GetBytes(id);

        Array.Copy(_size, 0, send_buff, 0, 4);
        Array.Copy(_id, 0, send_buff, 4, 4);
        Array.Copy(body, 0, send_buff, 8, body.Length);

        Client.Instance.Send(send_buff);
    }
    
    //处理注册(结果)请求
    private void RigisterMsgHandle(byte[] obj)
    {
            
    }

    //处理登录(结果)请求
    private void LoginMsgHandle(byte[] obj)
    {

    }

    //处理聊天(转发)请求
    private void ChatMsgHandle(byte[] obj)
    {

    }
}
public async void Receive()
{
    while (client.Connected)
    {
        try
        {
            byte[] buff = new byte[4096];
            int length = await client.GetStream().ReadAsync(buff, 0, buff.Length);
            if (length > 0)
            {
                Debug.Log($"接收到的数据长度:{length}");
                //接收到处理CopyToData给MessageHelper处理信息
                MessageHelper.Instance.CopyToData(buff, length);
            }
            else
            {
                client.Close();
            }
        }
        catch (Exception e)
        {
            Debug.Log(e.Message);
            client.Close();
        }
    }
}

发送消息的时候必须按照格式发送

//按格式封装后,发送消息
public void SendToClient(int id, string str)
{
    //包体转换为byte[]
    var body = Encoding.UTF8.GetBytes(str);

    //包体大小(4) 协议ID(4) 包体内容
    byte[] send_buff = new byte[body.Length + 8];

    int size = body.Length;
    var _size = BitConverter.GetBytes(size);
    var _id = BitConverter.GetBytes(id);

    Array.Copy(_size, 0, send_buff, 0, 4);
    Array.Copy(_id, 0, send_buff, 4, 4);
    Array.Copy(body, 0, send_buff, 8, body.Length);

    Send(send_buff);
}

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

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

相关文章

【JavaEE多线程】synchronized原理篇

目录 一、synchronized的优化机制 1)无锁状态 2)偏向锁状态:非必要&#xff0c;不加锁 3)轻量级锁 4)重量级锁&#xff1a;挂起等待 二、锁消除 三、锁粗化 锁的粒度 锁粗化的好处 在这一篇文章当中&#xff0c;我们也提到了synchronized的作用。 Java对于synchroniz…

Python super()函数:调用父类的构造方法

Python 中子类会继承父类所有的类属性和类方法。严格来说&#xff0c;类的构造方法其实就是实例方法&#xff0c;因此毫无疑问&#xff0c;父类的构造方法&#xff0c;子类同样会继承。但我们知道&#xff0c;Python 是一门支持多继承的面向对象编程语言&#xff0c;如果子类继…

『Nonebot 插件编写教程』nonebot2处理消息的完整过程

文章目录前言捕获消息处理消息Bot机器人参数Event事件参数回复消息字符串与Message调用MessageSegment接口前言 前面已经有不止一篇博客教大家如何搭建nonebot2环境了大家可以去专栏查看&#xff0c;这篇博客并不会再次带大家来搭建nonebot2环境&#xff0c;而是着手与插件的编…

【微服务】Sentinel规则持久化

Sentinel 规则持久化 一、修改微服务 修改微服务&#xff0c;让其监听Nacos中的sentinel规则配置。 具体步骤如下&#xff1a; 1.引入依赖 在order-service中引入sentinel监听nacos的依赖&#xff1a; <dependency><groupId>com.alibaba.csp</groupId>…

数据结构实验三: 图的操作与实现

数据结构实验一:线性表,堆栈和队列实现 数据结构实验二 :二叉树的操作与实现 数据结构实验三: 图的操作与实现 数据结构实验四 : 查找和排序算法实现 文章目录一、实验目的&#xff1a;二、使用仪器、器材三、实验内容及原理1、教材P310实验题1&#xff1a;实现图的邻接矩阵和邻…

Springboot扩展点之BeanFactoryPostProcessor

Springboot扩展点之BeanFactoryPostProcessor1.功能特性BeanFactoryPostProcessor的执行是Spring Bean生命周期非常重要的一部分&#xff1b; BeanFactory级别的后置处理器&#xff0c;在Spring生命周期内&#xff0c;org.springframework.beans.factory.config.BeanFactoryPos…

【C语言】10题相关讲解+总结----有用的知识1

总结【C语言】10题&#xff0c;有兴趣的可以看看1.结构体与typedef联系2.结构体中涉及的操作符3.指针数组与数组指针4.数组首元素的作用5.喝汽水问题6.上三角矩阵判定7 矩阵相等判定8.VS调试技巧9.Debug与Release关系10.调整奇数偶数顺序11.有序序列合并1.结构体与typedef联系 …

开发互动直播应用很简单:声网 Android Demo保姆级运行教程

本文作者是来自声网开发者社区的用户“Xiaohua”。 前言 本人在参与《声网开发者漫游指南》期间&#xff0c;通过学习了解和学会跑通声网的实时互动Demo&#xff0c;但因为课程提供的demo是移动端和pc端的&#xff0c;很少接触过&#xff0c;所以只能花点时间学习一下才能运行…

如何屏蔽 iOS 软件自动更新,去除更新通知和标记

如何禁用 iPhone、iPad 软件自动更新。适用于 iOS、iPadOS 和 watchOS&#xff0c;即 iPhone、iPad 和 Apple Watch 通用。 请访问原文链接&#xff1a;https://sysin.org/blog/disable-ios-update/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&a…

WebAssembly编译之(4)-WASM编译进阶(多文件、多接口)

引言 上一节我们讲到如何用Emscripten将一个C编译陈wasm&#xff0c;并导出可供Javascirpt调用的接口&#xff0c;以及C导出类的函数接口、导出类的封装对象等。然而&#xff0c;编译的方式比较玛法&#xff0c;有没办法能更友好一点实现wasm的编译呢 WASM 相关文档&#xff1a…

【自学Docker】Docker diff命令

Docker diff命令 大纲 docker diff命令教程 docker diff 命令用于比较一个 Docker容器 不同版本提交的文件差异。该命令后面的 CONTAINER 可以是容器Id&#xff0c;或者是容器名。 docker diff命令会列出 3 种容器内文件状态变化&#xff08;A - Add, D - Delete, C - Chang…

Java-基础-3.容器

一&#xff1a;为什么会出现容器&#xff1f; 在之前的学习中&#xff0c;我们学习了变量和常量。都是一个字符或者字符串&#xff0c;数字的情况。但是在实际的生产中&#xff0c;我们一次会接受到很多类型不同&#xff0c;个数不同的数据。所以&#xff0c;为了方便我们后续…

红杉:2022企业数字化年度指南

省时查报告-专业、及时、全面的行研报告库省时查方案-专业、及时、全面的营销策划方案库【免费下载】2022年12月份热门报告盘点罗振宇2023年跨年演讲PPT原稿吴晓波2022年年终秀演讲PPT原稿2023年&#xff0c;如何科学制定年度规划&#xff1f;《底层逻辑》高清配图华为2021数字…

[基础语法] python语法之列表的基本操作

文章目录列表已发布列表的基本操作增删改查排序列表实例练习列表 已发布 python判断语句python循环语句python之列表list python 的数据格式主要有列表、字典、元组、集合。其中列表的使用最为广泛。 任何一种数据格式的使用都离不开增、删、改、查四个操作。列表除了这四个…

【Mysql第四期 运算符规则计算】

文章目录写在前面1.算数运算符2.比较运算符3.逻辑运算符4.位运算符5.运算符的优先级拓展&#xff1a;使用正则表达式查询写在前面 基本的运算符号在计算机编程领域都是相通的&#xff0c;会有自己的一些特定符号语言&#xff0c;就像是各地的普通话一样&#xff0c;尽管语音描…

剑指 Offer II 004只出现一次的数字

给你一个整数数组 nums &#xff0c;除某个元素仅出现 一次 外&#xff0c;其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。 示例 1&#xff1a; 输入&#xff1a;nums [2,2,3,2] 输出&#xff1a;3 示例 2&#xff1a; 输入&#xff1a;nums [0,1,0,…

Linux中Vi编辑器和Vim编辑器

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;Java案例分…

Docker常用命令总结

基础命令 1.启动docker systemctl start docker 2.关闭docker systemctl stop docker 3.设置docker为自启动 systemctl enable --now docker 4.重启docker systemctl restart docker 3.查看docker版本信息 docker version 4.查看docker详细信息 docker info Clien…

Spring定时器超过30分钟问题

目前需要定时器做一个定时扫描任务的功能&#xff0c;原先都是定时在半个小时&#xff0c;程序跑起来也没事。但是最近公司要求定时时间加长到45分钟&#xff0c;而调整完配置完后发现&#xff0c;程序是在45分钟和整点进行的扫描。 下面是我做的示例时间缩短为45秒 spring。x…

守护进程编程流程及代码实现

概念不做阐述&#xff0c;本文主要内容为守护进程编程部分的知识说明 守护进程的编程流程&#xff1a; 1.fork退出父进程&#xff0c;保证留下的子进程是组员进程 2.利用setsid()创建新会话&#xff0c;把子进程挪到新的会话中 //获取会话是getsid() 3.fork退出父进程&#x…