unity + python socket通信,自定义数据包

news2024/11/25 8:12:39

unity和python相互之间通过socket通信来发送自定义数据包是一个利用unity构建场景和通过python来做数据处理的方式,能够有效的利用两种不同语言的优势。

我已经将对应的操作封装为对应的一个模块,SocketTools.cs,我们先来看一下具体的代码用法(这段代码是一个简单的将unity主相机渲染的图像经过编码处理后通过socket发送给python并接收python对图像处理后结果并显示的一个代码):

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
using System.Text;
using System.Runtime.InteropServices;
using System.Linq;
using UnityEngine.UI;




public class HttpClient : MonoBehaviour
{
    public Camera cam;

    //关于相机的参数
    public RawImage rawImage;

    private float timeOld = 0;
    private RenderTexture cameraView = null;
    private Texture2D screenShot = null;
    private Texture2D texture = null;
    private byte[] image_recv_bytes = new byte[0];

    SocketTools tools = new SocketTools();
    private bool renderState = false;

    // Start is called before the first frame update
    void Start()
    {
        cameraView = new RenderTexture(Screen.width, Screen.height, 8);
        cameraView.enableRandomWrite = true;
        texture = new Texture2D(500, 400);
        Rigidbody rigidbody = GetComponent<Rigidbody>();
        rigidbody.angularVelocity = new Vector3(0,0.2f,0);

        timeOld = Time.time;
        tools.SocketInit();
        tools.SetCallBackFun(CallBackFun);
        tools.ConnectServer("127.0.0.1", 4444);
    }

    public void CallBackFun(SocketStatus socketStatus, System.Object obj)
    {
        switch (socketStatus)
        {
            case SocketStatus.eSocketConnecting:
                Debug.Log("正在连接服务器...");
                break;
            case SocketStatus.eSocketConnected:
                Debug.Log("服务器连接成功...");
                break;
            case SocketStatus.eSocketReceived:
                {
                    //Debug.Log("接收到消息:" + System.Text.Encoding.Default.GetString((byte[])obj));
                    image_recv_bytes = (byte[])obj;
                }
                break;
//             case SocketStatus.eSocketSending:
//                 Debug.Log("正在发送消息...");
//                 break;
//             case SocketStatus.eSocketSent:
//                 Debug.Log("消息已发送...");
//                 break;
            case SocketStatus.eSocketClosed:
                Debug.Log("服务器已断开...");
                break;
            default:
                break;
        }
    }

    // Update is called once per frame
    void Update()
    {
        if (tools.Connected && renderState)
        {
            renderState = false;
            if (null == screenShot)
            {
                screenShot = new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, false);
            }
            // 读取屏幕像素进行渲染
            cam.targetTexture = cameraView;
            cam.Render();
            RenderTexture.active = cameraView;
            screenShot.ReadPixels(new Rect(0, 0, cameraView.width, cameraView.height), 0, 0);
            screenShot.Apply();
            byte[] bytes = screenShot.EncodeToJPG();
//             //实例化一个文件流--->与写入文件相关联  
//             FileStream fs = new FileStream("test_unity.jpg", FileMode.Create);
//             //开始写入  
//             fs.Write(bytes, 0, bytes.Length);
//             //清空缓冲区、关闭流  
//             fs.Flush();
//             fs.Close();
//             return;
            cam.targetTexture = null;
            try
            {
                timeOld = Time.time;
                tools.SendToServer(tools.PackData(bytes, 1));
            }
            catch
            {
                if (!tools.Connected)
                {
                    tools.SocketClose();
                    Debug.Log("the server has been disconnected. ");
                }
            }
        }

        if (image_recv_bytes != null)
        {
            Debug.Log("图片大小:" + image_recv_bytes.Length + "bytes, 帧间延时:" + Time.deltaTime * 1000 + "ms, 网络时间延迟:" + ((Time.time - timeOld) * 1000).ToString() + "ms");
            texture.LoadImage(image_recv_bytes);
            image_recv_bytes = null;
            rawImage.texture = texture;
            renderState = true;
        }

        if (Input.GetKeyDown(KeyCode.Space))
        {
            if (tools.Connected)
            {
                tools.SocketClose();
                Debug.Log("socket closed. ");
            }
            else
            {
                tools.ConnectServer("127.0.0.1", 4444);
            }
        }

    }

}

下面是对应的代码效果:

unity+python

下面将会针对一些技术细节做详细的说明,如果您对此实现的细节不感兴趣,您也可以直接下载我已经完成的项目代码:

unity和python通过自定义socket协议实现异步通信资源-CSDN文库

接下来是详细的技术实现过程,总的来讲主要技术手段分为三个部分:

1. python和unity异步socket的实现

2. python和unity处理自定义数据包的拆分和合并的实现

3. python和unity两者socket通信的数据包认证实现

4. unity数据处理异步封装逻辑

1. python和unity异步socket的实现

a. python异步socket

由于此处python主要是作为服务端的,所以此处pytho代码主要是讲python作为socket tcp server来实现的。

首先是初始化服务器代码

# 初始化服务器
    def socket_init(self, host, port):
        # 1 创建服务端套接字对象
        self.tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # signal_update.emit(0.2, 1, 1, 'socket object created...')
        # 设置端口复用,使程序退出后端口马上释放
        self.tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
        # 2 绑定端口
        self.tcp_server.bind((host, port))
        # signal_update.emit(0.7, 1, 1, "socket bind successfully...")
        print('server port bind successfully, server host: {}, server port: {}...'.format(host, port))

然后是启动服务器监听代码,首先 start 开启一个新的线程用于监听tcp服务器的状态,然后在start_thread_fun线程函数中具体实现对应的代码,具体如下:

# 开始的线程函数,外部最好调用 start() 函数,不要调用此函数
    # 否则会阻塞
    def start_thread_fun(self):
        print("server_thread started. ")
        # 3 设置监听
        self.tcp_server.listen(self.max_connections)
        print('start to listen connections from client, max client count: {}'.format(
            self.max_connections))
        # 4 循环等待客户端连接请求(也就是最多可以同时有128个用户连接到服务器进行通信)
        while True:
            tcp_client_1, tcp_client_address = self.tcp_server.accept()
            self.tcp_clients.append(tcp_client_1)
            # 创建多线程对象
            thd = threading.Thread(target=self.client_process,
                                   args=(tcp_client_1, tcp_client_address), daemon=True)
            # 设置守护主线程  即如果主线程结束了 那子线程中也都销毁了  防止主线程无法退出
            thd.setDaemon(True)
            # 启动子线程对象
            thd.start()
            print("new client connected, client address: {}, total client count: {}".format(tcp_client_address, 1))

    # 启动服务器
    def start(self):
        self.start_thread = threading.Thread(target=self.start_thread_fun, daemon=True)
        self.start_thread.start()
        print("starting server_thread...")

注意:此处主线程不能终止,否则所有子线程都会停止,可以在主线程添加time.sleep(100)来手动延迟主线程运行时间。

b. unity 异步socket

unity的异步socket相较而言会比较麻烦一点,因为它的异步状态每次都需要手动的通过代码去复位调整,下面将针对其过程做具体的说明。

关于整个流程我觉得用流程图会比较合适,下面是代码的一个简单的流程图:

 其中各个地方的代码内容如下所示:

public void SocketInit()
    {
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    }

public void ConnectServer(string host, int port)
    {
        try
        {
            SocketAsyncEventArgs args = new SocketAsyncEventArgs();//创建连接参数对象
            this.endPoint = new IPEndPoint(IPAddress.Parse(host), port);
            args.RemoteEndPoint = this.endPoint;
            args.Completed += OnConnectedCompleted;//添加连接创建成功监听
            socket.ConnectAsync(args); //异步创建连接
        }
        catch (Exception e)
        {
            Debug.Log("服务器连接异常:" + e);
        }
    }

private void OnConnectedCompleted(object sender, SocketAsyncEventArgs args)
    {
        try
        {   ///连接创建成功监听处理
            if (args.SocketError == SocketError.Success)
            {
                StartReceiveMessage(); //启动接收消息

            }
        }
        catch (Exception e)
        {
            Debug.Log("开启接收数据异常" + e);
        }

    }


    private void StartReceiveMessage(int bufferSize = 40960)
    {
        //启动接收消息
        SocketAsyncEventArgs receiveArgs = new SocketAsyncEventArgs();
        //设置接收消息的缓存大小,正式项目中可以放在配置 文件中
        byte[] buffer = new byte[bufferSize];
        //设置接收缓存
        receiveArgs.SetBuffer(buffer, 0, buffer.Length);
        receiveArgs.RemoteEndPoint = this.endPoint;
        receiveArgs.Completed += OnReceiveCompleted; //接收成功
        socket.ReceiveAsync(receiveArgs);//开始异步接收监听
    }

public void OnReceiveCompleted(object sender, SocketAsyncEventArgs args)
    {
        try
        {
            //Debug.Log("网络接收成功线程:" + Thread.CurrentThread.ManagedThreadId.ToString());

            if (args.SocketError == SocketError.Success && args.BytesTransferred > 0)
            {
                //创建读取数据的缓存
                byte[] bytes = new byte[args.BytesTransferred];
                //将数据复制到缓存中
                Buffer.BlockCopy(args.Buffer, 0, bytes, 0, args.BytesTransferred);
                Debug.Log(bytes);
                //再次启动接收数据监听,接收下次的数据。
                StartReceiveMessage();
            }
        }
        catch (Exception e)
        {
            Debug.Log("接收数据异常:" + e);
        }
    }

public void SendToServer(byte[] data)
    {
        try
        {
            //创建发送参数
            SocketAsyncEventArgs sendEventArgs = new SocketAsyncEventArgs();
            sendEventArgs.RemoteEndPoint = endPoint;
            //设置要发送的数据
            sendEventArgs.SetBuffer(data, 0, data.Length);
            sendEventArgs.Completed += OnSendCompleted;
            //异步发送数据
            socket.SendAsync(sendEventArgs);

        }
        catch (Exception e)
        {
            Debug.Log("发送数据异常:" + e);
        }
    }

    public void OnSendCompleted(object sender, SocketAsyncEventArgs args)
    {
        if (args.SocketError == SocketError.Success)
        {
            Debug.Log("send ok");
        }
    }

public void SocketClose()
    {
        socket.Close();
    }

其中代码包含了一些最简单的异步c#socket的代码。

2. python和unity处理自定义数据包的拆分和合并的实现

但是大家都知道,tcp是流式传输协议,特别是针对图像这类大的数据包是不能够很好的实现帧之间的拆分的,我们需要自定义协议的数据包头和数据包体来实现不定长的数据传输,接下来讲一下如何在python和c#中实现字节层面上的数据拆分和组合。

a. python

python主要采用struct.pack和struct.unpack函数来实现数据的拆分和组合。具体的代码如下图所示:

    # 判断当前的数据头对不对,如果正确返回解析结果
    def process_protocol(self, header):
        header_unpack = struct.unpack(self.header_format, header)
        if header_unpack[0] == self.header_bytes:
            return True, header_unpack
        else:
            return False, None

    def pack_data(self, data, data_type):
        if type(data) is str:
            data = data.encode()
        data_pack = struct.pack(self.header_format, self.header_bytes, data_type, len(data))
        # print("datalen:{}".format(len(data)))
        return data_pack+data


# 部分相关变量定义
self.header_bytes = b'\x0a\x0b'  # 占用两个字节
self.header_format = "2ssi"


b. unity

在unity的c#中,为了便于解析对应的数据帧头的内容,采用的是将对应的byte[]类型的变量直接转换为struct的方式,对应的两者相互转换的代码如下:

public static object BytesToStruct(byte[] buf, int len, Type type)
    {
        object rtn;
        IntPtr buffer = Marshal.AllocHGlobal(len);
        Marshal.Copy(buf, 0, buffer, len);
        rtn = Marshal.PtrToStructure(buffer, type);
        Marshal.FreeHGlobal(buffer);
        return rtn;
    }

public static byte[] StructToBytes(object structObj)
    {
        //得到结构体的大小
        int size = Marshal.SizeOf(structObj);
        //创建byte数组
        byte[] bytes = new byte[size];
        //分配结构体大小的内存空间
        IntPtr structPtr = Marshal.AllocHGlobal(size);
        //将结构体拷到分配好的内存空间
        Marshal.StructureToPtr(structObj, structPtr, false);
        //从内存空间拷到byte数组
        Marshal.Copy(structPtr, bytes, 0, size);
        //释放内存空间
        Marshal.FreeHGlobal(structPtr);
        //返回byte数组
        return bytes;
    }

其次就是一些简单的内存操作,由于c#封装的缘故,我们并不能像c++那样直接通过指针的方式访问内存数据,我们需要借助Buffer类,具体的用法如下所示:

//用于同时操作内存块中的数据
public static void BlockCopy (Array src, int srcOffset, Array dst, int dstOffset, int count);

它可以将src起始位置偏移srcOffset的数据截取count位复制到dst起始位置偏移dstOffset的位置后面count长度的内粗区域中。

如果要截取一个byte[]类型的变量指定范围的数据,需要用到如下的函数:

bytes.Skip(recvLenReal).Take(bytes.Length - recvLenReal).ToArray();

从头跳过recvLenReal的长度然后截取bytes.Length - recvLenReal长的数据并将其转换为byte[]类型的变量。

3. 数据帧封装逻辑

关于自定义数据包的实现主要分为两部分:数据头和数据主体。

数据包帧头的定义方式如下:

数据头数据类型数据主体长度
       

两个字节的数据头用于进行协议的区分辨认;一个字节的数据类型用于标识后续的数据主体的数据类型,例如:图片或者文字或者其他的内容;四个字节的数据主体长度共同组成一个int类型的变量,可以指示后续的数据总长度。

其中该数据头在unity和python的实现方法如下图所示:

public struct DataHeader
{
    public byte header1;
    public byte header2;
    public byte data_type;
    public int data_len;
    
}

python部分的代码上面有展示,此处不再重复。

4. python和unity两者socket通信的数据包认证实现

关于数据包认证的代码,两者采用的是同一个逻辑此处仅提供对应的逻辑处理注释内容,只是在代码的实现上稍有不同。

    def process_raw_data(self, recv_data):
        '''
        关于操作:
            本函数应该具有递归功能,否则无法处理复杂任务
        关于消息接收的逻辑处理:
        1. 首先判断当前是否已经接收过帧头 (self.frame_info.data_type is not None)
            接收过:
                根据帧头的数据长度接收对应的数据体内容
            没接收过:
            判断当前接收的数据长度是否满足帧头的长度
                满足:尝试解析
                    解析失败:正常传输数据
                    解析成功:如果有其他的数据,继续接收处理后续的数据
                不满足:将本次数据输出,丢弃此次的数据 !!!
        '''
        # 如果已经接收过数据头,直接继续接收内容
        if self.frame_info.data_type is not None:
            # 首先计算剩余的数据包长度
            recv_len_left = self.frame_info.data_all_length - self.frame_info.data_recv_length
            # 然后计算本次可以接收的数据长度,选取数据长度和剩余接收长度的最小值
            recv_len_real = min(recv_len_left, len(recv_data))
            self.frame_info.data_buffer += recv_data[:recv_len_real]
            # 更新对应的接收的数据长度
            self.frame_info.data_recv_length = len(self.frame_info.data_buffer)
            # 判断当前是否已经接受完本帧的内容
            if self.frame_info.data_recv_length >= self.frame_info.data_all_length:
                # 根据回调函数返回对应的内容
                if self.callback_fun is not None:
                    self.callback_fun(self.frame_info.data_buffer)
                # 从剩余的数据中尝试检索出对应的数据头
                # 首先更新 recv_data 的数据的内容
                # print(self.frame_info.data_buffer)
                self.frame_info.reset()
                recv_data = recv_data[recv_len_real:len(recv_data)]
                if len(recv_data) != 0:
                    self.process_raw_data(recv_data)
            else:
                return
            # 从剩余的数据中尝试解析数据头
        else:
            if len(recv_data) >= self.header_length:
                ret = self.process_protocol(recv_data[:self.header_length])
                if ret[0]:
                    # 打印出协议对应的内容
                    # print(ret[1])
                    self.frame_info.set(ret[1][1], ret[1][2])
                    # 此处还得继续判断当前是否转换完了,如果没有的话需要继续转换接收到的内容
                    recv_data = recv_data[self.header_length:len(recv_data)]
                    if len(recv_data) != 0:
                        self.process_raw_data(recv_data)
                else:
                    print(recv_data)
            else:
                print(recv_data)
    private void ProcessRawData(ref byte[] bytes)
    {
//         关于操作:
//             本函数应该具有递归功能,否则无法处理复杂任务
//         关于消息接收的逻辑处理:
//         1.首先判断当前是否已经接收过帧头(self.frame_info.data_type is not None)
//             接收过:
//                 根据帧头的数据长度接收对应的数据体内容
//             没接收过:
//             判断当前接收的数据长度是否满足帧头的长度
//                 满足:尝试解析
//                     解析失败:正常传输数据
//                     解析成功:如果有其他的数据,继续接收处理后续的数据
//                 不满足:将本次数据输出,丢弃此次的数据!!!
        
        if (frameInfo.sPyDataTest.data_type != 0)
        {
            int recvLenLeft = frameInfo.sPyDataTest.data_len - frameInfo.data_recv_len;
            int recvLenReal = Math.Min(recvLenLeft, bytes.Length);
            frameInfo.AddBufferData(bytes.Skip(0).Take(recvLenReal).ToArray());
            frameInfo.data_recv_len = frameInfo.GetBufferLength();
            if (frameInfo.data_recv_len >= frameInfo.sPyDataTest.data_len)
            {
                //Debug.Log("接收成功!");
                socketStatus = SocketStatus.eSocketReceived;
                if (callBackFun != null)
                {
                    callBackFun(socketStatus, frameInfo.data_buffer);
                }
                frameInfo.Reset();
                bytes = bytes.Skip(recvLenReal).Take(bytes.Length - recvLenReal).ToArray();
                if (bytes.Length != 0)
                {
                    ProcessRawData(ref bytes);
                }
            }
            else
            {
                return;
            }
        }
        else
        {
            if (recvBuffer != null)
            {
                byte[] newBytes = new byte[recvBuffer.Length + bytes.Length];
                Buffer.BlockCopy(recvBuffer, 0, newBytes, 0, recvBuffer.Length);
                Buffer.BlockCopy(bytes, 0, newBytes, recvBuffer.Length, bytes.Length);
                bytes = newBytes;
                recvBuffer = null;
            }

            if (bytes.Length >= frameInfo.StructLength)
            {
                bool ret = true;
                ret = frameInfo.IsHeaderMatch(bytes.Take(frameInfo.StructLength).ToArray());
                if (ret == true)
                {
                    //Debug.Log(frameInfo.sPyDataTest);
                    frameInfo.Set(frameInfo.sPyDataTest.data_type, frameInfo.sPyDataTest.data_len);
                    bytes = bytes.Skip(frameInfo.StructLength).Take(bytes.Length - frameInfo.StructLength).ToArray();
                    if (bytes.Length != 0)
                    {
                        ProcessRawData(ref bytes);
                    }
                }
                else
                {

                    Debug.Log(bytes);
                }
            }
            else
            {
                recvBuffer = bytes;
                Debug.Log(bytes);
            }
        }
        
    }

总结

此处仅仅提供对应的关键步骤的代码实现手段和原理讲解,如有错误欢迎大家指正!

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

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

相关文章

7.3.2 【Linux】磁盘分区: gdisk/fdisk

MBR 分区表使用 fdisk 分区&#xff0c; GPT 分区表使用 gdisk 分区。 gdisk 通过lsblk或blkid先找到磁盘&#xff0c;再用parted /dev/xxx print来找出内部的分区表类型&#xff0c;之后采用gdisk或fdisk来操作系统。上表中可以发现 gdisk 会扫描 MBR 与 GPT 分区表&#xff…

【Arduino】超声波实验

4个端&#xff1a; Vcc &#xff1a; 5V电源Trig &#xff1a; 控制端&#xff08;触发&#xff09;Echo &#xff1a; 接收端&#xff08;回声&#xff09;Gnd &#xff1a; 接地端 相关参数 &#xff1a; 工作电流 &#xff1a; 15mA工作电压 &#xff1a; 5V工作频率 &am…

Linux常用命令——expr命令

在线Linux命令查询工具 expr 一款表达式计算工具 补充说明 expr命令是一款表达式计算工具&#xff0c;使用它完成表达式的求值操作。 expr的常用运算符&#xff1a; 加法运算&#xff1a;减法运算&#xff1a;-乘法运算&#xff1a;\*除法运算&#xff1a;/求摸&#xff0…

【Android】解决 build项目报错manifest merge fail XXX

报错图片&#xff1a; 解决方式&#xff1a; 找到 AndroidManifest.xml文件&#xff0c;找到找到文件的上一级&#xff0c;加上android:exported“true” 作用&#xff1a;Android:exported true 在Activity中该属性用来标示:当前Activity是否可以被另一个Application的组件启…

牛客网基础语法111~120题

牛客网基础语法111~120题&#x1f618;&#x1f618;&#x1f618; &#x1f4ab;前言&#xff1a;今天是咱们第十一期刷牛客网上的题目。 &#x1f4ab;目标&#xff1a;能使用数组来解决问题。 &#x1f4ab;鸡汤&#xff1a;一张纸对折就能站立。先干为敬&#xff0c;大家随…

自定义MVC框架实现增删改查

目录 一、环境搭建 二、导入配置文夹 1.中央控制器xml 2.增删改配置文件 3.导入工具类 三、编写后端代码 1. 通用增删改查 2. BookDao类 3. book实现增删改查类 4. 分页助手类 四、编写前端代码 1. 数据显示主界面 2. 默认运行显示所有数据servlet 3. 新增、修改共用…

解决uni-app微信小程序底部输入框,键盘弹起时页面整体上移问题

存在问题 做了一个记录页面&#xff08;类似单方聊天页&#xff09;&#xff0c;输入框在底部&#xff1b;当弹出键盘时&#xff0c;页面整体上移&#xff0c;页面头信息会消失不见 需要实现效果&#xff1a; 比如一个记录页面&#xff0c;需要在键盘弹出时&#xff1a; 底…

解析ASEMI代理海矽美快恢复二极管SFP6012A的性能与应用

编辑-Z 在电子元件领域&#xff0c;快恢复二极管是一种重要的半导体器件&#xff0c;它在电路中起到关键的保护和控制作用。今天&#xff0c;我们将重点介绍一款优秀的快恢复二极管——SFP6012A&#xff0c;深入探讨其性能特点和应用领域。 一、SFP6012A快恢复二极管的性能特点…

chatglm docker镜像,一键部署chatglm本地知识库

好久没有写文章了&#xff0c;今天有空&#xff0c;记录一下chatglm本地知识库的docker镜像制作过程。 核心程序是基于“闻达”开源项目&#xff0c;稍作改动。 制作镜像&#xff1a; docker tag chatglm:v1 ch1949/chatglm:latest docker push ch1949/chatglm:latest 使用 …

性能测试小白‘壁咚’~~~

很多时候&#xff0c;我们都知道软件有黑白盒测试&#xff0c;但往往还遗漏掉了一个性能测试。 性能测试种类&#xff1a; 负载测试压力测试并发测试配置测试可靠性测试容量测试 1、负载测试 &#xff08;1&#xff09;定义 负载测试是指逐步增加系统负载&#xff0c;测试系统…

NSS [SWPUCTF 2021 新生赛]easy_md5

NSS [SWPUCTF 2021 新生赛]easy_md5 先看题目&#xff0c;md5弱比较&#xff0c;可以0e&#xff0c;数组&#xff0c;或者强碰撞。 payload&#xff1a; GET&#xff1a; ?name[]1 POST&#xff1a;password[]7

【面试系列】八股文之线程篇202306

union all和union的区别 union all&#xff1a;包含重复行 union&#xff1a;不包含重复行 线程池的shutdown()与shutdownNow()方法的区别 shutdown()&#xff0c;调用shutdown方法&#xff0c;线程池会拒绝接收新的任务&#xff0c;处理中的任务和阻塞队列中的任务会继续处…

redis基础及哨兵集群部署、故障切换

一、概述 Redis是一个开源的&#xff0c;使用C语言编写&#xff0c;支持网络&#xff0c;可基于内存工作亦可持久化&#xff08;AOF、RDB&#xff09;的日志型&#xff0c;key-values&#xff08;键值对&#xff09;数据库&#xff0c;一个速度极快的非关系型数据库&#x…

R语言APSIM模型及批量模拟

随着数字农业和智慧农业的发展&#xff0c;基于过程的农业生产系统模型在模拟作物对气候变化的响应与适应、农田管理优化、作物品种和株型筛选、农田固碳和温室气体排放等领域扮演着越来越重要的作用。 APSIM (Agricultural Production Systems sIMulator)模型是世界知名的作物…

Python 数据类型转换

文章目录 每日一句正能量前言隐式类型转换实例实例 显式类型转换实例实例实例实例 每日一句正能量 在人生的道路上&#xff0c;即使一切都失去了&#xff0c;只要一息尚存&#xff0c;你就没有丝毫理由绝望。因为失去的一切&#xff0c;又可能在新的层次上复得。 前言 有时候&…

HOT41-二叉树的层序遍历

leetcode原题链接&#xff1a;二叉树的层序遍历 题目描述 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&…

redis如何实现持久化

RDB快照 RDB是一种快照存储持久化方式&#xff0c;具体就是将Redis某一时刻的内存数据保存到硬盘的文件当中&#xff0c;默认保存的文件名为dump.rdb&#xff0c;而在Redis服务器启动时&#xff0c;会重新加载dump.rdb文件的数据到内存当中恢复数据。 开启RDB持久化方式 开启…

Vue生态及实践 - Vue Router(2)

目录 导航守卫 解析流程 代码演示 router.js pages/Foo.vue router/router.js router/history/base.js router/util/async.js router/components/RouterView.vue 接上一篇文章~&#xff0c;代码部分会有部分重叠&#xff0c;想看完整版的代码&#xff0c;看这篇文章的…

END-TO-END OPTIMIZED IMAGE COMPRESSION论文阅读

END-TO-END OPTIMIZED IMAGE COMPRESSION 文章目录 END-TO-END OPTIMIZED IMAGE COMPRESSION单词重要不重要 摘要&#xff1a; 单词 重要 image compression 图像压缩 quantizer 量化器 rate–distortion performance率失真性能 不重要 a variant of 什么什么的一个变体 …

大数据之flink容错机制

大数据之flink容错机制https://blog.51cto.com/dashujutongmeng/5241407