C#上位机与三菱PLC的通信08---开发自己的通讯库(A-1E版)

news2024/12/25 1:13:28

1、A-1E报文回顾

 

具体细节请看:

C#上位机与三菱PLC的通信03--MC协议之A-1E报文解析

C#上位机与三菱PLC的通信04--MC协议之A-1E报文测试

2、为何要开发自己的通讯库

前面使用了第3方的通讯库实现了与三菱PLC的通讯,实现了数据的读写,对于通讯库,我们只要引用并调用相关的方法即可实现目的,为什么别人可以封装通讯库dll文件,自己能不能做到?当然可以,但写一个通讯库需要非凡的技术,需要考虑的东西很多,比如扩展性,通用性,等等之类的。通过封装通讯库达到更高的层次,想想,别人使用自己的东西,说明自己牛XXXX啊,大师就是这样锻造出来的,接下来马上安排,鸿鹄之志从小事做起,振兴工业自动化,匹夫有责。

3、空谈误国,实干兴邦

1、创建vs项目

2、添加类库项目

3、创建目录及基础类 

 

     

 

 

  AreaCode.cs代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Mitsubishi.Communication.MC.Mitsubishi.Base
{
    /// <summary>
    /// 存储区枚举
    /// </summary>
    public enum AreaCode
    {
        D = 0xA8,
        X = 0x9C,
        Y = 0x9D,
        M = 0x90,
        R = 0xAF,
        S = 0x98,
        TS = 0xC1,
        CN = 0xC5
    }
}

MelsecBase.cs代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Mitsubishi.Communication.MC.Mitsubishi.Base
{
    /// <summary>
    /// mc协议基类
    /// </summary>
    public class MelsecBase
    {
        /// <summary>
        /// plc的ip地址
        /// </summary>
        public string _ip;
        /// <summary>
        /// plc的端口号
        /// </summary>
        public int _port;
        /// <summary>
        /// socket对象
        /// </summary>
        public Socket socket = null;
        /// <summary>
        /// 超时事件
        /// </summary>
        ManualResetEvent TimeoutObject = new ManualResetEvent(false);
        /// <summary>
        /// 连接状态
        /// </summary>
        bool connectState = false;

        /// <summary>
        /// 构造方法
        /// </summary>
        /// <param name="ip"></param>
        /// <param name="port"></param>
        public MelsecBase(string ip, short port)
        {
            _ip = ip;
            _port = port;
            // 初始化一个通信对象
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }

        /// <summary>
        /// 连接PLC
        /// </summary>
        /// <param name="timeout">超时时间</param>
        /// <returns></returns>
        public Result Connect(int timeout = 50)
        {
            TimeoutObject.Reset();
            Result result = new Result();
            try
            {
                if (socket == null)
                {
                    socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                }
                int count = 0;
                while (count < timeout)
                {
                    if (!(!socket.Connected || (socket.Poll(200, SelectMode.SelectRead) && (socket.Available == 0))))
                    {
                        return result;
                    }
                    try
                    {
                        socket?.Close();
                        socket.Dispose();
                        socket = null;

                        socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                        //异步连接 
                        socket.BeginConnect(_ip, _port, callback =>
                        {
                            connectState = false;
                            var cbSocket = callback.AsyncState as Socket;
                            if (cbSocket != null)
                            {
                                connectState = cbSocket.Connected;
                                if (cbSocket.Connected)
                                {
                                    cbSocket.EndConnect(callback);
                                }
                            }
                            TimeoutObject.Set();
                        }, socket);
                        TimeoutObject.WaitOne(2000, false);
                        if (!connectState)
                        {
                            throw new Exception();
                        }
                        else
                        {
                            break;
                        }
                    }
                    catch (SocketException ex)
                    {
                        if (ex.ErrorCode == 10060)
                        {
                            throw new Exception(ex.Message);
                        }
                    }
                    catch (Exception ex)
                    {
                        throw new Exception(ex.Message);
                    }
                    finally
                    {
                        count++;
                    }
                }
                if (socket == null || !socket.Connected || ((socket.Poll(200, SelectMode.SelectRead) && (socket.Available == 0))))
                {
                    throw new Exception("网络连接失败");
                }
            }
            catch (Exception ex)
            {
                result.IsSuccessed = false;
                result.Message = ex.Message;
            }
            return result;
        }

        /// <summary>
        /// 构建开始地址
        /// </summary>
        /// <param name="areaCode">存储区</param>
        /// <param name="startAddr">开始地址</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public List<byte> StartToBytes(AreaCode areaCode, string startAddr)
        {
            List<byte> startBytes = new List<byte>();
            if (areaCode == AreaCode.X || areaCode == AreaCode.Y)
            {
                string str = startAddr.ToString().PadLeft(8, '0');
                for (int i = str.Length - 2; i >= 0; i -= 2)
                {
                    string v = str[i].ToString() + str[i + 1].ToString();
                    startBytes.Add(Convert.ToByte(v, 16));
                }
            }
            else
            {
                int addr = 0;
                if (!int.TryParse(startAddr, out addr))
                {
                    throw new Exception("软元件地址不支持!");
                }
                startBytes.Add((byte)(addr % 256));
                startBytes.Add((byte)(addr / 256 % 256));
                startBytes.Add((byte)(addr / 256 / 256 % 256));
                startBytes.Add((byte)(addr / 256 / 256 / 256 % 256));
            }

            return startBytes;
        }

        /// <summary>
        /// 发送报文
        /// </summary>
        /// <param name="reqBytes">字节集合</param>
        /// <param name="count">字节长度</param>
        /// <returns></returns>
        public virtual List<byte> Send(List<byte> reqBytes, int count)
        {
            return null;
        }

        /// <summary>
        /// 数据解析
        /// </summary>
        /// <typeparam name="T">读取的数据类型</typeparam>
        /// <param name="datas">数据列表</param>
        /// <param name="typeLen">类型长度</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public List<T> AnalysisDatas<T>(List<byte> datas, int typeLen)
        {
            List<T> resultDatas = new List<T>();
            if (typeof(T) == typeof(bool))//bool类型
            {
                for (int i = 0; i < datas.Count; i++)
                {
                    // 10 10 10 10 10
                    string binaryStr = Convert.ToString(datas[i], 2).PadLeft(8, '0');
                    dynamic state = binaryStr.Substring(0, 4) == "0001";
                    resultDatas.Add(state);
                    state = binaryStr.Substring(4) == "0001";
                    resultDatas.Add(state);
                }
            }
            else//其他类型:ushort,short,float
            {
                for (int i = 0; i < datas.Count;)
                {
                    List<byte> valueByte = new List<byte>();
                    for (int sit = 0; sit < typeLen * 2; sit++)
                    {
                        valueByte.Add(datas[i++]);
                    }
                    Type tBitConverter = typeof(BitConverter);
                    MethodInfo method = tBitConverter.GetMethods(BindingFlags.Public | BindingFlags.Static).FirstOrDefault(mi => mi.ReturnType == typeof(T)) as MethodInfo;
                    if (method == null)
                    {
                        throw new Exception("未找到匹配的数据类型转换方法");
                    }
                    resultDatas.Add((T)method?.Invoke(tBitConverter, new object[] { valueByte.ToArray(), 0 }));
                }
            }

            return resultDatas;
        }

        /// <summary>
        /// 计算长度
        /// </summary>
        /// <typeparam name="T">读取的数据类型</typeparam>
        /// <returns></returns>
        public int CalculatLength<T>()
        {
            int typeLen = 1;
            if (!typeof(T).Equals(typeof(bool)))
            {
                typeLen = Marshal.SizeOf<T>() / 2;// 每一个数据需要多少个寄存器
            }
            return typeLen;
        }

        /// <summary>
        /// 获取数据的字节列表
        /// </summary>
        /// <typeparam name="T">数据类型</typeparam>
        /// <param name="values">数据列表</param>
        /// <returns></returns>
        public List<byte> GetDataBytes<T>(List<T> values)
        {
            List<byte> datas = new List<byte>();
            int count = values.Count;
            if (typeof(T) == typeof(bool))//bool类型的数据
            {
                dynamic value = false;
                // 添加一个填充数据,保存一个完整字节
                if (values.Count % 2 > 0)
                {
                    values.Add(value);
                }

                for (int i = 0; i < values.Count; i += 2)
                {
                    byte valueByte = 0;
                    if (bool.Parse(values[i].ToString()))
                    {
                        valueByte |= 16;
                    }
                    if (bool.Parse(values[i + 1].ToString()))
                    {
                        valueByte |= 1;
                    }
                    datas.Add(valueByte);
                }
            }
            else //其他类型:float,short,int16
            {
                for (int i = 0; i < values.Count; i++)
                {
                    dynamic value = values[i];
                    datas.AddRange(BitConverter.GetBytes(value)); // MC不需要字节的颠倒
                }
            }

            return datas;
        }
    }
}

Result.cs代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Mitsubishi.Communication.MC.Mitsubishi.Base
{
    /// <summary>
    /// 结果类
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class Result<T>
    {
        /// <summary>
        /// 状态
        /// </summary>
        public bool IsSuccessed { get; set; }
        /// <summary>
        /// 对应的消息
        /// </summary>
        public string Message { get; set; }
        /// <summary>
        /// 数据列表
        /// </summary>
        public List<T> Datas { get; set; }


        public Result() : this(true, "OK") { }
        public Result(bool state, string msg) : this(state, msg, new List<T>()) { }
        public Result(bool state, string msg, List<T> datas)
        {
            this.IsSuccessed = state; Message = msg; Datas = datas;
        }
    }

    public class Result : Result<bool> { }
}

确保上面的三个类文件编译成功,继续干

4、编写核心的通信类A1E.cs 

  

A1E.cs完整代码: 

using Mitsubishi.Communication.MC.Mitsubishi.Base;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace Mitsubishi.Communication.MC.Mitsubishi
{
    /// <summary>
    /// A1E报文通讯库
    /// </summary>
    public class A1E : MelsecBase
    {
        /// <summary>
        /// 构造方法
        /// </summary>
        /// <param name="ip"></param>
        /// <param name="port"></param>
        public A1E(string ip, short port) : base(ip, port)
        {

        }

        #region 读取数据

        /// <summary>
        /// 读取数据
        /// </summary>
        /// <typeparam name="T">读取的数据类型</typeparam>
        /// <param name="address">开始地址</param>
        /// <param name="count">读取长度</param>
        /// <returns></returns>
        public Result<T> Read<T>(string address, short count)
        {
            AreaCode areaCode;
            string start;
            (areaCode, start) = this.AnalysisAddress(address);
            return Read<T>(areaCode, start, count);
        }

        /// <summary>
        /// 读取数据
        /// </summary>
        /// <typeparam name="T">读取的数据类型</typeparam>
        /// <param name="areaCode">存储区代码</param>
        /// <param name="startAddr">开始地址</param>
        /// <param name="count">读取长度</param>
        /// <returns></returns>
        public Result<T> Read<T>(AreaCode areaCode, string startAddr, short count)
        {
            Result<T> result = new Result<T>();
            try
            {
                var connectState = this.Connect();
                if (!connectState.IsSuccessed)
                {
                    throw new Exception(connectState.Message);
                }

                //读取类型
                byte readCode = (byte)(typeof(T) == typeof(bool) ? 0x00 : 0x01);
                //起始地址
                List<byte> startBytes = this.StartToBytes(areaCode, startAddr);
                //存储区代码
                List<byte> areaBytes = this.AreaToBytes(areaCode);
                //读取长度
                int typeLen = this.CalculatLength<T>();
                //组装报文
                List<byte> command = new List<byte> {
                    readCode,///读取类型
                    0xFF,0x0A,0x00,//0xFF指PLC编号,0x0A,0x00指超时时间,超时时间是以250ms为单位 
                    startBytes[0],startBytes[1],startBytes[2],startBytes[3], // 起始地址,占4个字节
                    areaBytes[0],areaBytes[1], // 存储区,占2个字节
                    (byte)(typeLen*count%256),// 读取长度,低位
                    (byte)(typeLen*count/256%256) // 读取长度,高位
                };
                //计算响应报文的长度
                int respLen = typeLen * 2 * count;
                if (typeof(T) == typeof(bool))
                {
                    respLen = (int)Math.Ceiling(typeLen * count * 1.0 / 2);
                }
                //发送报文
                List<byte> respBytes = this.Send(command, respLen);
                //数据解析
                result.Datas = this.AnalysisDatas<T>(respBytes, typeLen);
            }
            catch (Exception ex)
            {
                result = new Result<T>(false, ex.Message);
            }

            return result;
        }

        #endregion 

        #region 写入数据

        /// <summary>
        /// 写数据
        /// </summary>
        /// <typeparam name="T">写入的数据类型</typeparam>
        /// <param name="values">数据值列表</param>
        /// <param name="addr">开始地址</param>
        /// <returns></returns>
        public Result Write<T>(List<T> values, string addr)
        {
            AreaCode areaCode; string start;
            (areaCode, start) = this.AnalysisAddress(addr);
            return this.Write<T>(values, areaCode, start);
        }

        /// <summary>
        /// 写数据
        /// </summary>
        /// <typeparam name="T">写入的数据类型</typeparam>
        /// <param name="values">数据值列表</param>
        /// <param name="areaCode">存储区代码</param>
        /// <param name="startAddr">开始地址</param>
        /// <returns></returns>
        public Result Write<T>(List<T> values, AreaCode areaCode, string startAddr)
        {
            Result result = new Result();

            try
            {
                var connectState = this.Connect();
                if (!connectState.IsSuccessed)
                {
                    throw new Exception(connectState.Message);
                }

                // 写操作的类型 //0x00 批量位读取 //0x01 批量字读取 //0x02 批量位写入 //0x03 批量字写入 //0x04 随机位写入 //0x05 随机字写入
                byte writeCode = (byte)(typeof(T) == typeof(bool) ? 0x02 : 0x03);
                //开始地址
                List<byte> startBytes = this.StartToBytes(areaCode, startAddr);
                //存储区代码
                List<byte> areaBytes = this.AreaToBytes(areaCode);
                //构建数据的字节列表
                int count = values.Count;
                List<byte> datas = this.GetDataBytes<T>(values);
                //判断写入的长度,如果是float类型则长度要扩大2倍
                int length = count;//长度等于值的个数 
                if (typeof(T) == typeof(float))
                {
                    length = length * 2;
                }
                //拼装报文
                List<byte> command = new List<byte> {
                    writeCode,
                    0xFF, 0x0A, 0x00,//0xFF指PLC编号,0x0A,0x00指超时时间,超时时间是以250ms为单位 
                    startBytes[0], startBytes[1], startBytes[2], startBytes[3], // 起始地址
                    areaBytes[0], areaBytes[1], // 存储区 
                    //写入的长度的低位和高位 
                    (byte)(length % 256),
                    (byte)(length / 256 % 256),
                };
                command.AddRange(datas);//写入的具体数据
                //发送报文
                socket.Send(command.ToArray());

                // 判断写入的结果 
                byte[] respBytes = new byte[2];
                socket.Receive(respBytes);
                if (respBytes[0] != (writeCode |= 0x80))
                {
                    throw new Exception("响应报文结构异常。" + respBytes[0].ToString());
                }
                if (respBytes[1] != 0x00)
                {
                    throw new Exception("响应异常。" + respBytes[1].ToString());
                }
            }
            catch (Exception ex)
            {
                result.IsSuccessed = false;
                result.Message = ex.Message;
            }

            return result;
        }

        #endregion

        #region PLC启停,区别功能码  0x13,0x14
        public Result Run()
        {
            return PlcState(0x13);
        }
        public Result Stop()
        {
            return PlcState(0x14);
        }
        private Result PlcState(byte commandCode)
        {
            Result result = new Result();
            try
            {
                var connectState = this.Connect();
                if (!connectState.IsSuccessed)
                {
                    throw new Exception(connectState.Message);
                }

                List<byte> commandBytes = new List<byte>
                {
                    commandCode,0xFF,0x0A,0x00
                };
                socket.Send(commandBytes.ToArray());

                // 先判断响应状态
                byte[] respBytes = new byte[2];
                socket.Receive(respBytes);

                if (respBytes[0] != (commandCode |= 0x80))
                {
                    throw new Exception("响应报文结构异常。" + respBytes[0].ToString());
                }
                if (respBytes[1] != 0x00)
                {
                    throw new Exception("响应异常。" + respBytes[1].ToString());
                }
            }
            catch (Exception ex)
            {
                result.IsSuccessed = false;
                result.Message = ex.Message;
            }

            return result;
        }
        #endregion

        #region 内部方法

        /// <summary>
        /// 构建存储区代码
        /// </summary>
        /// <param name="areaCode">存储区代码</param>
        /// <returns></returns>
        private List<byte> AreaToBytes(AreaCode areaCode)
        {
            List<byte> areaBytes = new List<byte>();
            string areaStr = areaCode.ToString();
            areaBytes.AddRange(Encoding.ASCII.GetBytes(areaStr));
            if (areaBytes.Count == 1)
            {
                areaBytes.Add(0x20);
            }
            areaBytes.Reverse(); //字节反转
            return areaBytes;
        }

        /// <summary>
        /// 发送报文
        /// </summary>
        /// <param name="reqBytes">报文字节集合</param>
        /// <param name="len">报文字节长度</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public override List<byte> Send(List<byte> reqBytes, int len)
        {
            socket.Send(reqBytes.ToArray()); //发送报文
            // 先判断响应状态
            byte[] respBytes = new byte[2];
            socket.Receive(respBytes);

            if (respBytes[0] != (reqBytes[0] |= 0x80))
            {
                throw new Exception("响应报文结构异常。" + respBytes[0].ToString());
            }
            if (respBytes[1] != 0x00)
            {
                throw new Exception("响应异常。" + respBytes[1].ToString());
            }
            respBytes = new byte[len];
            socket.Receive(respBytes, 0, len, SocketFlags.None);
            return new List<byte>(respBytes);
        }


        /// <summary>
        /// 地址解析,输入的地址:X100    X1A0    M100    D100   TN10
        /// </summary>
        /// <param name="address">地址字符串</param>
        /// <returns>返回元组</returns>
        public Tuple<AreaCode, string> AnalysisAddress(string address)
        {
            // 取两个字符
            string area = address.Substring(0, 2);
            if (!new string[] { "TN", "TS", "CS", "CN" }.Contains(area))
            {
                area = address.Substring(0, 1);
            }
            string start = address.Substring(area.Length);
            // 返回元组(一个对象,该对象包括编号和地址)
            var obj = new Tuple<AreaCode, string>((AreaCode)Enum.Parse(typeof(AreaCode), area), start);
            return obj;
        }

        #endregion 


    }
}

确保项目编译成功,可以进行下一步

4、测试通讯库

1、添加项目引用    

 2、启动MC服务器 

3、利用通讯库读写数据

 1 读取X区100开始的4个bool数据

 

 2、读取D区100开始的5个float数据

 3、读取M区200开始的2个short数据

4、写入M区200开始的2个short数据 

 

5、写入D区200开始的5个float数据 

 4、完整代码

using Mitsubishi.Communication.MC.Mitsubishi;
using Mitsubishi.Communication.MC.Mitsubishi.Base;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Mitsubishi.Communication.Test
{
    internal class Program
    {
        static void Main(string[] args)
        {
            MCLibTestA1E(); 
            Console.WriteLine("执行完成!");
            Console.ReadKey();
        }

        /// <summary>
        /// 测试A-1E通讯库
        /// </summary>
        static void MCLibTestA1E()
        {
            A1E a1E = new A1E("192.168.1.7", 6000);
            #region 读数据
            Console.WriteLine("读取X区100开始的4个bool数据");
            var result1 = a1E.Read<bool>(AreaCode.X, "100", 4);
            if (result1.IsSuccessed)
            {
                result1.Datas.ForEach(d => Console.WriteLine(d));
            }
            else
            {
                Console.WriteLine(result1.Message);
            }
            Console.WriteLine("读取D区200开始的5个float数据");
            var result2 = a1E.Read<float>(AreaCode.D, "200", 5);
            if (result2.IsSuccessed)
            {
                result2.Datas.ForEach(d => Console.WriteLine(d));
            }
            else
            {
                Console.WriteLine(result2.Message);
            }
            Console.WriteLine("读取M区200开始的2个short数据");
            var result3 = a1E.Read<short>(AreaCode.M, "200", 2);
            if (result3.IsSuccessed)
            {
                result3.Datas.ForEach(d => Console.WriteLine(d));
            }
            else
            {
                Console.WriteLine(result3.Message);
            }

            #endregion

            #region 写数据
            Console.WriteLine("写入M区200开始的2个short数据");
            var result4 = a1E.Write<short>(new List<short> { 61, 72 }, "M200");
            if (result4.IsSuccessed)
            {
                Console.WriteLine(result4.Message);
            }
            Console.WriteLine("写入D区200开始的5个float数据");
            var result5 = a1E.Write<float>(new List<float> { 3.2f, -2.5f, 0, 35, -98 }, "D200");
            if (result5.IsSuccessed)
            {
                Console.WriteLine(result5.Message);
            }

            #endregion

        }
    }
}

5、小结

原创真的不容易,走过路过不要错过,点赞关注收藏又圈粉,共同致富。

原创真的不容易,走过路过不要错过,点赞关注收藏又圈粉,共同致富。

原创真的不容易,走过路过不要错过,点赞关注收藏又圈粉,共同致富

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

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

相关文章

熬夜整理的考研考公学习资料,祝愿大家成功上岸

现如今大学生毕业大都在考研、考公这两条道路上选一个。今天给这些朋友同学分享一下考研、考公的学习资料。希望能够帮助到部分努力的同学&#xff01; 以下就是考研、考公的学习资料 学习资料获取地址 点击获取学习资料 就拿考研来说这里不仅包含了深入浅出的复习笔记&#x…

steam搬砖项目还能不能做,新手小白月入过万真的假的

steam搬砖项目还能不能做&#xff1f;今天&#xff0c;我们将通过实际数据来告诉你&#xff0c;当前市场上存在着大量没有实操过Steam搬砖项目的人&#xff0c;他们也开始参与其中&#xff0c;导致市场格局混乱。这些人可能是第一次接触该项目&#xff0c;但却毫不犹豫地发表自…

Facebook Horizon:探索虚拟现实中的社交空间

随着科技的不断进步&#xff0c;虚拟现实&#xff08;VR&#xff09;技术正成为社交互动和娱乐体验的新前沿。在这个数字时代&#xff0c;Facebook作为全球最大的社交媒体平台之一&#xff0c;正在引领虚拟社交的新时代&#xff0c;其推出的虚拟社交平台Facebook Horizon成为了…

如何使用Docker部署MongoDB并结合内网穿透实现远程访问本地数据库

文章目录 前言1. 安装Docker2. 使用Docker拉取MongoDB镜像3. 创建并启动MongoDB容器4. 本地连接测试5. 公网远程访问本地MongoDB容器5.1 内网穿透工具安装5.2 创建远程连接公网地址5.3 使用固定TCP地址远程访问 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的 …

Fiddler工具 — 19.Fiddler抓包HTTPS请求(二)

5、查看证书是否安装成功 方式一&#xff1a; 点击Tools菜单 —> Options... —> HTTPS —> Actions 选择第三项&#xff1a;Open Windows Certificate Manager打开Windows证书管理器。 打开Windows证书管理器&#xff0c;选择操作—>查看证书&#xff0c;在搜索…

【webrtc】m77 PacedSender

mediasoup是m77的代码,m77的代码并没有paced controller ,而且与paced sender 的逻辑混在了一起。结合大神们的代码分析,对照m77 进行 理解。m77 有ProbeController。给pacersender 更新飞行数据:PacedSender::InsertPacket(size_t bytes) 对应的是 PacingController::OnPa…

微信小程序错误----config is not defined

微信小程序出错 请求头发生错误 修改 options.header {// 为请求头对象添加 token 验证的 Authorization 字段Access-Token: token,platform: MP-WEIXIN,// 保留原有的 header...options.header,}

运维SRE-11 备份服务及备份项目

1.第一个服务-rsync备份服务-守护进程模式 1.1概述 守护进程&#xff1a;持续运行的进程&#xff0c;也可以叫作服务服务一般分为&#xff1a;服务端与客户端服务端&#xff1a;linux服务器上运行的各种服务软件客户端&#xff1a;linux中的客户端可能是一个命令&#xff0c;…

Android 11以上获取不到第三方app是否安装

开年第一篇&#xff0c;处理了一下年前的小问题。 问题&#xff1a;本地app跳转到第三方app地图进行导航&#xff0c;获取不到第三方地图是否安装。 解决&#xff1a; 1.添加包名 This can be done by adding a <queries> element in the Android manifest.在app下的…

性能全面提升!探索ONLYOFFICE最新8.0版:更快速、更强大,PDF表单编辑轻松搞定!

文章目录 PDF表单功能表单模板 屏幕朗读器功能EXCEL新增功能单变量求解图表向导数字排序 PPT 新增功能新增语言区域设置和优化插件界面 ONLYOFFICE 是由 Ascensio System SIA 推出的一款功能强大的办公套件&#xff0c;其中提供了适用于文本文档、表格以及演示文稿的在线编辑软…

Redis部署方式(一)四种部署方式介绍

redis的四种部署方式&#xff1a; Redis单机模式部署、Redis主从模式部署、Redis哨兵模式部署、Cluster集群模式部署&#xff0c;后面三种&#xff08;主从模式&#xff0c;Sentinel哨兵模式&#xff0c;Cluster模式&#xff09;也可以统称为集群模式。 一、单机 1、缺点&…

简单实现节流函数踩的小坑

平时debounce&#xff08;防抖&#xff09;用得多&#xff0c;throttle用得少&#xff0c;记下写 throttle 时遇到的低级错误。 节流&#xff0c;一定时间内多次操作&#xff0c;在最早操作的若干延迟后执行。 场景参考&#xff1a;周期上报&#xff0c;有的数据不急着报&#…

OpenWRT部署web站点并结合内网穿透实现无公网ip远程访问

文章目录 前言1. 检查uhttpd安装2. 部署web站点3. 安装cpolar内网穿透4. 配置远程访问地址5. 配置固定远程地址 前言 uhttpd 是 OpenWrt/LuCI 开发者从零开始编写的 Web 服务器&#xff0c;目的是成为优秀稳定的、适合嵌入式设备的轻量级任务的 HTTP 服务器&#xff0c;并且和…

医疗行业的数字化转型:开发智慧医疗源码与互联网医院APP教学

今天&#xff0c;笔者将与大家共同了解医疗行业的数字化转型过程&#xff0c;重点关注开发智能医疗源码以及互联网医院APP的实践指南&#xff0c;希望能够为医疗机构的数字化建设提供有益的参考和指导。 一、数字化转型背景与意义 数字化转型则可以通过智能化技术&#xff0c;…

基于Android的大学生足球赛事管理系统的设计与实现

足球是世界范围内广受欢迎的一种体育运动&#xff0c;国内有中超、中甲及大学生联赛等各级别的赛事&#xff0c;中超和中甲基本上都有专业的球队在运营&#xff0c;而大学生联赛属于校园级别的赛事&#xff0c;其重视程度较为有限&#xff0c;使得其信息化水平不高&#xff0c;…

昇腾ACL应用开发之模型转换ATC

一.前提条件 在前面的章节中我们已经安装了包含模型转换的套件包CANN-TOOLKIT&#xff0c;默认的安装路径会在/usr/local/Ascend里面&#xff0c;我们将该套件所需要的东西加入到环境变量中以便我们调用&#xff1a; 将source /usr/local/Ascend/ascend-toolkit/set_env.sh加入…

yml配置文件中常见的配置及含义

1.数据库连接的相关配置 项目名称:datasource:driver-class-name: com.mysql.cj.jdbc.Driverhost: localhostport: 3306database: 数据库名username: 用户名password: 密码 springboot配置文件,用于配置数据库源连接信息 数据库驱动类型为com.mysql.cj.jdbc.Driver,这是数据…

35岁测试经理自述:为何我做测试十年,内心仍无比恐慌?

记得在求职的时候&#xff0c;面试官经常问我&#xff1a;“为什么要选择软件测试工作?” 而我也会经常说一堆自己有的没的优势去应付。 工作这么久了&#xff0c;也不再浮躁&#xff0c;静下心来回忆当初选择软件测试工作的历程&#xff0c;也是对自己职业生涯的一次回顾。 …

vulnhub靶场之Deathnote

一.环境搭建 1.靶场描述 Level - easy Description : dont waste too much time thinking outside the box . It is a Straight forward box . This works better with VirtualBox rather than VMware 2.靶场下载 https://www.vulnhub.com/entry/deathnote-1,739/ 3.启动环…

图——最小生成树实现(Kruskal算法,prime算法)

目录 预备知识&#xff1a; 最小生成树概念&#xff1a; Kruskal算法&#xff1a; 代码实现如下&#xff1a; 测试&#xff1a; Prime算法 &#xff1a; 代码实现如下&#xff1a; 测试&#xff1a; 结语&#xff1a; 预备知识&#xff1a; 连通图&#xff1a;在无向图…