数字孪生项目实战,WPF与Unity结合开发之路(一)

 数字孪生项目实战,WPF与Unity结合开发之路(一)

数字孪生项目实战,WPF与Unity结合开发之路(一)

作   者:水娃

  • 嗨大家好,我是一名骨灰级的WPF开发者,我叫水娃。

  • 这次主要是向大家讲解一个WPFUnity相结合来实现WPF3D的交互项目。此前一直做WPF开发,但是有时候要实现一些3D过程的时候,用WPF做就很麻烦。经过不断探索,作者总结了一套合理的WPFUnity通讯和嵌入方式,如果能用不同的技术相组合,用各自的技术做他擅长的方向,那既能达到产品需求又可以避免技术开发难点,是不是要比单用一门技术来实现要好很多呢?

  • 项目的起因是要做一个数字孪生项目,按照白皮书的解释,数字孪生分为几个阶段:

    • 1.虚实映射

    • 2.实时同步

    • 3.共生演进

    • 4.闭环优先。

  • 这里由于版权原因,我们只开源到第二部分实时同步阶段,属于集成前的测试程序,但是整体的集成方式和通讯过程已经全部实现了。项目主要实现对一个风机电厂中各种风机的监测和控制,由于风机的采集协议是用的Modbus,所以采集这部分选择WPF开发,SQLite存储。但是要用3D来实现风机的表现,比如风速、转向、掉线离线、不同风速对风机的影响,这部分如果用WPF来表现,那就很麻烦了,所以最终决定采用Unity来开发这部分。最后WPF里嵌入Unity来最终项目呈现。演示效果如下。

  • 我们分为三部分来开发:

    • 1.WPF部分

    • 2.Unity部分

    • 3.集成部分

(一)、WPF 测试界面如下:

dbeac93e50befe48103049fcf7a21f3a.png
  • 先简单介绍一下Modbus协议,Modbus是一个现场总线协议,应用在电子控制器上,可以实现控制器相互之间、PC到控制器的通讯。支持多种电气接口(RS232、RS422、RS485、RJ45)和多种传输介质(双绞线、网线)。主要有串口和网口方式,串口(电脑后面的串口孔,PC只有232串口的,所以需要买串口转换器,才能用485协议)主要是用RS485协议一主多从模式,传输格式有ModbusAsciiModbusRTU;

  • 网口(电脑后面插网线的口)主要是ModbusTCPModbusUDP,传输格式和串口ModbusRTU的相同。Modbus中数据存储类型为bit(bool),byte(8位),word(16位),dword(32位). 这几个类型的主要区别是存储的长度不同,类似C#里的intdouble。所谓的上位机一般都是用别人写好的库,连好硬件,然后根据地址表,从硬件中读出来对应的数据,然后再解析出来。 因为C#的最小单位是byte,所以我们读取完之后,一切都是byte[], 一定要写好解析过程,不然就会出错。

  • Modbus这部分解决了,数据有了,下一步就是要从WPF发送给Unity,作者选择了Socket协议,别的一些网络协议也可以,但是Socket比较成熟,作者用的比较熟悉。最终封装的类库代码如下

public class socketServer{public class StateObject{// Client socket.public Socket workSocket = null;// Size of receive buffer.public const int BufferSize = 1024;// Receive buffer.public byte[] buffer = new byte[BufferSize];}public class ConnectionServer{public static ConnectionServer Instance => _instance;private static readonly ConnectionServer _instance = new ConnectionServer();/// <summary>/// 监听线程/// </summary>public Socket listenSocket;/// <summary>/// tcp客户端对象/// </summary>public Socket clientSocket = null;/// <summary>/// 异步发送数据/// </summary>/// <returns></returns>public int Send(byte[] byteMessage, int size){int offset = 0;try{return SendBytes(byteMessage, size, ref offset);}catch (Exception ex){Console.WriteLine($"发送出现异常{ex.Message.ToString()}");return 0;}}private int SendBytes(byte[] byteMessage, int size, ref int offset){if (clientSocket != null){while (offset < size){int n = clientSocket.Send(byteMessage, offset, size - offset, SocketFlags.None);if (n > 0){offset += n;}else{Console.WriteLine("发送数据失败");break;}}return offset;}return 0;}public void Start(string ipServer, int portServer){IPEndPoint ipEnd = new IPEndPoint(IPAddress.Parse(ipServer), portServer);//创建监听listenSocket = new Socket(ipEnd.AddressFamily, SocketType.Stream, ProtocolType.Tcp);//监听该IPlistenSocket.Bind(ipEnd);//设置监听队列中最多可容纳的等待接受的传入连接数listenSocket.Listen(100);Console.WriteLine($"开始监听:{ipServer}:{portServer}");//开始接受客户端连接while (true){clientSocket = listenSocket.Accept();var ip = ((IPEndPoint)clientSocket.RemoteEndPoint).Address;var port = ((IPEndPoint)clientSocket.RemoteEndPoint).Port;var appkey = $"{ip}^{port}";if (clientSocket.Connected){Console.WriteLine($"{appkey}连接到了服务端");try{// 开始异步接受数据SetupReceiveCallback();}catch (Exception ex){Console.WriteLine("Socket异步方式接收数据发生异常:{0}", ex.StackTrace);}}else{Console.WriteLine("连接建立失败");}}}/// <summary>/// 开始用Socket异步方式接收数据。/// </summary>private void SetupReceiveCallback(){if (clientSocket != null){try{StateObject state = new StateObject();state.workSocket = clientSocket;clientSocket.BeginReceive(state.buffer, 0, StateObject.BufferSize, SocketFlags.None,new AsyncCallback(OnReceive), state);}catch (Exception ex){Console.WriteLine("Socket异步方式接收数据发生异常:{0}", ex.StackTrace);}}else{Console.WriteLine("异步接收回报消息socket为null");}}/// <summary>/// 异步接收回调/// </summary>/// <param name="ar"></param>private void OnReceive(IAsyncResult ar){try{if (clientSocket != null){StateObject state = (StateObject)ar.AsyncState;Socket client = state.workSocket;// Read data from the remote device.int bytesRead = client.EndReceive(ar);if (bytesRead > 0){byte[] result = new byte[bytesRead];Buffer.BlockCopy(state.buffer, 0, result, 0, bytesRead);var msg = Encoding.UTF8.GetString(result);Console.WriteLine("收到消息:" + msg);MsgCenter.Receive(msg);// Send(result, result.Length);SetupReceiveCallback();}else{Console.WriteLine("异步接受数据bytesRead为0");}}}catch (Exception ex){Console.WriteLine($"异步接受数据异常{ex.Message}");}}}}
  • 这个类没有处理粘包情况,都是直接发直接收和解析,如果不是高频通讯,比如毫秒级的通讯,其实粘包情况很少发生。

  • 一般处理粘包都是一个消息分为消息头+消息体+消息尾巴,或者简单一点直接消息头+消息体的形式。消息头里面一般会有序号和消息体的长度,方便接收端进行处理。由于项目通讯频率不高,每台风机是1s通讯一次,也就是1s WPF会把一个风机数据发送给Unity,一个程序中最多有10台风机,所以1s最多发送10次。

  • 因为都是本机通讯,经过大量测试,没有出现粘包的情况,所以测试通讯类只封装了发送和接收,实际使用起码要封装断线重连,心跳检测才能真正使用。

(二)、  然后定义通讯格式,代码如下:

public  class MessageModel{/// <summary>/// id/// </summary>public string msid;/// <summary>///风机名字/// </summary>public string epname;/// <summary>///消息类型/// </summary>public string msg_type;/// <summary>/// 状态字服务器/// </summary>public string severztz;/// <summary>/// 控制字服务器/// </summary>public string severkzz;/// <summary>/// 偏航修正量,绝对值/// </summary>public string severphxzl;/// <summary>/// 风速修正量,相对值/// </summary>public string severfxxzl;/// <summary>/// 计数器/// </summary>public string severjzq;/// <summary>///  reserved/// </summary>public string severreserved;/// <summary>///  状态字/// </summary>public string ztz;/// <summary>///  计数器/// </summary>public string jsq;/// <summary>/// 风速/// </summary>public string fs;/// <summary>/// 风向/// </summary>public string fx;/// <summary>/// 机舱方位角/// </summary>public string jcfwj;/// <summary>/// 雷达风速/// </summary>public string ldfs;/// <summary>/// 雷达风向/// </summary>public string ldfx;/// <summary>/// 雷达状态字1/// </summary>public string ldztzone;/// <summary>/// 雷达状态字2/// </summary>public string ldztztwo;/// <summary>///雷达判断数据是否有效/// </summary>public string ldsfyx;/// <summary>///雷达扫描层/// </summary>public string dyldsmc;/// <summary>///轴向风速/// </summary>public string zxfs;/// <summary>///水平风速/// </summary>public string spfs;/// <summary>///垂直风速/// </summary>public string czfs;/// <summary>///光束1/// </summary>public string vlos1;/// <summary>///光束2/// </summary>public string vlos2;/// <summary>///光束3/// </summary>public string vlos3;/// <summary>///光束4/// </summary>public string vlos4;/// <summary>///光束5/// </summary>public string vlos5;/// <summary>///光束6/// </summary>public string vlos6;/// <summary>///光束7/// </summary>public string vlos7;/// <summary>///光束8/// </summary>public string vlos8;/// <summary>///光束9/// </summary>public string vlos9;/// <summary>///光束10/// </summary>public string vlos10;/// <summary>///光束11/// </summary>public string vlos11;/// <summary>///光束12/// </summary>public string vlos12;/// <summary>///光束13/// </summary>public string vlos13;/// <summary>///光束14/// </summary>public string vlos14;/// <summary>///光束15/// </summary>public string vlos15;/// <summary>///光束16/// </summary>public string vlos16;/// <summary>///光束测量有效性/// </summary>public string gsclyxx;/// <summary>///reserved1/// </summary>public string reserved1;/// <summary>///reserved2/// </summary>public string reserved2;/// <summary>///reserved3/// </summary>public string reserved3;}
  • 其中的关键字段是epnamemsg_type,一个是风机名字,用来区分不同风机,一个是msg_type用来区分不同消息。其余字段都是用来控制风机的状态。

(三)、  Unity部分:

71086bfc91e590241c609c8a22572e26.png
  • 首先,我们需要建立地形,这个使用Unity内置的Terrain,就和WPF内置的控件一样,拖进去进去微调,然后拖进去风机模型,进行位置调整。调整完效果如下:

1973fb50d20c44dca370ba4510c48af3.png
  • 然后我们开始制作图表,这个图表的制作方式其实和WPF写界面是大同小异的。我们要实现的大屏界面如下:

2c2d4b9f99220058445b7a640c79b0ee.png
  • 首先实现最上面的部分,先把Unity设置为2D模式,然后增加一个Image控件和一个Text控件,Image控件选择背景图片,Text输入文字。标题栏就形成了,如下

356c4e47bdf1775e182144a178b66485.png
  • 下面的风机总数那几个圆形图表的开发方式也类似,如下

cca50b285598b18c57f8299927cfdf0b.png
  • 下面的几个图表也是类似的开发方式,是不是发现很简单?

  • 甚至比WPF的界面开发也要简单,有时候高手和我们的差距,就是他们懂很多我们不懂的基础知识,因为不懂,被高手一顿组合拳下来,老戳中我们的盲点,就觉得高手比较厉害,其实坚持学一学,我们也可以成为高手,虽然我现在也是个菜鸟。

  • 界面开发方式结束了,再来看看后台代码,同样的我们也需要一个Socket接收类,如下:

public class ConnectionClient{public static ConnectionClient Instance => _instance;private static readonly ConnectionClient _instance = new ConnectionClient();private string ip { get; set; }private int port { get; set; }/// <summary>/// 当前状态/// </summary>public ConnState CurrState { get; set; }/// <summary>/// tcp客户端对象/// </summary>private Socket socket = null;/// <summary>/// 上一个队列数据中剩余字节长度/// </summary>private byte[] lastBytes;public bool InitConnection(){//创建SOCKETsocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);CurrState = ConnState.Connecting;socket.NoDelay = true;socket.ReceiveTimeout = 10000;socket.SendTimeout = 5000;return true;}public bool ConnectServer(string ipServer, int portServer){if (socket.Connected){CurrState = ConnState.Connected;Console.WriteLine("已经有了连接");return true;}try{Console.WriteLine("开始建立连接");this.ip = ipServer;this.port = portServer;IPEndPoint ipEnd = new IPEndPoint(IPAddress.Parse(ip), port);socket.Connect(ipEnd);if (socket.Connected){//接受数据SetupReceiveCallback();CurrState = ConnState.Connected;}else{CurrState = ConnState.Disconnected;Console.WriteLine("连接不上");}}catch (Exception ex){CurrState = ConnState.Disconnected;Console.WriteLine("连接socket异常" + ex.Message.ToString());return false;}if (CurrState == ConnState.Connected){return true;}return false;}public void Reconnection(){while (true){if (CurrState == ConnState.Disconnected){InitConnection();ConnectServer(ip, port);}else { }Thread.Sleep(3000);}}/// <summary>/// 开始用Socket异步方式接收数据。/// </summary>protected void SetupReceiveCallback(){if (socket != null){try{StateObject state = new StateObject();state.workSocket = socket;socket.BeginReceive(state.buffer, 0, StateObject.BufferSize, SocketFlags.None,new AsyncCallback(OnReceive), state);}catch (Exception ex){Console.WriteLine("Socket异步方式接收数据发生异常:{0}", ex.Message.ToString());}}else{Console.WriteLine("异步接收回报消息socket为null");}}/// <summary>/// 异步接收回调/// </summary>/// <param name="ar"></param>private void OnReceive(IAsyncResult ar){CurrState = ConnState.Connected;try{StateObject state = (StateObject)ar.AsyncState;Socket client = state.workSocket;int bytesRead = socket.EndReceive(ar);if (bytesRead > 0){byte[] result = new byte[bytesRead];Buffer.BlockCopy(state.buffer, 0, result, 0, bytesRead);var msg = Encoding.UTF8.GetString(result);var model = Newtonsoft.Json.JsonConvert.DeserializeObject<MessageModel>(msg);// Debug.Log(msg);ReceiveAction(model);SetupReceiveCallback();}else{CurrState = ConnState.Disconnected;}}catch (Exception ex){Console.WriteLine($"发生异常{ex.Message.ToString()}");}}/// <summary>/// 异步发送数据/// </summary>/// <returns></returns>public int Send(byte[] byteMessage, int size){int offset = 0;try{return SendBytes(byteMessage, size, ref offset);}catch (Exception ex){Console.WriteLine($"发送出现异常{ex.Message.ToString()}");return 0;}}private int SendBytes(byte[] byteMessage, int size, ref int offset){if (socket != null){while (offset < size){int n = socket.Send(byteMessage, offset, size - offset, SocketFlags.None);if (n > 0){offset += n;}else{Console.WriteLine("发送数据失败");break;}}return offset;}return 0;}}void Start(){ConnectionClient.Instance.InitConnection();ConnectionClient.Instance.ConnectServer(GlobalInit.basicInfoDict["ServerIP"], int.Parse(GlobalInit.basicInfoDict["Port"]));ReceiveAction = Receive;
}void Receive(MessageModel msg){lock (lockObject){message.Add(msg);}}
  • WPF不同之处在于,Unity每个脚本都有一个StartUpdate函数,所以更新界面的操作都要在这2个函数内执行。

  • Start是初始化时候执行的,Update是更新每一帧画面时候执行的(Unity的渲染原理是根据计算机不同,1s内固定更新多少帧图像,然后图像连起来就形成了实时画面)。

  • 所以最终我们改变界面的代码要写到Update内,他无法像WPF一样可以自由切换UI线程。因此我们Socket收到的数据全部扔到了List<MessageModel> message里面,然后在Update里面判断Message的信息,来对界面进行改变。如下:

void Update(){lock (lockObject){if (message.Count != 0){for (int i = 0; i < message.Count; i++){var model = message[i];//Debug.Log(model.epname);switch (model.msg_type){//sqlite 10,传0 sql server 11,传1case "10":GameManager.Instance.InitSql(0);message.Remove(model);break;case "11":GameManager.Instance.InitSql(1);message.Remove(model);break;//100为实时数据 case "100":GameManager.Instance.WeiLiu(model);RightPanel.Instance.SetInfo(model);RightPanel.Instance.SetFengJiState(model);message.Remove(model);break;//case "200"://    GameManager.Instance.SetModelEffect(model.vlos1, model.vlos2);//    break;}}message.Clear();}}}
  • 最终,Unity的开发过程总结一下就是:

    • 1.导入风机模型,记录一个初始位置,然后隐藏风机,点击新建时候克隆这个风机,输入属性后存储到sqlite数据库里面。

    • 2.写好socket接受类,收到wpf传来的消息,在update函数里面进行逻辑判断,从而更改界面显示。

(四)、集成部分

  • wpfunity开发完成后,来到了最终的集成环节。

  • 这里我们不采用网上那种方式,作者自己经过几天研究,总结了一个比较好的集成方式。

  • 就是把unity固定的放到wpf界面的一个区域内,在移动和放大缩小wpf界面的时候,不断的对unity程序进行移动和放大缩小,这样整体保持了一致。

  • 主要是用几个windows函数来操作:

[DllImport("user32.dll", CharSet = CharSet.Auto)]static extern int MoveWindow(IntPtr hWnd, int x, int y, int nWidth, int nHeight, bool BRePaint);[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]public static extern int ShowWindow(IntPtr hwnd, int nCmdShow);[DllImport("gdi32.dll")]private static extern int GetDeviceCaps(IntPtr hDc, int nIndex);
  • 这里要注意,wpf界面要选择window,不能选择page,因为page页面没有句柄,无法把Unity程序设置为wpf界面的子元素。代码如下:

public MainWindow(){//静态指定Current = this;//窗口关闭方式,主窗口一旦关闭,就关闭Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;//初始化窗口大小,到屏幕的80%this.Height = SystemParameters.PrimaryScreenHeight * 0.8d;this.Width = SystemParameters.PrimaryScreenWidth * 0.8d;//读取配置文件option = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("AppSetting.json").Build().GetSection("config").Get<Option>();option.UnityDir = Environment.CurrentDirectory + "\\unity\\demo.exe";InitializeComponent();}private void Window_Loaded(object sender, RoutedEventArgs e){//开始socket监听Task.Run(() => ConnectionServer.Instance.Start(option.Ip, option.Port));//开启渲染窗口,并设置父级RenderWindow.Current.OpenRenderWindow();//实际应该延时后建立socket后立马发送选择数据库的信号// Thread.Sleep(2000);//Button_Click_3(new object(), new RoutedEventArgs());}//初始化,调整渲染窗口public void Init(){Window_SizeChanged(null, null);}public void OpenRenderWindow(){//渲染程序路径string RenderExePath = MainWindow.option.UnityDir;//如果成功找到了渲染程序if (!string.IsNullOrEmpty(RenderExePath) &&System.IO.File.Exists(RenderExePath)){UnityEngine = Process.Start(RenderExePath);Thread.Sleep(3000);SetRenderWindow();}//没找到渲染程序,就关闭else{MessageBox.Show("未找到渲染程序");//System.Windows.Application.Current.Shutdown();}}
  • 好了,这次的分享到这里结束,之后我会把开发过程详细的写出来,帮助大家手把手 的从01搭建这个项目,最终这个项目也会集成到web里面。如果有不懂的可以随时加作者沟通,互相提高。二维码在下方。

e163415fae68c4749749c171db7effa6.gifd80fe6109a299ecc3129a1e4733dbe09.png5bd4efed5bf68cf42733fb928cf96c13.jpeg

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

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

相关文章

idou老师教你学Istio06: 如何用istio实现流量迁移

流量迁移是流量管理的一个重要功能。istio提供的流量管理功能将流量从基础设施扩展中解耦&#xff0c;支持动态请求路由&#xff0c;故障注入、超时重试、熔断和流量迁移等。流量迁移的主要目的是将流量从微服务的某一版本的逐步迁移至另一个版本&#xff0c;如在新旧版本之间进…

用最少的代码,写一个完整MES项目(.NET6+WPF)

工业4.0时代&#xff0c;智能智造MES系统大行其道&#xff0c;然而基于.NET跨平台的罕见&#xff01;这里有一套《.NET6WPF企业级MES实战》教程&#xff0c;基于.NET6跨平台开发&#xff0c;实现了MES多核心功能&#xff0c;尤其是开发框架完整&#xff0c;非常适合复用。这里分…

django18:auth模块

Auth模块 执行数据库迁移命令后&#xff0c;自动生产多个表。 django_session auth_user 直接访问admin路由&#xff0c;需要输入用户名和密码&#xff0c;就是参考auth_user表 管理员用户才能进入 创建超级用户 createsuperuser from django.contrib import auth1.校验用…

hulu dpp_什么是直播电视的Hulu,它可以代替您的有线电视订阅吗?

hulu dppStreaming cable replacements are becoming a much more appealing option for cable cutters across the board, with more choices available than ever before. Hulu’s Live TV option is a relative newcomer to the scene, but is it worth it? 对于全系列的电…

suse linux ssh远程无法访问问题

当正常安装完Suse Linux Enterprise Server 11 sp1 时&#xff0c;无法通过SecureCRT或者PuTTY之类的终端程序进行连接。 折腾了一下&#xff0c;发现问题所在&#xff1a; 1、 需要关闭防火墙&#xff0c;如下图在YAST里可以关闭&#xff0c;也可以使用下面命令行的方式&…

Jade —— 源于 Node.js 的 HTML 模板引擎

2013-12-11 发布Jade —— 源于 Node.js 的 HTML 模板引擎 开源项目介绍 web 模板引擎 node.js jade 207.8k 次阅读 读完需要 69 分钟54Jade 是一个高性能的模板引擎&#xff0c;它深受 Haml 影响&#xff0c;它是用 JavaScript 实现的&#xff0c;并且可以供 Node…

诈骗者如何伪造电子邮件地址,以及如何分辨

Consider this a public service announcement: Scammers can forge email addresses. Your email program may say a message is from a certain email address, but it may be from another address entirely. 考虑这是一项公共服务公告&#xff1a;诈骗者可以伪造电子邮件地…

如何对整个 WPF 应用程序进行灰度

如何对整个 WPF 应用程序进行灰度控件名&#xff1a;GrayscaleEffect作 者&#xff1a;WPFDevelopersOrg - 驚鏵原文链接[1]&#xff1a;https://github.com/WPFDevelopersOrg/WPFDevelopers 简易源码[2]框架使用.NET40&#xff1b;Visual Studio 2019;如果要实现灰度第一反是…

django19:项目开发流程

参考&#xff1a;https://www.bilibili.com/video/BV1QE41147hU?p831&spm_id_frompageDriver

React Native - FlexBox弹性盒模型

FlexBox布局 1. 什么是FlexBox布局? 弹性盒模型(The Flexible Box Module),又叫FlexBox,意为"弹性布局",旨在通过弹性的方式来对齐和分布容器中内容的空间,使其能适应不同屏幕,为盒模型提供最大的灵活性.   Flex布局主要思想是: 让容器有能力让其子项目能够改变其…

java虚拟机读写其他进程的数据

在java中&#xff0c;process类提供了如下3个方法&#xff0c;用于让程序和其他子进程进行通信。 InputStream getErrorStream&#xff08;&#xff09;&#xff1a;获取子进程的错误流。 InputStream getInputStream&#xff08;&#xff09;&#xff1a;获取子进程的输入流。…

release8_如何在Windows 8 Release Preview中将Chrome用作Metro浏览器

release8Windows 8 allows third-party browser to replace Internet Explorer in the Metro environment — except on Windows RT. You can use Google Chrome in Metro today, and Firefox for Metro is on the way. Windows 8允许第三方浏览器在Metro环境中替换Internet Ex…

html jQuery/bootstrap通过网络bootcdn导入连接

网络连接网址 https://www.bootcdn.cn/ <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"utf-8"><title>title</title><!-- Bootstrap --><link href"https://cdn.bootcdn.net/ajax/libs/twi…

Python深入类和对象

一. 鸭子类型和多态 1.什么是鸭子类型&#xff1a; 在程序设计中&#xff0c;鸭子类型&#xff08;英语&#xff1a;Duck typing&#xff09;是动态类型和某些静态语言的一种对象推断风格。"鸭子类型"像多态一样工作&#xff0c;但是没有继承。“鸭子类型”的语言是这…

linux中/usr下文件权限修改setuid导致的问题

2019独角兽企业重金招聘Python工程师标准>>> 在Ubuntu系统中因为一些原因我使用如下命令修改了/usr目录的拥有者权限&#xff1a; chown -R root:root /usr结果直接导致系统无法正常启动&#xff0c;通过跟踪系统启动日志/var/log/syslog找到如下失败原因&#xff1…

[转载]unix环境高级编程备忘:理解保存的设置用户ID,设置用户ID位,有效用户ID,实际用户ID...

转载自http://www.cnblogs.com/stemon/p/5287631.html 一、基本概念 实际用户ID(RUID)&#xff1a;用于标识一个系统中用户是谁&#xff0c;一般是在登录之后&#xff0c;就被唯一的确定&#xff0c;就是登录的用户的uid。 有效用户ID(EUID)&#xff1a;用于系统决定用户对系统…

django20:BBS网页设计/注册功能/验证码代码

表设计 注册功能 """ 1.注册功能需要forms组件 不同功能&#xff0c;可单独一个py文件2.利用forms组件渲染前端标签1.利用ajax提交2.forms组件获取用户数据的数据。$(#form).serializeArray()获取forms标签所有用户普通键值对的数据3. 手动渲染头像label里面内…

用最少的代码打造一个Mini版的gRPC框架

在《用最少的代码模拟gRPC四种消息交换模式》中&#xff0c;我使用很简单的代码模拟了gRPC四种消息交换模式&#xff08;Unary、Client Streaming、Server Streaming和Duplex Streaming&#xff09;&#xff0c;现在我们更近一步&#xff0c;试着使用极简的方式打造一个gRPC框架…

Windows 10的下一个更新将在您观看视频时隐藏通知

Windows 10’s Focus Assist feature temporarily hides incoming notifications. In Windows 10’s next update, Focus Assist can activate when you’re using any full-screen app, whether that’s YouTube in a browser, Netflix, or a desktop video player like VLC. …

Ubuntu安装Samba文件共享服务器(NAS)

终于有点时间来解决下家中NAS需求了。一般自制NAS&#xff0c;只有选Samba。速度比FTP快&#xff0c;便利性比Windows文件夹共享好&#xff0c;设置多等等。 ▶参考&#xff1a;samba简介 安装Samba $ sudo apt-get update $ sudo apt-get install samba samba-common-bin 核心…