Blue_Moon

Blue_Moon 查看完整档案

广州编辑山东大学  |  伴读书童 编辑广州某公司  |  Programmer 编辑 wangdongjie.com 编辑
编辑

Coder
不相信中医
不相信日人民报

'''
期待一日GFW会像Berlin Wall一样倒塌

个人动态

Blue_Moon 赞了回答 · 10月16日

解决flex布局嵌套的问题

哪一层用就要加哪一层,flex布局子级不会继承父级

关注 3 回答 2

Blue_Moon 赞了文章 · 9月17日

javascript自执行函数

定义

立即执行函数模式是一种语法,可以让你的函数在定义后立即被执行,
这种模式本质上就是函数表达式(命名的或者匿名的),在创建后立即执行;
立即执行函数(immediate function)术语不是在ECMAScript标准中定义的,但它很短有助于描述和讨论模式;
这种模式有一些几部分组成:

  • 使用函数表达式定义一个函数(函数声明不能起作用)

  • 在结尾加上一对括号,让函数立即被执行

  • 将整个函数包裹在一对括号中(只有在你不将函数赋值给一个变量的时候才需要)

功能

  • 它可以帮你封装大量的工作而不会在背后遗留任何全局变量。

  • 你定义的所有变量都会成员立即执行函数的局部变量,所以你不用担心这些临时变量会污染全局空间

  • 可以使用这种技术可以模仿一个私有作用域,用匿名函数作为一个“容器”,“容器”内部可以访问外部的变量,而外部环境不能访问“容器”内部的变量

  • 可以添加更多的加强模块,移除它们,单独测试它们,允许用户去禁用它们等等
    为什么要有js立即执行函数,存在的意义是什么

注意点

立即执行函数通常作为一个单独模块使用。一般没有问题,但是,建议在自己写的立即执行函数前加分号,这样可以有效地与前面代码进行隔离。否则,可能出现意想不到的错误。
js自执行函数前加个分号是什么意思?

写法

  1. 最前最后加括号

    (function(){alert(1);}());

    JSLINT推荐这种写法

  2. function外面加括号

     (function(){alert(1);})();

    目前很多比较好的js library 使用的都是第二种方式。
    比如: web 图形绘制的: git , draw2d ,....

  3. function前面加运算符,常见的是!与void

    !function(){alert(1);}(); 
    void function(){alert(2);}(); 

    在function前面加!、+、 -甚至是逗号等到都可以起到函数定义后立即执行的效果,而()、!、+、-、=等运算符,都将函数声明转换成函数表达式,消除了javascript引擎识别函数表达式和函数声明的歧义
    js中立即执行函数

查看原文

赞 6 收藏 11 评论 0

Blue_Moon 关注了标签 · 8月20日

unity3d

Unity是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

关注 211

Blue_Moon 赞了文章 · 8月20日

从游戏脚本语言说起,剖析Mono所搭建的脚本基础

0x00 前言

在日常的工作中,我偶尔能遇到这样的问题:“为何游戏脚本在现在的游戏开发中变得不可或缺?”。那么这周我就写篇文章从游戏脚本聊起,分析一下游戏脚本因何出现,而mono又能提供怎样的脚本基础。最后会通过模拟Unity3D游戏引擎中的脚本功能,将Mono运行时嵌入到一个非托管(C/C++)程序中,实现脚本语言和“引擎”之间的分离。

0x01 Why?从为何需要游戏脚本开始

首先聊聊为何现在的游戏开发需要使用游戏脚本这个话题。

为何需要有脚本系统呢?脚本系统又是因何而出现的呢?其实游戏脚本并非一个新的名词或者技术,早在暴雪的《魔兽世界》开始火爆的年代,人们便熟知了一个叫做Lua的脚本语言。而当时其实有很多网游都不约而同的使用了Lua作为脚本语言,比如网易的大话西游系列。
但是在单机游戏流行的年代,我们却很少听说有什么单机游戏使用了脚本技术。这又是为什么呢?因为当时的硬件水平不高,所以需要使用C/C++这样的语言来尽量压榨硬件的性能,同时,单机游戏的更新换代并不如网游那么迅速,所以开发时间、版本迭代速度并非其考虑的第一要素,因而可以使用C/C++这样开发效率不高的语言来开发游戏。

但是随着时间的推移,硬件水平逐年水涨船高,压榨硬件性能的需求已经不再迫切。相反,此时网游的兴起却对开发速度、版本更迭提出了更高的要求。所以开发效率并不高效,且投资巨大风险很高的C/C++便不再适应市场的需求了。而更加现实的问题是,随着java、.net甚至是javascript等语言的流行,程序员可以选择的语言越来越多,这更加导致了优秀的C/C++程序员所占比例越来越小。而网游市场的不断扩大,这种对人才的需求也同样越来越大,这就造成了大量的人才空缺,也就反过来提高了使用C/C++开发游戏的成本。而由于C/C++是门入门容易进阶难的语言,其高级特性和高度灵活性带来的高风险也是每个项目使用C/C++进行开发时,所不得不考虑的问题。

而一个可以解决这种困境的举措便是在游戏中使用脚本。可以说游戏脚本的出现,不仅解决了由于C/C++难以精通而带来的开发效率问题,而且还降低了使用C/C++进行开发的项目风险和成本。从此,脚本与游戏开发相得益彰,互相促进,逐渐成为了游戏开发中不可或缺的一个部分。

而到了如今手游兴起的年代,市场的需求变得更加庞大且变化更加频繁。这就更加要求需要有脚本语言来提高项目的开发效率、降低项目的成本。
而作为游戏脚本,它具体的优势都包括哪些呢?

  1. 易于学习,代码方便维护。适合快速开发。

  2. 开发成本低。由于上述第一点,因为易于学习,所以可以启用新人,同时开发速度快,这些都是降低成本的方法。

因此,包括Unity3D在内的众多游戏引擎,都提供了脚本接口,让开发者在开发项目时能够摆脱C/C++(注:Unity3D本身是用C/C++写的)的束缚,这其实是变相的降低了游戏开发的门槛,吸引了很多独立开发者和游戏制作爱好者。

0x02 What?Mono提供的脚本机制

首先一个问题:Mono是什么?

Mono是一个由Xamarin公司所赞助的开源项目。它基于通用语言架构(Common Language Infrastructure ,缩写为CLI)和C#的ECMA 标准(Ecma-335、Ecam-334),提供了微软的.Net框架的另一种实现。与微软的.Net框架不同的是,Mono具备了跨平台的能力,也就是说它不仅能运行在Windows系统上,而且还可以运行在Mac OSX、Linux甚至是一些游戏平台上。

所以把它作为跨平台的方案是像Unity3D这种开发跨平台游戏的游戏引擎的一个不错的选择。但Mono又是如何提供这种脚本的功能的呢?

如果需要利用Mono为应用开发提供脚本功能,那么其中一个前提就是需要将Mono的运行时嵌入到应用中,因为只有这样才有可能使得托管代码和脚本能够在原生应用中使用。所以,我们可以发现,将Mono运行时嵌入应用中是多么的重要。但在讨论如何将Mono运行时嵌入原生应用中去之前,我们首先要搞清楚Mono是如何提供脚本功能的,以及Mono提供的到底是怎样的脚本机制。

Mono和脚本

本小节将会讨论如何利用Mono来提高我们的开发效率以及拓展性而无需将已经写好的C/C++代码重新用C#写一遍,也就是Mono是如何提供脚本功能的。

常常使用一种编程语言开发游戏是比较常见的一种情况。因而游戏开发者往往需要在高效率的低级语言和低效率的高级语言之间抉择。例如一个用C/C++开发的应用的结构如下图:

可以看到低级语言和硬件打交道的方式更加直接,所以其效率更高。

可以看到高级语言并没有和硬件直接打交道,所以其效率较低。
如果以速度作为衡量语言的标准,那么语言从低级到高级的大体排名如下:

  • 汇编语言

  • C/C++,编译型静态不安全语言

  • C#、Java,编译型静态安全语言

  • Python, Perl, Javascript,解释型动态安全语言

开发者在选择适合自己的开发语言时,的确面临着很多现实的问题。

高级语言对开发者而言效率更高,也更加容易掌握,但高级语言也并不具备低级语言的那种运行速度、甚至对硬件的要求更高,这在某种程度上的确也决定了一个项目到底是成功还是失败。

因此,如何平衡两者,或者说如何融合两者的优点,便变得十分重要和迫切。脚本机制便在此时应运而生。游戏引擎由富有经验的开发人员使用C/C++开发,而一些具体项目中功能的实现,例如UI、交互等等则使用高级语言开发。

通过使用高级脚本语言,开发者便融合了低级语言和高级语言的优点。同时提高了开发效率,如同第一节中所讲的,引入脚本机制之后开发效率提升了,可以快速的开发原型,而不必把大量的时间浪费在C/C++上。

脚本语言同时提供了安全的开发沙盒模式,也就是说开发者无需担心C/C++开发的引擎中的具体实现细节,也无需关注例如资源管理和内存管理这些事情的细节,这在很大程度上简化了应用的开发流程。

而Mono则提供了这种脚本机制实现的可能性。即允许开发者使用JIT编译的代码作为脚本语言为他们的应用提供拓展。

目前很多脚本语言的选择趋向于解释型语言,例如cocos2d-js使用的javascript。因此效率无法与原生代码相比。而Mono则提供了一种将脚本语言通过JIT编译为原生代码的方式,提高了脚本语言的效率。例如,Mono提供了一个原生代码生成器,使你的应用的运行效率尽可能高。同时提供了很多方便的调用原生代码的接口。

而为一个应用提供脚本机制时,往往需要和低级语言交互。这便不得不提到将Mono的运行时嵌入到应用中的必要性了。那么接下来,我将会讨论一下如何将Mono运行时嵌入到应用中。

Mono运行时的嵌入

既然我们明确了Mono运行时嵌入应用的重要性,那么如何将它嵌入应用中就成为了下一个值得讨论的话题。

这个小节我会为大家分析一下Mono运行时究竟是如何被嵌入到应用中的,以及如何在原生代码中调用托管方法,相应的,如何在托管代码中调用原生方法。而众所周知的一点是,Unity3D游戏引擎本身是用C/C++写成的,所以本节就以Unity3D游戏引擎为例,假设此时我们已经有了一个用C/C++写好的应用(Unity3D)。

将你的Mono运行时嵌入到这个应用之后,我们的应用就获取了一个完整的虚拟机运行环境。而这一步需要将“libmono”和应用链接,一旦链接完成,你的C++应用的地址空间就会像下图一般:

而在C/C++代码中,我们需要将Mono运行时初始化,一旦Mono运行时初始化成功,那么下一步最重要的就是将CIL/.NET代码加载进来。加载之后的地址空间将会如下图所示:

那些C/C++代码,我们通常称之为非托管代码,而通过CIL编译器生成CIL代码我们通常称之为托管代码。
所以,将Mono运行时嵌入我们的应用,可以分为三个步骤:

  1. 编译C++程序和链接Mono运行时

  2. 初始化Mono运行时

  3. C/C++和C#/CIL的交互

让我们一步一步的进行。首先我们需要将C++程序进行编译并链接Mono运行时。此时我们会用到pkg-config工具。在Mac上使用homebrew来进行安装,在终端中输入命令“brew install pkgconfig”即可。
待pkg-config安装完毕之后,我们新建一个C++文件,并且命名为unity.cpp,作为我们的原生代码部分。我们需要将这个C++文件进行编译,并和Mono运行时链接。
在终端输入:

g++ unity.cpp  -framework CoreFoundation -lobjc -liconv `pkg-config --cflags --libs mono-2`

此时,经过编译和链接之后,我们的unity.cpp和Mono运行时被编译成了可执行文件。
到此,我们需要能够将Mono的运行时初始化。所以再重新回到刚刚新建的unity.cpp文件中,我们要在C++文件中来进行运行时的初始化工作,即调用mono_jit_init方法。代码如下:

#include <mono/jit/jit.h>
#include <mono/metadata/assembly.h>
#include <mono/metadata/class.h>
#include <mono/metadata/debug-helpers.h>
#include <mono/metadata/mono-config.h>

MonoDomain* domain;

domain = mono_jit_init(managed_binary_path);

mono_jit_init这个方法会返回一个MonoDomain,用来作为盛放托管代码的容器。其中的参数managed_binary_path,即应用运行域的名字。除了会返回MonoDomain之外,这个方法还会初始化默认框架版本,即2.0或4.0,这个主要由使用的Mono版本来决定。当然,我们也可以手动指定版本。只需要调用下面的方法即可:

domain = mono_jit_init_version ("unity", ""v2.0.50727);

到此,我们获取了一个应用域——domain。但是当Mono运行时被嵌入一个原生应用的时候,它显然需要一种方法来确定自己所需要的运行时程序集以及配置文件。默认情况下它会使用在系统中定义的位置。

如图,可以看到,在一台电脑上可以存在很多不同版本的Mono,如果我们的应用需要特定的运行时的话,我们显然也需要指定其程序集和配置文件的位置。

为了选择我们所需要的Mono版本,可以使用mono_set_dirs方法:

mono_set_dirs("/Library/Frameworks/Mono.framework/Home/lib", "/Library/Frameworks/Mono.framework/Home/etc");

这样,我们就设置了Mono运行时的程序集和配置文件路径。
当然,Mono运行时在执行一些具体功能的时候,可能还需要依靠额外的配置文件来进行。所以我们有时也需要为Mono运行时加载这些配置文件,通常我们使用mono_config_parse 方法来进行加载这些配置文件的工作。
当mono_config_parse 的参数为NULL时,Mono运行时将加载Mono的配置文件。当然作为开发者,我们也可以加载自己的配置文件,只需要将我们自己的配置文件的文件名作为mono_config_parse方法的参数即可。
Mono运行时的初始化工作到此完成。接下来,我们就需要加载程序集并且运行它了。这里我们需要用到MonoAssembly和mono_domain_assembly_open这个方法。

const char* managed_binary_path = "./ManagedLibrary.dll";
MonoAssembly *assembly;  
assembly = mono_domain_assembly_open (domain, managed_binary_path); 
if (!assembly)   
   error ();

上面的代码会将当前目录下的ManagedLibrary.dll文件中的内容加载进已经创建好的domain中。此时需要注意的是Mono运行时仅仅是加载代码而没有立刻执行这些代码。

如果要执行这些代码,则需要调用被加载的程序集中的方法。或者当你有一个静态的主方法时(也就是一个程序入口),你可以很方便的通过mono_jit_exec方法来调用这个静态入口。

下面我将为各位举一个将Mono运行时嵌入C/C++程序的例子,这个例子的主要流程是加载一个由C#文件编译成的DLL文件,之后调用一个C#的方法输出Hello World。

首先,我们完成C#部分的代码。

namespace ManagedLibrary
{
   public static class MainTest
   {
       public static void Main()
       {
         System.Console.WriteLine("Hello World");
       }
   }
}

在这个文件中,我们实现了输出Hello World的功能。之后我们将它编译为DLL文件。这里我也直接使用了Mono的编译器——mcs。在终端命令行使用mcs编译该cs文件。同时为了生成DLL文件,还需要加上-t:library选项。

mcs ManagedLibrary.cs -t:library

这样,我们便得到了cs文件编译之后的DLL文件,叫做ManagedLibrary.dll。

接下来,我们完成C++部分的代码。嵌入Mono的运行时,同时加载刚刚生成ManagedLibrary.dll文件,并且执行其中的Main方法用来输出Hello World。

#include <mono/jit/jit.h>
#include <mono/metadata/assembly.h>
#include <mono/metadata/class.h>
#include <mono/metadata/debug-helpers.h>
#include <mono/metadata/mono-config.h>
MonoDomain *domain;
int main()
{
    const char* managed_binary_path = "./ManagedLibrary.dll";
    //获取应用域
    domain = mono_jit_init (managed_binary_path);
    //mono运行时的配置
    mono_set_dirs("/Library/Frameworks/Mono.framework/Home/lib", "/Library/Frameworks/Mono.framework/Home/etc");
    mono_config_parse(NULL);
    //加载程序集ManagedLibrary.dll
    MonoAssembly* assembly = mono_domain_assembly_open(domain, managed_binary_path);
    MonoImage* image = mono_assembly_get_image(assembly);
    //获取MonoClass
    MonoClass* main_class = mono_class_from_name(image, "ManagedLibrary", "MainTest");
    //获取要调用的MonoMethodDesc
    MonoMethodDesc* entry_point_method_desc = mono_method_desc_new("ManagedLibrary.MainTest:Main()", true);
    MonoMethod* entry_point_method = mono_method_desc_search_in_class(entry_point_method_desc, main_class);
    mono_method_desc_free(entry_point_method_desc);
    //调用方法
    mono_runtime_invoke(entry_point_method, NULL, NULL, NULL);
    //释放应用域
    mono_jit_cleanup(domain);
    return 0;
}

之后编译运行,可以看到屏幕上输出的Hello World。
但是既然要提供脚本功能,将Mono运行时嵌入C/C++程序之后,只是在C/C++程序中调用C#中定义的方法显然还是不够的。脚本机制的最终目的还是希望能够在脚本语言中使用原生的代码,所以下面我将站在Unity3D游戏引擎开发者的角度,继续探索一下如何在C#文件(脚本文件)中调用C/C++程序中的代码(游戏引擎)。

0x03 How?如何模拟Unity3D中的脚本机制

首先,假设我们要实现的是Unity3D的组件系统。为了方便游戏开发者能够在脚本中使用组件,那么我们首先要在C#文件中定义一个Component类。

//脚本中的组件Component
public class Component
{
   public int ID { get; }
   private IntPtr native_handle;
}

与此同时,在Unity3D游戏引擎(C/C++)中,则必然有和脚本中的Component相对应的结构。


//游戏引擎中的组件Component
struct Component
{
   int id;
}

托管代码(C#)中的接口

可以看到此时组件类Component只有一个属性,即ID。我们再为组件类增加一个属性,Tag。

之后,为了使托管代码能够和非托管代码交互,我们需要在C#文件中引入命名空间System.Runtime.CompilerServices,同时需要提供一个IntPtr类型的句柄以便于托管代码和非托管代码之间引用数据。(IntPtr 类型被设计成整数,其大小适用于特定平台。 即是说,此类型的实例在 32 位硬件和操作系统中将是 32 位,在 64 位硬件和操作系统上将是 64 位。IntPtr 对象常可用于保持句柄。 例如,IntPtr 的实例广泛地用在 System.IO.FileStream 类中来保持文件句柄。)

最后,我们将Component对象的构建工作由托管代码C#移交给非托管代码C/C++,这样游戏开发者只需要专注于游戏脚本即可,无需去关注C/C++层面即游戏引擎层面的具体实现逻辑了,所以我在此提供两个方法即用来创建Component实例的方法:GetComponents,以及获取ID的get_id_Internal方法。

这样在C#端,我们定义了一个Component类,主要目的是为游戏脚本提供相应的接口,而非具体逻辑的实现。下面便是在C#代码中定义的Component类。

using System;
using System.Runtime.CompilerServices;
namespace ManagedLibrary
{
   public class Component
   {
      //字段
      private IntPtr native_handle = (IntPtr)0;
      //方法
      [MethodImpl(MethodImplOptions.InternalCall)]
      public extern static Component[] GetComponents();
      [MethodImpl(MethodImplOptions.InternalCall)]
      public extern static int get_id_Internal(IntPtr native_handle);
      //属性
      public int ID
      {
         get 
         {
            return get_id_Internal(this.native_handle);
         }
      }
      public int Tag {
         [MethodImpl(MethodImplOptions.InternalCall)]
         get;
      }
   }
}

之后,我们还需要创建这个类的实例并且访问它的两个属性,所以我们再定义另一个类Main,来完成这项工作。
Main的实现如下:

// Main.cs
namespace ManagedLibrary
{
   public static class Main
   {
      public static void TestComponent ()
      {
         Component[] components = Component.GetComponents();
         foreach(Component com in components)
         {
            Console.WriteLine("component id is " + com.ID);
            Console.WriteLine("component tag is " + com.Tag);
         }
      }
   }
}

非托管代码(C/C++)的逻辑实现

完成了C#部分的代码之后,我们需要将具体的逻辑在非托管代码端实现。而我上文之所以要在Component类中定义两个属性:ID和Tag,是为了使用两种不同的方式访问这两个属性,其中之一就是直接将句柄作为参数传入到C/C++中,例如上文我提供的get_id_Internal这个方法,它的参数便是句柄。第二种方法则是在C/C++代码中通过Mono提供的mono_field_get_value方法直接获取对应的组件类型的实例。
所以组件Component类中的属性获取有两种不同的方法:

//获取属性
int ManagedLibrary_Component_get_id_Internal(const Component* component)
{
    return component->id;
}
 
int ManagedLibrary_Component_get_tag(MonoObject* this_ptr)
{
    Component* component;
    mono_field_get_value(this_ptr, native_handle_field, reinterpret_cast<void*>(&Component));
    return component->tag;
}

之后,由于我在C#代码中基本只提供接口,而不提供具体逻辑实现。所以我还需要在C/C++代码中实现获取Component组件的具体逻辑,之后再以在C/C++代码中创建的实例为样本,调用Mono提供的方法在托管环境中创建相同的类型实例并且初始化。
由于C#中的GetComponents方法返回的是一个数组,所以对应的,我们需要使用MonoArray从C/C++中返回一个数组。所以C#代码中GetComponents方法在C/C++中对应的具体逻辑如下:

MonoArray* ManagedLibrary_Component_GetComponents()
{
    MonoArray* array = mono_array_new(domain, Component_class, num_Components);
    
    for(uint32_t i = 0; i < num_Components; ++i)
    {
        MonoObject* obj = mono_object_new(domain, Component_class);
        mono_runtime_object_init(obj);
        void* native_handle_value = &Components[i];
        mono_field_set_value(obj, native_handle_field, &native_handle_value);
        mono_array_set(array, MonoObject*, i, obj);
    }
    
    return array;
}

其中num_Components是uint32_t类型的字段,用来表示数组中组件的数量,下面我会为它赋值为5。之后通过Mono提供的mono_object_new方法来创建MonoObject的实例。而需要注意的是代码中的Components[i],Components便是在C/C++代码中创建的Component实例,这里用来给MonoObject的实例初始化赋值。
创建Component实例的过程如下:

num_Components = 5;
    Components = new Component[5];
    for(uint32_t i = 0; i < num_Components; ++i)
    {
        Components[i].id = i;
        Components[i].tag = i * 4;
    }

C/C++代码中创建的Component的实例的id为i,tag为i * 4。
最后我们还需要将C#中的接口和C/C++中的具体实现关联起来。即通过Mono的mono_add_internal_call方法来实现,也即在Mono的运行时中注册刚刚用C/C++实现的具体逻辑,以便将托管代码(C#)和非托管代码(C/C++)绑定。

// get_id_Internal
mono_add_internal_call("ManagedLibrary.Component::get_id_Internal", reinterpret_cast<void*>(ManagedLibrary_Component_get_id_Internal));
//Tag get
mono_add_internal_call("ManagedLibrary.Component::get_Tag", reinterpret_cast<void*>(ManagedLibrary_Component_get_tag));
//GetComponents
mono_add_internal_call("ManagedLibrary.Component::GetComponents", reinterpret_cast<void*>(ManagedLibrary_Component_GetComponents));

这样,我们便使用非托管代码(C/C++)实现了获取组件、创建和初始化组件的具体功能,接下来为了验证我们是否成功的模拟了将Mono运行时嵌入“Unity3D游戏引擎”中,我们需要将代码编译并且查看输出是否正确。
首先将C#代码编译为DLL文件。我们在终端直接使用Mono的mcs编译器来完成这个工作。
运行后生成了ManagedLibrary.dll文件。
之后将unity.cpp和Mono运行时链接、编译,会生成一个a.out文件(在Mac上)。执行a.out,可以看到在终端上输出了创建出的组件的ID和Tag的信息。

0x04 后记

通过本文,我们可以看到游戏脚本语言出现的必然性。同时也应该了解Unity3D的底层是C/C++实现的,但是它通过Mono提供了一套脚本机制,以方便游戏开发者快速的开发游戏同时也降低了游戏开发的门槛。

查看原文

赞 5 收藏 13 评论 1

Blue_Moon 赞了文章 · 6月12日

Nginx入门及如何反向代理解决生产环境跨域问题

1.Nginx入门与基本操作篇

注:由于服务器是windows系统,所以本文主要讲解Nginx在windows下的操作。

  • 首先下载Nginx
  • 解压缩,我们所有的配置基本都在万能的 nginx/conf/nginx.conf 中完成,其它文件可以不用理
  • 关于nginx.conf
...
#需要我们按需要修改的一般只有中间server里的代码
    server {
        # 设置监听端口
        listen       9000;
        server_name  localhost;
        # 设置静态资源路径,如下设置打输入地址时会打开H盘app/dist下的index页面
        location / {
            root   H:/app/dist;
            index  index.html;
        }
        # 报错页面,同上根据需要设置,也可不理
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
...
  • 这样一个简单服务的配置就完成了,启动服务访问 http://localhost:9000 即可打开对应网页,如果需要启动多个服务,只需多添加server配置后重启即可。
  • 几个常用的操作指令(以下指令请在nginx文件目录下使用)
# 启动nginx(我的情况是运行起来后cmd就输入不了其它指令了,需要在打开一个cmd来操作)
    nginx.exe
# 修改nginx.conf文件后重启nginx
    nginx.exe -s reload
# 暂停nginx服务
    nginx.exe -s stop
# 有时暂停服务失效,需要强制终止nginx进程 注:/f 强制执行
    taskkill /im nginx.exe /f

2.反向代理解决跨域问题

通常开发环境可以通过设置proxy解决跨域问题,而生产环境下要么把前端项目放在后端项目里,要么设置cor解决跨域问题,前者不利于前后端分离,后者需要后端配置,而现在使用nginx做启动服务设置反向代理可以很好解决跨域问题。
  • 还是回到万能的nginx.conf文件,添加location匹配规则实现代理转发
    server {
        listen       9000;
        server_name  localhost;
        
        location / {
            root   H:/app/dist;
            index  index.html;
        }
        #设置代理转发
        location /api/ {
          proxy_pass   http://localhost:9600/;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
  • 通过上面的设置,在重启服务,可以让页面中所有包含 /api/ 字段的请求都转为由服务器去向http://localhost:9600/地址发送请求,从而巧妙的解决了浏览器的跨域问题。
  • 怎么样,是不是很简单,快动手配置是试试吧(^_^)~
查看原文

赞 2 收藏 1 评论 0

Blue_Moon 赞了回答 · 5月23日

解决react点击事件如何传传传参。

最佳错了,应该改成这样!

<button onClick={(ev) => {this.handleClick(ev, arg1, arg2,……)}}/>

handleClick(ev, arg1, arg,……) {

//code

}

关注 9 回答 5

Blue_Moon 赞了文章 · 1月15日

工业上的通讯协议之Modbus

那有什么天生如此,只是我们天天坚持。

通讯协议

通讯协议又称通信规程,是指 通讯双方对数据传送控制的一种约定
约定中包括对数据格式,同步方式,传送速度,传送步骤,检纠错方式以及控制字符定义等问题做出统一规定,通信双方必须共同遵守,它也叫做链路控制规程

常用的仪表通讯协议有:

  • modbus通讯协议

  • RS-232通讯协议

  • RS-485通讯协议

  • HART通讯协议

  • MPI通信

  • 串口通信

  • PROFIBUS通信

  • 工业以太网

  • ASI通信

  • PPI通信

  • 远程无线通信

  • TCP

  • UDP

  • S7

  • profibus

  • pofinet

  • MPI

  • PPI

  • Profibus-DP

  • Devicenet

  • Ethernet

下面有pdf文档提供下载

Modbus通讯协议

Modbus协议最初由Modicon公司开发出来,在1979年末该公司成为施耐德自动化部门的一部分,现在Modbus已经是工业领域全球最流行的协议。此协议支持传统的RS-232、RS-422、RS-485和以太网设备

由于modbus协议是完全公开透明的,所需的软硬件又非常简单,这就使它成为了一种通用的工业标准。许多工业设备,包括PLC,DCS,智能仪表等都在使用Modbus协议作为他们之间的通讯标准。有了它,不同厂商生产的控制设备可以连成工业网络,进行集中监控。

特点

Modbus 协议是应用于电子控制器上的一种通用语言。通过此协议,控制器相互之间、控制器经由网络(例如以太网)和其它设备之间可以通信。 它已经成为一通用工业标准。此协议++定义了一个控制器能认识使用的消息结构++,而不管它们是经过何种网络进行通信的。

modbus通讯协议是一种主从式异步半双工通信协议采用主从式通讯结构,可以使一个主站对应多个从站进行双向通信。它描述了一控制器请求访问其它设备的过程,如何回应来自其它设备的请求,以及怎样侦测错误并记录。它制定了消息域格局和内容的公共格式。

Modbus协议包括ASCII、RTU等通讯方式,并没有规定物理层。 此协议定义了控制器能够认识和使用的消息结构,而不管它们是经过何种网络进行通信的。标准的Modicon控制器使用RS232C实现串行的Modbus。Modbus的ASCII、RTU协议规定了消息、数据的结构、命令和就答的方式,数据通讯采用主-从方式,主站发出数据请求消息,从站接收到正确消息后就可以发送数据到主站以响应请求;主站也可以直接发消息修改从站的数据,实现双向读写。

clipboard.png

描述

当在一Modbus网络上通信时,此协议决定了每个控制器须要知道它们的设备地址,识别按地址发来的消息,决定要产生何种行动。如果需要回应,控制器将生成反馈信息并用Modbus协议发出。在其它网络上,包含了Modbus协议的消息转换为在此网络上使用的帧或包结构。这种转换也扩展了根据具体的网络解决节地址、路由路径及错误检测的方法。

当在网络上通信时,Modbus协议决定了每个控制器须要知道它们的设备地址,识别按地址发来的消息,决定要产生何种行动。如果需要回应,控制器将生成应答并使用Modbus协议发送给询问方。

Modbus协议需要对数据进行校验,串行协议中除有奇偶校验外,ASCII模式采用LRC校验,RTU模式采用16位CRC校验。另外,Modbus采用主从方式定时收发数据,在实际使用中如果某从站点断开后(如故障或关机),主端可以诊断出来,而当故障修复后,网络又可自动接通。因此,Modbus协议的可靠性较好。

Modbus与OSI参考模型

clipboard.png

Modbus之ASCII通讯方式

clipboard.png

数据帧

clipboard.png

广播模式(只用于写操作)

clipboard.png

非广播模式

clipboard.png

Modbus之RTU通讯方式

clipboard.png

简化文档 :链接:http://pan.baidu.com/s/1c99rZo 密码:zspd

查看原文

赞 1 收藏 1 评论 0

Blue_Moon 关注了专栏 · 1月10日

vue源码解析系列

最近在看vue源码。感觉网上的解析都是没有很深入。非常想自己利用业余事件。好好的深入进去。也分享出来。谢谢了

关注 42

Blue_Moon 关注了用户 · 1月9日

fengruiabc @fengruiabc

关注 23

认证与成就

  • 获得 19 次点赞
  • 获得 51 枚徽章 获得 1 枚金徽章, 获得 13 枚银徽章, 获得 37 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2013-09-22
个人主页被 651 人浏览