在Windows系统中,从XP开始就内嵌了一个设置网络端口转发的功能。依靠这个功能,任何到本地端口的TCP连接(ipv4或ipv6)都能够被转发到任意一个本地端口,甚至是远程主机的某个端口。并且,Windows系统并不需要去开启监听这个转发端口的服务。

在Windows服务器中,远程访问控制协议(RRAS)通常被用作端口转发,但是有一种更简单的配置方法,并且这种配置方法适用于windows的任意版本。

Windows系统下的端口转发使用portproxy模式下的netsh命令,该命令的使用前提是要在管理员身份打开cmd进行执行。

netsh interface portproxy add v4tov4  listenaddress=localaddress listenport= localport connectaddress=destaddress  connectport=destport protocol=tcp

listenaddress – 待连接的ip地址。

listenport –待连接的tcp本地端口 。

connectaddress – 待连接被转发的本地或远程主机的ip地址(支持域名)

connectport –从listenport转发到的tcp端口

案例

将一个RDP服务(远程桌面协议)转发到任意端口,将进来的流量从3340端口转发到标准的RDP端口3389。

netsh interface portproxy add v4tov4  listenport=3340 listenaddress=10.1.1.110  connectport=3389 connectaddress=10.1.1.110

1) netstat -ano | findstr :3340来验证3340端口是否正在监听中,如果该命令没有返回任何信息,或者说通过netsh接口并没有实现端口转发的功能,那么需要查看下系统是否开启了iphlpsvc(ip Helper)服务。

2) tasklist | findstr 3340查看监听该端口的进程

3) 检查防火墙是否关闭,如果关闭则跳过。如果打开需要手工配置相应的防火墙。

连接时请确保防火墙(Windows防火墙或者其他的第三方防护软件)允许外部连接到一个全新的端口,如果不允许,那么只能自行添加一个新的Windows防火墙规则,命令如下:netsh advfirewall firewall add rule name=”forwarded_RDPport_3340”  protocol=TCP dir=in localip=10.1.1.110  localport=3340 action=allow

当通过Windows防火墙接口为3340端口建立一个新的规则时,这个端口需要保证没有被任何程序占用,也就是说此端口仅供网络驱动使用。你可以创立任意的Windows端口转发规则,所有的netsh接口下的端口代理规则都是永久的,并且储存在系统中(不受开机重启的影响)。

查看系统中的所有转发规则是否生效:

netsh interface portproxy show all

查看端口转发的设置:netsh interface portproxy dump

删掉一个特定的端口转发规则:

netsh interface portproxy delete v4tov4

清空当前所有的配置规则:

netsh interface portproxy reset

从远程主机来尝试连接这个新转发的端口3340,3340端口等同于原来的3389端口,连接的地址为10.10.1.110:3340。

注意:这些转发规则仅仅适用于TCP端口,对于UDP的端口转发,使用上面的方法是无效的。在配置规则时,不能将127.0.0.1作为连接地址。

其他端口转发招式

1) 通过一台windows机器端口转发到一台windows远程主机

netsh interface portproxy add v4tov4  listenport=3340 listenaddress=10.1.1.110  connectport=3389  connectaddress=10.1.1.110

netsh interface portproxy add v4tov4 listenport=3389 listenaddress=0.0.0.0 connectport=3389 connectaddress=192.168.100.101

2) 通过一台ipv4的Windows机器转发到一台ipv6的服务器。

netsh interface portproxy add v4tov6  listenport=3340 listenaddress=10.1.1.110  connectport=3389  connectaddress=ffff::66

FAQ

1) 在Windows Server 2012 R2中,端口转发规则有可能会在系统重启后被重置,在这种情况下,需要在网络协议中检查是否存在配置不当,导致网络的间断性断开,或者当系统重启时是否出现了ip地址的变换(推荐使用静态ip)。在一个工作组里,通常是在windows任务计划程序里添加了一个实现端口转发的脚本。

2) 在Windows 2003/XP中,必须在注册表(HKLM\SYSTEM\ControlSet001\Services\Tcpip\Parameters)中找到并设置IPEnableRouter参数为1才能实现端口转发。

3) 防火墙规则检查

4) 端口转发依赖的ip helper服务是否启动

5) 端口转发是否生效

Windows日志查看

6273 网络策略服务器拒绝用户访问。

6274 网络策略服务器放弃用户的请求。

4868 证书管理器拒绝了挂起的证书请求。

4870 证书服务吊销了证书。

4944 当启动 Windows 防火墙时,以下策略处于活动状态。

4945 当启动 Windows 防火墙已列出规则。

4946 Windows 防火墙例外列表已更改。添加的规则。

4947 Windows 防火墙例外列表已更改。修改规则的。

4948 Windows 防火墙例外列表已更改。规则已被删除。

4949 Windows 防火墙设置都恢复为默认值。

4950 更改 Windows 防火墙设置。

4951 Windows 防火墙忽略规则,因为无法识别的主要版本号。

4952 Windows 防火墙忽略规则的部分,因为无法识别它的次要版本号。将强制执行该规则的其他部分。

4953 由于无法分析,Windows 防火墙将忽略规则。

4954 Windows 防火墙组策略设置已更改,并且未应用新设置。

4956 Windows 防火墙更改活动配置文件。

5024 Windows 防火墙服务已成功启动。

5025 Windows 防火墙服务已停止。

5027 Windows 防火墙服务无法从本地存储区中检索的安全策略。Windows 防火墙将继续执行当前的策略。

5028 Windows 防火墙无法分析新的安全策略。Windows 防火墙将继续执行当前的策略。

现象描述

日志中出现报错信息 “fork:Cannot allocate memory”。如下图所示:

可能原因

可能是进程数超限导致。系统内部的总进程数达到了 pid_max 时,再创建新进程时会报 “fork:Cannot allocate memory” 错。

解决思路

  1. 参考 处理步骤,查看实例内存使用率是否过高。
  2. 核实总进程数是否超限,并修改总进程数 pid_max 配置。

处理步骤

1、执行以下命令,查看系统 pid_max 值。

sysctl  -a | grep pid_max

根据返回结果,进行对应操作:

1.1、返回结果如下图所示,pid_max 默认值为32768,请执行下一步。

1.2、返回报错信息 “fork:Cannot allocate memory”,则需执行以下命令,临时调大 pid_max

echo 42768 > /proc/sys/kernel/pid_max

您可再次执行命令,查看系统 pid_max 值。

2、执行以下命令,查看系统内部总进程数。

pstree -p | wc -l

若总进程数达到了 pid_max,则系统在创建新进程时会报 “fork Cannot allocate memory” 错。

您可执行 ps -efL 命令,定位启动进程较多的程序。

3、将 /etc/sysctl.conf 配置文件中的 kernel.pid_max 值修改为65535,以增加进程数。修改完成后如下图所示:

4、执行以下命令,使配置立即生效。

sysctl -p

现象描述

Linux 云服务器在内存使用率未占满的情况下触发了 OOM(Out Of Memory)。如下图所示:

可能原因

可能是由系统可用内存低于 min_free_kbytes 值导致。min_free_kbytes 值表示强制 Linux 系统最低保留的空闲内存(Kbytes),如果系统可用内存低于设定的 min_free_kbytes 值,则默认系统启动 oom-killer 或强制重启。具体行为由内核参数 vm.panic_on_oom 值决定:

  • 若 vm.panic_on_oom=0,则系统会提示 OOM,并启动 oom-killer 杀掉占用最高内存的进程。
  • 若 vm.panic_on_oom =1,则系统会自动重启。

解决思路

  1. 参考处理步骤进行排查,查看实例内存使用率是否过高及总进程数是否受限。
  2. 核实 min_free_kbytes 值设置,并修改为正确配置。

处理步骤

1、登录云服务器,执行以下命令查看 min_free_kbytes 值

sysctl -a | grep min_free

min_free_kbytes 值单位为 kbytes,下图所示 min_free_kbytes = 1024000 即为1GB。

2、执行以下命令,使用 VIM 编辑器打开 /etc/sysctl.conf 配置文件。

vim /etc/sysctl.conf

3、按 i 进入编辑模式,修改 vm.min_free_kbytes 配置项。若该配置项不存在,则直接在配置文件中增加即可。

*建议修改 vm.min_free_kbytes 值为不超过总内存的1%即可。

4、按 Esc 并输入 :wq 后,按 Enter 保存并退出 VIM 编辑器。

5、执行以下命令,使配置生效即可。

sysctl -p

Version 22H2 (OS build 22621)

2022-10-1122621.674KB5018427
2022-09-3022621.608KB5017389
2022-09-2722621.525KB5019311
2022-09-2022621.521

1、使用人脸、指纹或 PIN 的Windows Hello可能会受到影响。

已通过KB5017389解决。

2、某些打印机可能只能进行默认设置,一些功能可能不可用,例如颜色、双面打印或更高分辨率。

已缓解。

微软正在努力研究解决方案,将在即将发布的版本中提供更新。

3、使用预配包,可能无法按预期工作。 

如果可以在升级到 Windows 11 版本 22H2 之前预配 Windows 设备,将不会有此问题。

微软正在调查此情况。

4、具有受影响的 Intel SST 驱动程序的 Windows 11 设备可能会收到蓝屏错误。

可以将 Intel® Smart Sound Technology 驱动程序更新为版本 10.30.00.5714 及更高版本或10.29.00.5714 及更高版本来解决此问题。(版本指的是最后一部分数字)

5、使用组策略首选项复制文件/快捷方式可能无法按预期工作。

微软正在努力研究解决方案,将在即将发布的版本中提供更新。

6、安装 KB5012170 时可能收到0x800f0922错误。

微软目前正在调查,将在即将发布的版本中提供更新。

9月20日微软发布了Windows11 2022(22H2)更新,这是Windows11的新版本。

Windows11使用了微软完善的更新系统和流程。当数据显示设备准备就绪时将分阶段逐步获得更新。

如果检测到设备可能有问题,比如应用程序不兼容,可能会采取保护措施,在问题解决之前不提供更新。

若是还没有收到该更新,建议等待后续更新推送。

若是仍然希望马上安装Windows11 22H2,

可以安装电脑健康状况检查应用,并运行后确认是否满足要求。

若是满足要求,再运行Windows11安装助手

*How to get the Windows 11 2022 Update | Windows Experience Blog

VirtualBox 是一款功能强大的虚拟机软件,它具有丰富的功能,性能也很优异,适用服务器、桌面和嵌入式等场景。除了适合企业,也适合家庭使用。VirtualBox 也是目前唯一一个开源的专业虚拟化解决方案。虚拟机的配置设置完全存储在 XML 中,并且独立于本地机器。因此虚拟机的相关设置可以很容易地移植到其他计算机上

目前 VirtualBox 7.0 发布了,上一个大型功能版本还是发布于 2018 年的 VirtualBox 6.0 。VirtualBox 7.0 带来最大的改动是允许虚拟机完全加密运行 —— 包括配置日志和保存状态的加密。但对于 VirtualBox 7.0,这种 VM 加密支持仅支持命令行界面。

此外,该版本还引入了一个基于 DirectX 11 的 3D 堆栈, Oracle 表示正在将 DXVK (尤其是 DXVK-Native)用于非 Windows 主机,意味着 Direct3D 堆栈最终在 Vulkan 上运行。

其他更改项目如下:

  • OCI:可以将云虚拟机添加到 Virtual Machine Manager ,并作为本地 VM 进行控制
  • OCI:可以通过网络管理器工具配置云网络,其方式与主机和 NAT 网络相同
  • 多项 GUI 的优化
  • 录音:使用 Vorbis 作为 WebM 容器的默认音频格式。
  • 音频:添加了 “默认” 主机驱动程序类型,可以在不同平台之间移动虚拟机(设备),无需显式更改音频驱动程序。
  • 来宾控制:实现了对 Linux 来宾 “添加自动更新” 的初始支持
  • 访客控制:通过 VBoxManage 更新添加访客时,实现等待和 / 或重新启动访客的功能
  • VBoxManage:添加了访客控制 “waitrunlevel” 子命令,可以等待访客达到某个运行级别
  • Windows 主机:添加了在会话 0 中运行自动启动 VM 的实验性支持,允许在用户未登录时运行 VMS(默认禁用,请参阅手册)
  • macOS 主机:删除了所有内核扩展。
  • macOS 主机:为具有 Apple 芯片 CPU 的系统提供开发者预览包。
  • Linux 访客:重新设计访客屏幕大小调整功能,添加了与一些访客桌面环境的基本集成
  • 设备:基于 DirectX 11(以及非 Windows 主机上的 DXVK)实现了新的 3D 支持
  • 设备:添加了虚拟 IOMMU 设备(Intel 和 AMD 变体)
  • 设备:添加了虚拟 TPM 1.2 和 2.0 设备
  • 设备:EHCI 和 XHCI USB 控制器设备现已作为开源基础包的一部分
  • EFI:增加了对安全启动的支持
  • 调试:添加了通过 GDB 对来宾调试的实验性支持,以及通过 KD/WinDbg 对来宾调试的高度实验性支持

更新公告:https://www.virtualbox.org/wiki/Changelog-7.0

nginx 发现警告日志提示:

WARNING: [pool www] server reached pm.max_children setting (5), consider raising it

原因是max_children设置太小了。

php-fpm.conf有两个至关重要的参数:

一个是”max_children”,另一个是”request_terminate_timeout”。

pm.max_children 表示 php-fpm 能启动的子进程的最大数量。

request_terminate_timeout 表示将执行时间太长的进程直接终止。

我的两个设置的值一个是”40″,一个是”900″,但是这个值不是通用的,而是需要自己计算的。

一、pm.max_children 多大合适?

这个值原则上是越大越好,php-cgi的进程多了就会处理的很快,排队的请求就会很少。

设置”max_children” 也需要根据服务器的性能进行设定。

计算方式如下:

一般来说一台服务器正常情况下每一个php-cgi所耗费的内存在20M~30M左右,因此我的”max_children”我设置成40个,20M*40=800M也就是说在峰值的时候所有PHP-CGI所耗内存在800M以内,低于我的有效内存2Gb。

而如果我 的”max_children”设置的较小,比如5-10个,那么php-cgi就会“很累“,处理速度也很慢,等待的时间也较长,占用的CPU也很高。

如果长时间没有得到处理的请求就会出现 504 Gateway Time-out 这个错误,而正在处理的很累的那几个php-cgi如果遇到了问题就会出现 502 Bad gateway 这个错误。

max_children较好的设置方式根据req/s(吞吐率,单位时间里服务器处理的最大请求数,单位req/s)来设置,若程序是 100 req/s 的处理能力,那么就设置 100比较好,这是动态来调整的。

二、request_terminate_timeout 多大合适?

计算方式如下:

如果你的服务器性能足够好,且宽带资源足够充足,PHP脚本没有循环或BUG的话你可以直接将”request_terminate_timeout”设 置成0s。0s的含义是让PHP-CGI一直执行下去而没有时间限制。

而如果你做不到这一点,也就是说你的PHP-CGI可能出现某个BUG,或者你的宽带不够充足或者其他的原因导致你的PHP-CGI能够假死那么就建议你给”request_terminate_timeout”赋一个值,这个值可以根 据你服务器的性能进行设定。

一般来说性能越好你可以设置越高,20分钟-30分钟都可以。由于我的服务器PHP脚本需要长时间运行,有的可能会超过10分钟因此我设置了900秒,这样不会导致PHP-CGI死掉而出现502 Bad gateway这个错误。

解码:

    public static string UnBase64String(string value)
    {
        if (value == null || value == "")
        {
            return "";
        }
        byte[] bytes = Convert.FromBase64String(value);
        return Encoding.UTF8.GetString(bytes);
    }

编码:

    public static string ToBase64String(string value)
    {
        if (value == null || value == "")
        {
            return "";
        }
        byte[] bytes = Encoding.UTF8.GetBytes(value);
        return Convert.ToBase64String(bytes);
    }

一:背景

昨天在 github 上准备找找 C# 9 又有哪些新语法糖可以试用,不觉在一个文档上看到一个很奇怪的写法: foreach (var item in myArray[0..5]) 哈哈,熟悉又陌生,玩过python的朋友对这个 [0..5] 太熟悉不过了,居然在 C# 中也遇到了,开心哈,看了下是 C# 8 的新语法,讽刺讽刺,8 都没玩熟就搞 9 了,我的探索欲比较强,总想看看这玩意底层是由什么支撑的。

二:.. 语法糖的用法

从前面介绍的 myArray[0..5] 语义上也能看出,这是一个切分array的操作,那到底有几种切分方式呢? 下面一个一个来介绍,为了方便演示,我先定义一个数组,代码如下:


var myarr = new string[] { "10", "20", "30", "40", "50", "60", "70", "80", "90", "100" };

1. 提取 arr 前3个元素

如果用 linq 的话,可以用 Take(3),用切片操作的话就是 [0..3], 代码如下:


        static void Main(string[] args)
        {
            var myarr = new string[] { "10", "20", "30", "40", "50", "60", "70", "80", "90", "100" };

            //1. 获取数组 前3个元素
            var query1 = myarr[0..3];

            var query2 = myarr.Take(3).ToList();

            Console.WriteLine($"query1={string.Join(",", query1)}");
            Console.WriteLine($"query2={string.Join(",", query2)}");
        }

2. 提取 arr 最后三个元素

这个怎么提取呢?在 python 中直接用 -3 表示就可以了,在C# 中需要用 ^ 来表示从末尾开始,代码如下:


        static void Main(string[] args)
        {
            var myarr = new string[] { "10", "20", "30", "40", "50", "60", "70", "80", "90", "100" };

            //1. 获取数组 最后3个元素
            var query1 = myarr[^3..];

            var query2 = myarr.Skip(myarr.Length - 3).ToList();

            Console.WriteLine($"query1={string.Join(",", query1)}");
            Console.WriteLine($"query2={string.Join(",", query2)}");
        }

3. 提取 array 中index = 4,5,6 的三个位置元素

用 linq 的话,就需要使用 Skip + Take 双组合,如果用切片操作的话就太简单了。。。


        static void Main(string[] args)
        {
            var myarr = new string[] { "10", "20", "30", "40", "50", "60", "70", "80", "90", "100" };

            //1. 获取数组 中 index=4,5,6 三个位置的元素
            var query1 = myarr[4..7];

            var query2 = myarr.Skip(4).Take(3).ToList();

            Console.WriteLine($"query1={string.Join(",", query1)}");
            Console.WriteLine($"query2={string.Join(",", query2)}");
        }

从上面的切割区间 [4..7] 的输出结果来看,这是一个 左闭右开 的区间,所以要特别注意一下。

4. 获取 array 中倒数第三和第二个元素

从要求上来看就是获取元素 80 和 90,如果你理解了前面的两个用法,我相信这个你会很快的写出来,代码如下:


        static void Main(string[] args)
        {
            var myarr = new string[] { "10", "20", "30", "40", "50", "60", "70", "80", "90", "100" };

            //1. 获取 array 中倒数第三和第二个元素
            var query1 = myarr[^3..^1];

            var query2 = myarr.Skip(myarr.Length - 3).Take(2).ToList();

            Console.WriteLine($"query1={string.Join(",", query1)}");
            Console.WriteLine($"query2={string.Join(",", query2)}");
        }

三. 探究原理

通过前面 4 个例子,我想大家都知道怎么玩了,接下来就是看看到底内部是用什么做支撑的,这里使用 DnSpy 去挖挖看。

1. 从 myarr[0..3] 看起

用 dnspy 反编译代码如下:


    //编译前
    var query1 = myarr[0..3];

    //编译后:
    string[] query = RuntimeHelpers.GetSubArray<string>(myarr, new Range(0, 3));

从编译后的代码可以看出,原来获取切片的 array 是调用 RuntimeHelpers.GetSubArray 得到了,然后我简化一下这个方法,代码如下:


        public static T[] GetSubArray<[Nullable(2)] T>(T[] array, Range range)
        {
            ValueTuple<int, int> offsetAndLength = range.GetOffsetAndLength(array.Length);
            int item = offsetAndLength.Item1;
            int item2 = offsetAndLength.Item2;
            T[] array3 = new T[item2];
            Buffer.Memmove<T>(Unsafe.As<byte, T>(array3.GetRawSzArrayData()), Unsafe.Add<T>(Unsafe.As<byte, T>(array.GetRawSzArrayData()), item), (ulong)item2);
            return array3;
        }

从上面代码可以看到,最后的 子array 是由 Buffer.Memmove 完成的,但是给 子array 的切割位置是由 GetOffsetAndLength 方法实现,继续追一下代码:


    public readonly struct Range : IEquatable<Range>
    {   
        public Index Start { get; }
        public Index End { get; }

        public Range(Index start, Index end)
        {
            this.Start = start;
            this.End = end;
        }

        public ValueTuple<int, int> GetOffsetAndLength(int length)
        {
            Index start = this.Start;
            int num;
            if (start.IsFromEnd)
            {
                num = length - start.Value;
            }
            else
            {
                num = start.Value;
            }
            Index end = this.End;
            int num2;
            if (end.IsFromEnd)
            {
                num2 = length - end.Value;
            }
            else
            {
                num2 = end.Value;
            }
            return new ValueTuple<int, int>(num, num2 - num);
        }
    }

看完上面的代码,你可能有两点疑惑:

1) start.IsFromEnd 和 end.IsFromEnd 是什么意思。

其实看完上面代码逻辑,你就明白了,IsFromEnd 表示起始点是从左开始还是从右边开始,就这么简单。

2) 我并没有看到 start.IsFromEnd 和 end.IsFromEnd 是怎么赋上值的。

在 Index 类的构造函数中,取决于上一层怎么去 new Index 的时候塞入的 true 或者 false,如下代码:

这个例子的流程大概是: new Range(1,3) -> operator Index(int value) -> FromStart(value) -> new Index(value) ,可以看到最后在 new 的时候并没有对可选参数赋值。

2. 探究 myarr[^3..]

刚才的例子是没有对可选参数赋值,那看看本例是不是 new Index 的时候赋值了?


//编译前:
var query1 = myarr[^3..];

//编译后:
string[] query = RuntimeHelpers.GetSubArray<string>(myarr, Range.StartAt(new Index(3, true)));

看到没有,这一次 new Index 的时候,给了 IsFromEnd = true , 表示从末尾开始计算,大家再结合刚才的 GetOffsetAndLength 方法,我想这逻辑你应该理顺了吧。

四:总结
总的来说这个切片操作太实用了,作用于 arr 可以大幅度减少对 skip & take 的使用,作用于 string 也可以大幅减少 SubString 的使用,如:”12345″[1..3] -> “12345”.Substring(1, 2),嘿嘿,厉害了吧! 还是C# 大法🐂👃