2024年3月

前言

虽然已经正式转JAVA了,但最近发现一个特别好的开源项目masuit,不仅提供很多简便的功能,还有图像的一些特殊操作功能。
之前我们比较图片应该都是使用的openCV,不过这个masuit,看上去也不错,而且代码使用简单,因此强烈推荐。
下面就实现一个简单图像对比。

实现对比

首先添加引用masuit,如下图:

代码如下,实现一个可以对比翻转,旋转的图片。

点击查看代码

using Masuit.Tools.Media;
using SixLabors.ImageSharp;
using System.Diagnostics;
using SixLabors.ImageSharp.Processing;


var sw = Stopwatch.StartNew();
string img1 = "img/img1.png";
string img2 = "img/img3.png";
var hasher = new ImageHasher();
var hash1 = hasher.DifferenceHash256(img1); 
var hashList = new List<ulong[]>();
hashList.Add(hash1);
var sim = 0.85;
using (var image1 = SixLabors.ImageSharp.Image.Load<SixLabors.ImageSharp.PixelFormats.Rgba32>(img1))
{

    var actions = new List<Action>();
    //翻转委托
    actions.Add(() =>
    {
        using Image<SixLabors.ImageSharp.PixelFormats.Rgba32> clone = image1.Clone(c => c.Flip(FlipMode.Horizontal));
        var flipH = clone.DifferenceHash256();
        hashList.Add(flipH);
    });
    actions.Add(() =>
    {
        using var clone = image1.Clone(c => c.Flip(FlipMode.Vertical));
        var flipV = clone.DifferenceHash256();
        hashList.Add(flipV);
    });
    //选择委托
    actions.Add(() =>
    {
        using var clone = image1.Clone(c => c.Rotate(90));
        var rotate90 = clone.DifferenceHash256();
        hashList.Add(rotate90);
    });
    actions.Add(() =>
    {
        using var clone = image1.Clone(c => c.Rotate(180));
        var rotate180 = clone.DifferenceHash256();
        hashList.Add(rotate180);
    });
    actions.Add(() =>
    {
        using var clone = image1.Clone(c => c.Rotate(270));
        var rotate270 = clone.DifferenceHash256();
        hashList.Add(rotate270);
    });
    Parallel.Invoke(actions.ToArray());

    var hash2 = hasher.DifferenceHash256(img2);
    var similar = hashList.Select(h => ImageHasher.Compare(hash2, h)).Max();
    if (similar >= sim)
    {
        Console.WriteLine($"是一样的图片 similar:{similar}");
    }
    else
    {
        Console.WriteLine($"不是一样的图片  similar:{similar}");
    }
    using (var image2 = SixLabors.ImageSharp.Image.Load<SixLabors.ImageSharp.PixelFormats.Rgba32>(img2))
    {
        Console.WriteLine($"比较图片信息--分辨率:{image2.Width}x{image2.Height},大小:{new FileInfo(img2).Length / 1024}KB");
    }
    Console.WriteLine(sw.ElapsedMilliseconds + "ms");
    Console.ReadKey();

}


测试运行

这里使用三张图片对比,如下:

第一张是原图,第二张是略微裁剪边缘的图片,第三张是旋转图片。

1图和2图比较结果如下:

1图和3图比较结果如下:


代码简单实用,非常值得使用,比较速度也很快,大概在1秒以内。


注:此文章为原创,任何形式的转载都请联系作者获得授权并注明出处!



若您觉得这篇文章还不错,请点击下方的【推荐】,非常感谢!

https://www.cnblogs.com/kiba/p/18053616.html

Github :
https://github.com/makemyownlife/platform-sms

短信服务 platform-sms 0.5.0 发布 ,新的版本做了非常多的优化和改进。

1、支持发送任意时间延时短信;

2、优化三方渠道适配器加载逻辑;

3、支持 Docker 部署。

4、优化线程模型。

写这个项目的初心很简单:做一个
简单易用
的教学型项目,帮助工程师快速
提升技术认知

这篇文章,我们聊聊短信服务中那些有趣的设计模式,希望对大家有所启发。

1 适配器模式

笔者曾经在科大讯飞教育部门,维护过一款短信服务,过程是相当痛苦。因为业务代码和渠道发送短信代码耦合度太高,为了将短信渠道从阿里云迁移到亿美短信,花费了大量的时间和精力。

所以,笔者在设计 platform-sms 时,考虑到两点:
逻辑独立

资源隔离

  • 逻辑独立:独立的模块内编写各自的发送短信的逻辑 ,彼此之间互不影响。
  • 资源隔离:通过类加载器加载需要的类,不会有包冲突等问题。

因此,笔者参考了开源项目 canal adapter 的设计方式。

部署目录来看,短信平台将三方渠道的相关逻辑独立在单独的文件夹
plugin
中。

服务端启动后,需要将插件加载到服务端容器中,加载完成之后,服务端可以根据渠道编号获取适配器(
aliyun

tencent

emay
)对象进行发送短信,申请短信模版等操作。

下图是适配器核心模块。

模块定义了 OuterAdapter 接口,一个完成的适配器需要实现如下的接口:

在阿里云、腾讯云各自的模块内实现 OuterAdapter 接口 ,并配置好接口限定名文件。

觉得神奇吧,这就是适配器模式的优雅!

2 线程模型

对于一个合理的系统来讲,一定要配置合理的线程模型 , 不同的线程用于不同的场景。

发送一条即时短信需要两种线程协同处理,tomcat 线程负责创建短信记录,即时短信处理线程池负责发送短信。

假如即时短信处理线程池在发送短信时断电,重试线程可以起到部分容错的功能。

3 延时消息

延时消息是非常有趣的功能,最新的版本支持任意时间的延时短信。

springboot controller 接收到发送短信请求后,通过「发送短信处理器」将请求存储到记录表。

Redis 有容量限制 ,我们不必将所有的数据存储在 Redis 里。处理延迟消息时,我们将短信数据做了
冷热划分

  • 热数据:延时短信并不需要马上发送,而是延时单线程从延时队列中获取元素,获取到元素已经到了发送时间点了,则调用分发器发送短信。延时短信处理线程池会异步(相对延时服务单线程来讲)的执行发送短信。

  • 冷数据:定时任务加载下个自然小时的短信记录,load 到 Redis 延时队列里。

4 缓存实用技巧

1、本地缓存 + Redis PubSub 缓存同步

当客户端调用发送短信请求时,每次都需要鉴权,为了提升系统性能,应用信息都是从本地缓存中获取,然后判断客户端的请求是否合法。

应用信息
就是
存储在
ConcurrentHashMap
中 ,通过
定时任务刷新缓存

为了保存缓存与数据库同步,我们采用 Pub/Sub 的方案。

下图,当我们启动两个短信平台应用,在短信平台 web 控制台修改应用信息时,我们发现两个应用的本地内存都发生变化了。

2、模板页面列表缓存教学

为了帮助大家学习列表缓存的技巧,笔者特意在模板页面做了教学演示。

我们使用列表缓存方案:
查询对象ID列表,只缓存每个对象条目 。

SmsTemplateService 接口定义一个新的查询模板列表的方法
queryTemplates2

上图展示了模板条目缓存结果,性能相比直接从数据库查询得到显著的提升,平均性能提升 5 倍。

5 多种部署方式

短信平台可以支持多种部署方式:
前后端分离

前后端合并

Docker

项目中我们通过使用 maven 多种插件技巧 ,同时依赖 springboot 的特性,实现生成前后端合并包,并提供了 windows、linux 一键启动 / 停止脚本等。

为了便于大家学习如何制作 Docker 镜像,笔者贴心的将 Docker 打包命令、以及启动容器的命令注释写到 Dockerfile 文件。

下图是我们启动短信服务 Docerk 容器的效果,非常简单。

6 未来规划

短信服务 platform-sms 代码中还有非常多的技巧,比如:

  • 优雅的创建单线程、多线程
  • 参考 RocketMQ 的通讯模块,实现了适配器的分发器
  • Redis 实现分布式锁
  • 定制 SDK 与服务端通讯协议

笔者也会继续优化 platform-sms ,规划列表:

  • 控制台实现批量发送短信
  • 更加灵活易用的绑定模板
  • 限流配置(接口、应用、手机多个维度)

前言

作为前端开发人员,我们最关注的就是应用的交互体验,而元素背景是最基础的交互体验之一。一般而言,能够使用代码实现的界面,我们都会尽可能减少图片的使用,这主要是有几方面的原因,第一,是图片会消耗更多的带宽,对于移动端或者网络信号较差时的体验不够友好,第二是不够便捷,在使用图片的情况下即使有细微的调整也需要重新做图上传,第三是不够灵活,这一点主要体现在根据不同的条件需要呈现不同的效果。

本文就使用网格背景作为例子,来通过代码实现,这是在可视化课程中新学的内容,这里我做一个总结和复习。网格背景是一种常见的设计元素,它可以为网页增添一种现代感和动态感。要实现网格背景,我们可以使用CSS代码来实现,这类实现方式我在很多文章中都有看到过,但除了使用CSS,我们还可以通过WebGL实现,通过WebGL来实现,还能额外带来一些好处,比如有更好的性能,也能与画布上的其他元素更好地融合。

网格背景

在使用具体的代码实现之前,我们先简单来分析一下网格背景。

从上面的图片中我们能发现,网格背景其实可以看作是一个小的网格图案重复了n遍而形成的一个背景,也就是说这是一个重复图案的背景。

使用CSS实现

在使用WebGL来实现网格背景之前,我们还是先来看一下CSS代码是如何实现的。

在使用CSS代码实现网格背景时,需要用到CSS中的渐变函数,因为渐变函数最终形成的效果类似于图像,所以可以当作图像来使用,也就是说可以应用于background-image属性。渐变函数有三种:线性渐变、径向渐变和圆锥渐变,在这个例子中,由于网格是直线的,所以我们使用线性渐变函数就可以了。

线性渐变的语法比较简单,以下是
MDN给出的表达式

<linear-gradient()> = 
  linear-gradient( [ <linear-gradient-syntax> ] )  

<linear-gradient-syntax> = 
  [ <angle> | to <side-or-corner> ]? , <color-stop-list>  

<side-or-corner> = 
  [ left | right ]  ||
  [ top | bottom ]  

<color-stop-list> = 
  <linear-color-stop> , [ <linear-color-hint>? , <linear-color-stop> ]#  

<linear-color-stop> = 
  <color> <length-percentage>?  

<linear-color-hint> = 
  <length-percentage>  

<length-percentage> = 
  <length>      |
  <percentage>  

我们主要向线性渐变函数传递三类参数:第一是渐变的方向,第二是初始色值,第三是最终色值。如果要控制得更细致,我们还可以通过上述公式中的length或者percentage来控制某个色值的范围,或者设置多个阶梯色值。

现在我们就可以通过以下CSS代码,来实现网格背景的设置。

.grid-bg {
  width: 300px;
  height: 300px;
  background-image: linear-gradient(to right, transparent 95%, #ccc 0),
    linear-gradient(to bottom, transparent 95%, #ccc 0);
  background-size: 8px 8px, 8px 8px;
}

以上代码中首先使用background-size属性指定了一个网格图案的大小,然后我们知道background-repeat属性的默认值是repeat,所以会重复这个图案来铺满整个元素的背景,最后我们来看background-image这个属性,我们使用了线性渐变函数来给这个属性赋值,可以看到这里用了两个渐变函数。

第一个渐变函数,设置的渐变方向是
to right
,也就是自左向右进行渐变,初始色值是transparent也就是透明色,这里使用了百分数95%来控制透明色的范围,也就是说从0%的位置开始到95%的位置,都是透明色,然后最终色值是#ccc也就是灰色,我们想要95%到100%的范围都是#ccc的灰色,可以直接简写为0。

第二个渐变函数也是类似的。

在这两个渐变函数的作用下,最终形成的图案就是一个小网格,右边5%的宽度和下边5%的宽度由灰色填充;然后在background-repeat默认值repeat的作用下,就会铺满对应元素。

使用WebGL实现

那么既然CSS已经可以实现网格来替代图片了,为什么又要使用WebGL来实现呢?所以那肯定是WebGL的实现有其他的优势,首先是直接调用GPU的话,无论是有多少重复图案都能一次完成,理论上没有性能瓶颈,其次是能满足更多类型的需求,比如能使网格随着Canvas中的其他元素一起缩放。

那么在WebGL中要如何去实现网格背景呢?我们可以通过图案的使用来实现。

基础页面

首先最基础的,我们先在页面上放置一个Canvas。

<canvas width="512" height="512"></canvas>

操作WebGL

接着就可以开始操作WebGL去完成图案的绘制。

在本次实现中,使用了一个基础库
gl-renderer
来简化WebGL的操作,让我们可以将重心放在数据提供和编写shader上。

以下是操作WebGL的代码:

// 第一步:创建Renderer对象
const canvas = document.querySelector('canvas');
const renderer = new GlRenderer(canvas);
// 第二步:创建并启用WebGL程序
const program = renderer.compileSync(fragment, vertex);
renderer.useProgram(program);
// 第三步:设置uniform变量
renderer.uniforms.rows = 32; // 64; // 每一行显示多少个网格(在片元着色器中使用)
// 第四步:将顶点数据送入缓冲区
renderer.setMeshData([{
  positions: [ // 顶点(覆盖整个画布)
      [-1, -1], // 左下
      [-1, 1], // 左上
      [1, 1], // 右上
      [1, -1] // 右下
  ],
  attributes: {
    uv: [ // 纹理坐标(坐标系:左下角[0,0] 右上角[1,1])
        [0, 0], // 左下
        [0, 1], // 左上
        [1, 1], // 右上
        [1, 0] // 右下
    ]
  },
  cells: [ // 顶点索引(三角剖分):将矩形剖分成两个三角形
      [0, 1, 2],
      [2, 0, 3]
  ]
}]);
// 第五步:执行渲染
renderer.render();

以上代码不难理解,就是向WebGL传递数据,并执行渲染。主要有以下几个操作:

  • 首先在初始化阶段,根据GLSL代码和Canvas的WebGL上下文创建WebGL程序;

  • 接着就是传递数据,包括uniform常量、顶点数据和纹理坐标。其中纹理坐标的使用与图案有关。

  • 然后是设置三角剖分的顶点索引。三角剖分简单来说就是将一个多边形使用多个三角形组合拼接来表示,可以参考下图来理解。


  • 最后一步就是渲染。

GLSL代码

现在我们来看关键的GLSL代码。

// 顶点着色器
attribute vec2 a_vertexPosition;
attribute vec2 uv;
varying vec2 vUv;

void main() {
  gl_PointSize = 1.0;
  vUv = uv;
  gl_Position = vec4(a_vertexPosition, 1, 1);
}

以上是顶点着色器,这段代码比较基础,找到要处理的像素点,其余的就是将纹理坐标传递给片元着色器。

在网格背景的实现中,片元着色器是比较关键的一环,现在我们就来看片元着色器的实现代码:

#ifdef GL_ES
precision mediump float;
#endif

varying vec2 vUv; // 由顶点着色器传来的uv属性
uniform float rows;

void main() {
  // st:保存像素点对应纹理坐标的小数部分
  vec2 st = fract(vUv * rows); // fract函数是一个用于获取向量中小数部分的函数
  float d1 = step(st.x, 0.9); // step:阶梯函数。当step(a,b)中的b < a时,返回0;当b >= a时,返回1。
  float d2 = step(0.1, st.y);

  // 根据d1*d2的值,决定使用哪个颜色来绘制当前像素。
  // st.x <= 0.9 且 st.y >= 0.1时,d1*d2=1, 否则为0
  gl_FragColor.rgb = mix(vec3(0.8), vec3(1.0), d1 * d2);
  gl_FragColor.a = 1.0;
}

在编写shader时,要理解其中的GLSL代码不是太容易,因为对于课程中给出的解释也并不太能理解,所以我也是花了一点时间去思考。

首先就是这个fract函数的调用,课程中给出的说法是:它可以帮助我们获得重复的 rows 行 rows 列的值 st

当一个数从 0~1 周期性变化的时候, 我们只要将它乘以整数 N,然后再用 fract 取小数,就能得到 N 个周期的数值。

但我感觉这种说法并不太直观,至少我看下来觉得还是一头雾水,当然也可能是我的理解能力有限,着色器程序理论上是批量执行,那么这段代码针对某个待处理的像素点是什么含义呢?因为暂时我想先自己探索一番,所以我也没有参考其他资料。

经过多日思考,以下是我目前的几点理解:

  1. 首先是传递的纹理坐标,我们可以理解是一个单元的坐标,坐标的范围是一个正方形。

  2. 然后
    vUv * rows
    可以看作是相当于把纹理坐标在画布上的纵向放大rows倍,又因为纹理坐标范围的原因,同时相当于在横向上也放大了rows倍。

  3. 接着就得到了当前待处理的像素点映射到纹理坐标上的位置,后续会根据这个像素点的纹理坐标去计算设置。

  4. st保存了像素点对应纹理坐标的小数部分。

  5. step是阶梯函数,接收两个入参a和b,根据a和b的大小关系返回0或1;当b < a时,返回0;当b >= a时,返回1。

    两个step函数的执行后,我们就可以得到:


    st.x <= 0.9 且 st.y >= 0.1时,d1*d2=1, 否则为0

  6. 最后我们调用mix得到最终的色值。

    mix(a, b, c)是一个线性插值函数。a和b是两个色值,当c为0时,返回a;当c为1时,mix函数返回b。

    在这里
    vec3(0.8)
    是一个灰色色值,
    vec3(1.0)
    是白色;显而易见当d1或d2为0时,也就是st.x > 0.9 或 st.y < 0.1时,像素点渲染为灰色,否则渲染为白色。

具体渲染结果可参考下图(随手画的比较潦草),最终网格的数量由rows决定:

总之我理解的就是,在片元着色器中,我们主要去
计算某个像素点的色值
,在这个例子中,就是映射到纹理坐标并根据纹理坐标计算得到一个色值,最终像素点会被渲染为这个色值。

这段代码中,纹理坐标的小数部分是周期性重复的,所以就可以得到重复的图案,最终形成网格背景。

总结

最后总结一下吧,要理解WebGL代码,有时候还是需要转换一下思路,其实我也并不太确定自己的理解是不是对的,但我觉得有时候学习新的东西,就是给自己一个打开思路的机会,最重要的还是自己去思考理解,让自己得到新的启发。

写在开头

ok,everybody,在过去的两周内,我们大体上讲完了Java的集合,在最后我们探讨了关于HashMap线程不安全的原因,又提出了ConcurrentHashMap这个线程安全的集合解决方案,那么在接下来的2-3周内,我们就一起来学习一下Java中的并发多线程。
在开始学习之前,我们必须要搞清楚几个概念:线程与进程、并发与并行、同步与异步。

线程与进程

Java的并发指的是多线程,而与多线程对应的有个进程的概念,啥是进程呢?我们通过
CTRL+SHIFT+ESC
组合快捷键打开电脑的任务管理器,我们就看到了如下的这幅图。

这里面执行的每一条就是一个进程,
它是程序执行时的一个实例,操作系统会为每个进程分配独立的内存地址空间,因此,进程也是系统进行资源分配和调度的基本单位。

而线程是比进程更小的执行单位,
是进程的子任务,因此它本身不会独立存在,系统不会为线程分配内存,线程组之间只能共享所属进程的资源,而线程仅仅是CPU 调度和分派的基本单位,当前线程 CPU 时间片用完后,会让出 CPU 等下次轮到自己时候在执行。

进程与线程的关系

  1. 一个程序至少一个进程,一个进程至少一个线程,进程中的多个线程是共享进程的资源(堆,字符串常量池(JDK1.8)/方法区(JDK1.7));

  2. 一个进程中有多个线程,多个线程共享进程的堆和方法区资源,但是每个线程有自己的程序计数器,栈区域;

  3. Java 中当我们启动 main 函数时候就启动了一个 JVM 的进程,而 main 函数所在线程就是这个进程中的一个线程,也叫做主线程。 我们通过JMX(Java Management Extensions)管理器去监控一下JVM,看下面这段代码示例:
    【代码示例1】

public class Test {
    public static void main(String[] args) {
        // 获取 Java 线程管理 MXBean
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        // 不需要获取同步的 monitor 和 synchronizer 信息,仅获取线程和线程堆栈信息
        ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
        // 遍历线程信息,仅打印线程 ID 和线程名称信息
        for (ThreadInfo threadInfo : threadInfos) {
            System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());
        }
    }
}

输出:

[6] Monitor Ctrl-Break //监控Ctrl-Break中断信号的线程
[5] Attach Listener //添加事件
[4] Signal Dispatcher // 分发处理给 JVM 信号的线程
[3] Finalizer //调用对象 finalize 方法的线程
[2] Reference Handler //清除 reference 线程
[1] main //main 线程,程序入口

由此可见一个JVM进程在运行时,包含了很多个子线程。

并发与并行

并行:
指两个或两个以上事件或活动在同一时刻发生。如多个任务在多个 CPU 或 CPU 的多个核上同时执行,不存在 CPU 资源的竞争、等待行为。
并发:
并发指在某时刻只有一个事件在发生,某个时间段内由于 CPU 交替执行,可以发生多个事件,存在对 CPU 资源进行抢占。

同步与异步

同步:
发出一个调用之后,在没有得到结果之前, 该调用就不可以返回,一直等待。
异步:
调用在发出之后,不用等待返回结果,该调用直接返回。

结尾彩蛋

如果本篇博客对您有一定的帮助,大家记得
留言+点赞+收藏
呀。原创不易,转载请联系Build哥!

如果您想与Build哥的关系更近一步,还可以关注俺滴公众号“JavaBuild888”,在这里除了看到《Java成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!

开心一刻

正睡着觉,然后来了个电话

对方说:你好,方便面是吗

我愣了一下,以为是恶作剧

回了句:我不是,我是火腿肠!

就挂了电话

又躺了好一会,忽然琢磨过来......

不对呀,她好像说的是:你好,方便面试吗?

前提准备

项目中有这么一块业务

1、
脚本同步
这一环,是运维同事通过脚本实现的,所以我没去关注

2、
读取文件并上传
这一环,是我实现的,稳定运行了很长一段时间了,一直没出问题

中文乱码

直到有一天,
生产环境
出现了异常信息:
/data/userData/Rsync 下不存在文件:测试文件.txt

提示很明显,在目录
/data/userData/Rsync
下,没有找到文件
测试文件.txt

然后联系运维,让运维去看
本系统
的目录:
/data/userData/Rsync
下,文件
测试文件.txt
是否存在

运维的反馈是文件存在,还发了截图

并且把文件复制给我了

然后我将这个文件复制到
测试环境

/data/userData/Rsync
目录下,
测试环境
能正常找到文件
测试环境.txt

那问题出在哪?

问题排查

后面与同个运维同事沟通了下,了解了整个完整流程的细节:

其中
CentOS 7
作为
NFS 服务器
,其系统编码是
UTF-8

Win 2012
作为
NFS 客户端
,其系统编码是
GBK
(chcp 值 936 表示 GBK)

我在怀疑是不是
NFS
的问题?

怎么验证了?

其实最简单的办法就是看一眼
CentOS 7
目录
/data/userData/Rsync
下是不是有中文名乱码文件即可

奈何我认为我说清楚了,运维同事也认为他理解了

结果南辕北辙,我想看的是
CentOS 7
的目录
/data/userData/Rsync
,运维给我看的却是
Win 2012
下的挂载目录
L:

关键是当时我没发现他给我的截图其实不是我想看的!

然后我就开始了我的线上环境模拟之旅

NFS 服务端

CentOS 7
上搭建
NFS
服务器非常简单

1、安装
NFS
,执行
yum install -y nfs-utils
即可

2、对客户端 IP 进行授权

编辑
/etc/exports
,进行授权配置,例如

/data/userData/Rsync/
表示要共享出去的目录,不存在则需要先创建:
mkdir -p /data/userData/Rsync

10.5.108.221
表示客户端 IP 地址,说明共享给谁

(rw,sync,no_root_squash)
表示授权列表,
rw
表示客户端可以读写,
sync
表示自动同步,
no_root_squash
是什么含义,你们自己去查

涉及到目录的读写,需要对
/data/userData/Rsync/
配置合适的读写权限,方便演示就简单点:
chmod 777 /data/userData/Rsync/

编辑好之后,使配置生效,执行命令:
exportfs -rv

3、启动
NFS

systemctl start nfs-server

开机启动:
systemctl enable nfs-server

4、防火墙放行

为了演示方便,直接关闭防火墙:
systemctl stop firewalld

NFS 客户端

WIN 2012
作为客户端,配置起来也很简单

1、安装 NFS 客户端

控制面板
->
程序
->
启用或关闭 Windows 功能

添加角色与功能
,直接连续下一步,直到
服务器角色

如果在
WIN 2012
上搭建
NFS
服务器,则上面三个框住的需要勾选上,然后进行
安装

我们目前是搭建客户端,这个地方可以不勾,下一步,来到
功能

勾选上
NFS 客户端
,然后进行安装即可

2、防火墙放行,为了演示方便,直接关闭防火墙

3、目录挂载

直接在
cmd
中执行:
mount 10.5.108.225:/data/userData/Rsync L:

不出意外的话,挂载成功

此时,
我的电脑
会出现一个网络盘

正常来讲,不会有红叉;不影响效果,先不纠结

同步

简单点模拟,直接在
win 2012
上,往挂载盘(网络盘)写文件,文件名是:
测试文件.txt


win 2012
的挂载目录
L:
看,中文没乱码,简直太正常了!

但是,我们去
CentOS 7
的目录
/data/userData/Rsync/
看下

哦豁,换了个马甲,还真不认识了!

程序去找
测试文件.txt
,怎么找得到嘛

乱码原因

这个时候基本能断定问题出在
NFS
,直接
google
下关键字:
Window 2012 NFS 中文乱码
就能找到答案

根因就是:
windows 内置的 nfs 支持的编码非常有限,不支持 utf-8

问题解决

去掉中文

既然中文有问题,那就干掉中文

和上游系统协商了下,文件名改成英文,中文乱码自然就不存在了

有小伙伴可能会有疑问:之前没有中文乱码问题?

其实你们心里应该有答案了,没错,就如你们所想:这块业务,之前一直没出现中文名的文件!

升级 window 系统

win10
可以开启
Beta 版本
,来支持
UTF-8

具体可参考:
一个小设置,让Win10 NFS正常显示中文UTF-8

注意看评论区,坑很多,不推荐这种方式

至于
Windows
系统的其他版本是否支持该配置,大家结合自己的系统去查阅

第三方 NFS 客户端

ms-nfs41-client
,人家搭建步骤写的很清楚了

换共享方案

Samba
就是其中之一,大家可以去查阅并实现

总结

1、
Windows
自带的
NFS
支持的编码非常有限,竟连
UTF-8
都不支持,会导致中文乱码问题

2、沟通的时候,一定要清楚的表达自己的意图,要保证人家接收到的信息就是你要表达的信息