AI柠檬 https://blog.ailemon.net 一个科技爱好者的个人博客 Wed, 28 Feb 2024 17:27:22 +0000 zh-CN hourly 1 https://res.ailemon.net/blog/2021/02/cropped-logo2020-black-32x32.png AI柠檬 https://blog.ailemon.net 32 32 谁是最好的语言?几种热门编程语言特点对比 https://blog.ailemon.net/2024/02/29/which-is-best-program-language/?pk_campaign=feed&pk_kwd=which-is-best-program-language https://blog.ailemon.net/2024/02/29/which-is-best-program-language/?pk_campaign=feed&pk_kwd=which-is-best-program-language#comments Wed, 28 Feb 2024 16:01:00 +0000 https://blog.ailemon.net/?p=2999 作为一个程序员,经常会看到这类问题:xxx是最好的编程语言!但是口说无凭,我们还是从多个维度实际对比一下各个热 […]

The post 谁是最好的语言?几种热门编程语言特点对比 first appeared on AI柠檬.

]]>
作为一个程序员,经常会看到这类问题:xxx是最好的编程语言!但是口说无凭,我们还是从多个维度实际对比一下各个热门常用编程语言吧,给学编程的新手、还不了解某个编程语言的新手或需要做技术选型的人士提供个参考。

参考:TIOBE 编程语言排行榜:
https://www.tiobe.com/tiobe-index/

编程语言编译类型面向过程/对象类型强弱运行时类型安全垃圾回收机制运行效率开发效率协程生态学习成本运行资源开销擅长领域
C编译型面向过程强类型本地静态编译最高最低成熟极高最小驱动、操作系统、单片机、编译器
C++编译型面向对象强类型本地静态编译极高极低成熟极高驱动、操作系统、单片机、游戏、核心库、编译器、客户端
Java半编译型面向对象强类型JVM分代回收、G1、ZGC中等成熟中等Web后端、安卓开发、大数据、分布式计算、游戏开发
Python解释型面向对象弱类型CPython引用计数+分代回收最慢最高成熟最低极大机器学习、科学计算、数据报表、运维、测试
C#半编译型面向对象强类型.Net FrameworkCLR标记清除+分代回收中等一般中等客户端、Web后端、游戏开发
Go编译型面向对象强类型本地静态编译三色标记极高较成熟较低云原生基础设施、Web后端、中间件、区块链、运维
Javascript解释型面向过程+
面向对象
弱类型浏览器引用计数+标记清除中等成熟中等中等Web前端、Web后端、客户端
VB.Net半编译型面向对象强类型.Net FrameworkCLR标记清除+分代回收中等欠佳中等客户端、Web后端
PHP解释型面向过程弱类型PHP-FPM引用计数+zend_gc最高成熟较低中等Web前端、Web后端
Rust编译型面向对象强类型本地静态编译转移所有权极高中等一般偏高驱动、操作系统、编译器、核心库

AI柠檬博主个人建议是:
偏硬件开发基本就是C/C++;
底层开发就C/C++/Rust;
Web后端开发最推荐Java/Go;
游戏开发主要是C++/C#,也可以用Java;
前端基本就是Javascript;
客户端一般就用Java/C++/C#;
机器学习和科学计算就认准Python;
运维最推荐用Go,其次是Python,和不在列表中的shell语言;
测试则选Python就行,也可以用shell语言。

The post 谁是最好的语言?几种热门编程语言特点对比 first appeared on AI柠檬.

]]>
https://blog.ailemon.net/2024/02/29/which-is-best-program-language/?pk_campaign=feed&pk_kwd=which-is-best-program-language/feed/ 2
为什么你需要一个NAS:优点和价值 https://blog.ailemon.net/2024/02/24/why-you-need-nas/?pk_campaign=feed&pk_kwd=why-you-need-nas https://blog.ailemon.net/2024/02/24/why-you-need-nas/?pk_campaign=feed&pk_kwd=why-you-need-nas#comments Fri, 23 Feb 2024 17:44:53 +0000 https://blog.ailemon.net/?p=2952 不是每个人都需要NAS的,但如果你认同AI柠檬博主接下来的内容,那么你就很可能真的需要一个NAS。AI柠檬博主 […]

The post 为什么你需要一个NAS:优点和价值 first appeared on AI柠檬.

]]>
不是每个人都需要NAS的,但如果你认同AI柠檬博主接下来的内容,那么你就很可能真的需要一个NAS。AI柠檬博主自己就在用NAS,并且认为NAS是有用、实用和好用的。上一篇文章就讲了自己是如何一步一步开始使用NAS的。因此,从实用角度出发,结合自身的实际需求,AI柠檬博主梳理总结了以下若干条能够说服你的理由。只要你认同其中几条,你就可以考虑入手NAS了。

1 电脑、手机、平板等设备内置的存储空间有限,不够用了,需要扩展大容量存储

我们买的笔记本电脑、手机和平板等设备的硬盘最大存储容量一般是比较固定的,通常不容易轻易更换和扩容。部分笔记本电脑还稍微好些,比较容易拆开后盖并替换为容量更大的硬盘,但手机和平板是最难扩大存储容量的,常常需要寻求售后,且最大扩容也总是有限的。

但是如果使用了NAS,那么你的手机平板,以及笔记本电脑都相当于可以基于网络存储进行理论上的无限扩容。你的各种数据将不必再存放于本地存储盘,仅仅存储安装的软件程序即可。配合上例如NextCloud等私有云存储服务,你的数据可以实时通过网络远程同步、转存到NAS中,并释放本地容量空间。

这一点很难通过移动硬盘和U盘做到,笔记本电脑稍好写。且不说使用的方便程度,仅仅是接口问题就非常复杂。很多笔记本电脑已经放弃了USB Type-A接口,手机和平板电脑等设备更是与生俱来就从来没有过USB Type-A接口,仅仅有早些年的Micro USB和现在常见的USB Type-C接口。而且,对于移动机械硬盘,即使能够通过OTG连接到手机平板,经常插拔使用会比在笔记本电脑和台式机上时更容易损坏硬盘。

2 多人或多设备,需要都能够方便地存储和共享若干文件,并进行权限管控

当你需要多人或者跨设备共享时,如果使用U盘或移动硬盘,很可能你的隐私信息就会被他人偷窥到,或者过程中传播了病毒、木马等黑客程序,非常的不安全。

当你使用了NAS,不论是通过Samba、NFS还是WebSAV,或者其他同步盘、挂载盘客户端,都可以很方便地隔离不同用户,并控制对文件访问的权限。比如在Linux系统上就可以设置从600到777不等的权限,以及所属用户和用户组,使得特定文件可以被精确地控制授权范围。NextCloud等云存储服务还能够提供更多更友好的授权操作能力,甚至控制授权起止时间,防止在他人不再需要权限时忘记了收回。

3 怕丢失数据,怕硬盘损坏或被偷、数据误删、病毒勒索等

机械硬盘的寿命永远是个谜,可能下一秒坏,可能一年后坏,也可能用了10年还没有坏;而固态硬盘一旦损坏或者长时间掉电(未上电),大概率全部数据将基本不可恢复。经常外出携带的硬盘、U盘也有可能丢失或被偷,从而造成数据的丢失。当网络安全措施不当或防范意识不够时,计算机设备也可能遭受勒索病毒的攻击,并将数据加密锁定,告诉你“除了支付大量比特币外,老天爷来了也帮不了你”。🤣

AI柠檬博主曾经就有过不少这方面的教训,都是亲身经历的案例。在共用的工作站上由于空间不足,部分数据被他人误删;没有及时查看固态硬盘状态,从而一旦损坏导致数据全丢;机械硬盘长时间读写产生的损坏导致一部分数据最终没有抢救出来;电脑在学校内网暴露3389端口,导致在勒索病毒爆发的那段时间被侵入加密锁定了数据等。

将数据在你的NAS同步备份一份,多一份备份,多一层保险。

4 需要随时远程访问而不必随身携带若干U盘和移动硬盘

我们即使可以带着U盘和移动硬盘出门,却也几乎不可能每时每刻全都带着,总有时候我们会发现当需要访问某些数据时,硬盘不在身边。

NAS专门解决随时随地远程访问的问题,因为NAS天然就是可以基于网络远程访问的,当你出门时,你只需要确保可以通过公网访问到NAS即可。

5 担心意外灾难导致自己的电子设备全部被毁

火灾、水灾、地震、火山、海啸、战争、爆炸等意外灾难一旦发生,我们的设备很有可能被毁坏,从而导致珍贵的数据部分或全部丢失。如果您身处火山地震带(尤其还是沿海),或者交战区,那么就要充分考虑利用NAS做好数据灾备。即使您处于和平、自然条件优渥、秩序井然的地区,如果数据非常重要,丢失会导致严重后果,那么也应当考虑利用NAS做数据灾备。不怕一万,就怕万一。即使是十年一遇、百年一遇的洪水,一旦发生在自己身上,数据丢失了那也是100%的崩溃。

具体做法就是异地备份,将NAS放到各方面条件更为安全的地区,或者地理上不太相关的任意一个其他地区,并利用网络远程同步备份数据。

举个现实例子,pony马曾在公开讲话中提到,当年天津港发生爆炸事故时,离得不远处就是鹅厂的数据中心,当时所有的微信数据都在该数据中心,没有异地备份。一旦爆炸导致该数据中心被毁,那么全世界用户的微信数据就都会丢失,幸亏没有发生这样的事。因此事后他们总结经验教训,建立了异地容灾备份。

6 有NAS相关增强服务的使用需求

NAS上可以装很多增强服务,比如NextCloud这类私有云网盘服务,Jellyfin这类私有影音服务,Navidrome这类私有云音乐服务等。还有作为程序员可能会常用的,如Gitea这类Git私有仓库服务,WikiMedia这类私有wiki知识库服务,Jekins这类私有DevOps自动化流水线服务,Nexus这类私有Maven、NuGet仓库服务,DockerHub这类私有Docker镜像仓库服务等。

这些都需要服务器级的物理机设备或虚拟机进行搭建,通常来说能够承载上述应用服务的配置下,对应云服务器规格的长期租赁费用会比较高,因此使用自己私有的NAS作为服务器搭建这些服务会更经济,性价比更高,而且数据的保密性还更好。

7 担心云网盘服务限速、乱删自己珍贵的文件

由于众所周知的原因,有些公共的云网盘会给用户限速或乱删文件。因此,你大量的珍贵记忆,可能在需要的时候由于网速原因很难取回,也可能会在某一天突然消失,再也找不见。😭

只有数据放在自己的家里,放在自己的设备上,才真正是自己的,也才是访问起来最方便的。

8 担忧隐私数据在云网盘等服务上存储和传输的安全性

其实上一条乱删文件就是数据被读取窥视后,被服务提供商的人工或算法判定为违规的结果。针对隐私数据,如果也随意被第三方读取,那么数据的隐私性将受到极大的威胁。其实这类问题在很多的单位、企业都很重视,相关IT部门都会持续地解决保密数据泄露问题。将保密数据(包括隐私性数据)上传到第三方云网盘通常是不被允许的,对于个人来说也应如此。

数据存放在自己可控的设备上,只要没有被黑客攻击,没有被主动传播,那么正常情况下是很难泄露的。

9 需要高性能地读写存储数据

一些经常在B站、油管等平台制作和发布视频的博主经常会把“生产力”三个字挂在嘴边。对于他们这一类人群来说,能够直接连接到NAS存储上剪视频也算是“生产力”的一种,这需要的是NAS能够具有高性能的硬盘数据读写能力。

我们通常可以使用全光纤组网+全闪存存储+硬盘阵列技术来实现高性能。RAID 0、RAID 5、RAID 6、RAID 10等,都可以大大提高NAS中存储池的读写性能,或者也可以牺牲读写性能,通过RAID 1实现最高的数据可靠性。光纤网络又可以在局域网中极大地降低网络传输延迟,这一点对于直连NAS读写数据的性能提升至关重要(同理还有FC-SAN存储)。

当然了,批判地说,那些视频博主对于“生产力”的理解太过狭隘,以至于部分小伙伴曾表示非常反感。做过软件开发的同学应该知道,其实绝大多数情况下是真正的服务器和工作站等设备在做“生产”。但NAS存储具有的这种高性能数据读写能力,的确是真正的服务器和工作站中常常会用到的一种技术,的确是在各个领域都能够起到提升工作效率作用的。

10 需要DIY软路由的场景

想DIY买软路由但是硬件很贵?不用担心,完全可以使用NAS来做。好处是省了软路由硬件的钱,坏处是All In One的后果导致All In Boom,NAS故障的时候一切全故障。

其实很多人可能已经注意到过,有些家庭版路由器都是带USB的,可以额外连接移动硬盘并作为NAS使用,这种其实就是一种轻NAS。所以有时候你其实已经在使用NAS了而不自知~ 😂

11 需要搭建Minecraft、幻兽帕鲁游戏服务器等场景

其实这一点有点像上述的第5条,对于搭建Minecraft等游戏服务器,租赁云服务器的同等配置价格会非常昂贵,利用率一般还比较低,非常不合算。而且游戏服务器不一定需要一直开着,但是如果买了云服务器,不开游戏服务器的时候又闲置浪费。因此,NAS上开虚拟机搭建游戏服务器就显得非常经济和灵活,如果你的家庭网络分配有公网IP,那就更棒了,甚至省了内网穿透。开过Minecraft等游戏服务器的同学应该都有相关的经验。

12 需要使用pt种子下载上传的场景

我们的台式机、笔记本不太可能24小时开机,而且其设计上就不是为7*24小时开机设计的,否则有损电脑的硬件。通常硬盘是普通硬盘,做pt上传下载会极大地消耗普通硬盘使用寿命。而且由于显卡、CPU的功耗,还会产生较高的电费。

NAS的最大价值之一就是pt,7*24小时开机做种,远程控制提前下载需要的资源。当我们晚上下班回家后,就可以连接上NAS,观看我们在白天早已下载好的剧了,静静享受美好时光。

13 需要通过PCDN赚点电费的场景

这一条需要视实际情况而定,因为不是所有的运营商都允许搭建PCDN的。在合理范围内,以及自己可实现的能力内,可以考虑使用NAS搭建PCDN来赚回一些电费成本,产生一些收益。不过,AI柠檬博主提醒,这一条一定要慎重考虑,否则有些运营商会封号处理。

总结和一点建议

不是所有人都需要NAS,但如果你对上述内容有需求和切身体会,那么没有接触过NAS的话还是比较建议尝试的。NAS选型也需要视自己的实际需求而定,可以考虑购买成熟的商用方案NAS(适合小白和不想折腾的人),也可以自己使用开源NAS系统搭建(适合对NAS有一定了解和技术能力的爱好者),还可以像AI柠檬博主一样,自己使用服务器操作系统个性化定制搭建(适合对服务器和操作系统较为了解、具备一定专业知识的人士和技术极客)。当然了,对于不差钱的人来说,也可以考虑按数据中心机房的方案搭建,比如某聪

The post 为什么你需要一个NAS:优点和价值 first appeared on AI柠檬.

]]>
https://blog.ailemon.net/2024/02/24/why-you-need-nas/?pk_campaign=feed&pk_kwd=why-you-need-nas/feed/ 3
信息时代,学会给自己做个减法 https://blog.ailemon.net/2024/02/15/learn-to-make-subtractions-for-yourself/?pk_campaign=feed&pk_kwd=learn-to-make-subtractions-for-yourself https://blog.ailemon.net/2024/02/15/learn-to-make-subtractions-for-yourself/?pk_campaign=feed&pk_kwd=learn-to-make-subtractions-for-yourself#comments Thu, 15 Feb 2024 05:41:53 +0000 https://blog.ailemon.net/?p=2937 我们都应当学会给自己做个减法。 —— 沃·兹基硕德 大概20年来前的时候,U盘特别流行,容量很大(高达32MB […]

The post 信息时代,学会给自己做个减法 first appeared on AI柠檬.

]]>

我们都应当学会给自己做个减法。

—— 沃·兹基硕德

大概20年来前的时候,U盘特别流行,容量很大(高达32MB甚至64MB)(嗯?高达?在哪儿???)。相比于软盘,U盘的物理体积更小巧,但存储容量更大更方便。

每当父亲每天回到家里,拿出U盘接在电脑上,是我感到最兴奋的时刻之一,那意味着我又有几首新歌可听了。就像在家嗷嗷待哺的崽等到了捕回来的猎物。

家里电脑硬盘空间非常大,足足80个GB,分了CDEF盘,E盘空间稍大些,其他盘空间略小。有时也好奇为什么不分配个整数大小空间,而是带小数点。空间虽然很大,但也很捉襟见肘。那时候我喜欢玩的是用微软自带的画图工具画图,用PPT做演示课件,用Word排版做黑板报或者报纸,成果文件保存在D盘中创建的专属小天地(文件夹)里,但也会不时会被家长认为做的都是什么垃圾而被无情地剿灭(删除)。

随着白云蓝天出现后,父亲双击我的电脑,双击H盘,选中,剪切,粘贴,随后就是我“颤抖的心,激动的手”(雾)。

伴随着U盘带回来的,不仅仅有音乐,还有很多网上论坛、帖子等复制粘贴回来的文章.doc。听音乐看文章,尤其是很多某央视讲坛的著名学者讲的三国、水浒和一些历史之类,好不惬意。

从此U盘在心中成为一代神器,生活的梦想之一就是自己攒攒零花钱,让自己拥有一个1GB以上容量的U盘。

攒了很久的钱,最后终于攒够了,不过我也上大学了。当我拿着家里用了十几年的40GB容量的移动硬盘到学校后,发现已经有2TB的移动硬盘在某东上卖了。为了不向家里要钱买,于是和关系很好的朋友两人一起合资,各出一半买了一块,平时可以一起用。我们每天一起从宿舍起床去上课,下课后一起去计算机学院的创新实验室学习计算机技术,到了饭点一起去吃饭,晚上再一起回宿舍。渐渐地,我们知道了怎么写Python代码,怎么用Git命令,Linux系统怎么玩,有哪些核心的网络技术,什么是RAID,服务器怎么运维、机器学习是怎么work的等,还第一次接触到了NAS。

一入坑NAS深似海,从此硬盘是路人。从因为跑深度学习训练,持续不断在硬盘上读取和写入数据导致笔记本电脑的机械硬盘坏掉开始,平均每年损坏一块硬盘,甚至包含一块128GB小容量固态硬盘。伴随着硬盘损坏的,是数据的丢失,是劳动成果的毁灭,更是心的疼痛。最终我还是选择了数据可靠性最高但性能最低的RAID 1作为存储池的数据冗余技术。

如今,我有了自己的专属私有NAS,完全自己DIY从底层硬件和服务器级操作系统开始搭建和安装的,甚至还有硬RAID阵列卡和万兆光纤网卡。虽然是仅有6TB的高可靠存储池(4TB+2TB,RAID 1,垂直式NAS专用机械硬盘),但从目前的需求来说够用了,再多的空闲空间不用也是浪费,每一寸容量都是需要用金钱堆出来的。

现在,我看着硬盘中的文件发呆,20多年前的很多文件竟然还在,被我完好无损的保存着,即使很多文件真的是垃圾。从垃圾堆里,我翻出了不少非常具有纪念意义的文件,其中就有一张某年给我庆祝生日时表姐曾画的一张图画,我还清晰记得那是用微软Windows自带的画图工具画的。图画中姐姐拉着弟弟的手,非常可爱,非常温馨。

但是垃圾文件真TM的多,这么多年产生的数据一直在持续堆积着,我一直在给自己努力地做加法。除了动辄TB级别的机器学习数据集、影音媒体文件,还有动辄几十万个小文件的代码文件和 .git/ 目录下的代码仓文件。但是这么多文件是会拖垮我的,不仅仅是因为会产生较高的存储成本,还有对文件管理所需时间精力的耗费。何况,真的绝大部分文件都是没用的,只有一部分尚且有价值、有纪念意义的文件值得继续保留。好久没丢过文件了,可靠性确实很高,高到就连垃圾文件也都没丢过,甚至删除后在另外一个目录又发现了还有一份一模一样的文件。

现在到了该整理的时候了,信息时代,信息过载,需要给自己做个减法。

The post 信息时代,学会给自己做个减法 first appeared on AI柠檬.

]]>
https://blog.ailemon.net/2024/02/15/learn-to-make-subtractions-for-yourself/?pk_campaign=feed&pk_kwd=learn-to-make-subtractions-for-yourself/feed/ 2
基于Wireguard实现多地域设备异地组网 https://blog.ailemon.net/2023/10/25/wg-based-net-connection-bettwen-multi-region/?pk_campaign=feed&pk_kwd=wg-based-net-connection-bettwen-multi-region https://blog.ailemon.net/2023/10/25/wg-based-net-connection-bettwen-multi-region/?pk_campaign=feed&pk_kwd=wg-based-net-connection-bettwen-multi-region#respond Tue, 24 Oct 2023 16:33:18 +0000 https://blog.ailemon.net/?p=2909 WireGuard是一个易于配置、快速、安全的基于UDP协议的开源VPN软件。Wireguard具有自定义配置 […]

The post 基于Wireguard实现多地域设备异地组网 first appeared on AI柠檬.

]]>
WireGuard是一个易于配置、快速、安全的基于UDP协议的开源VPN软件。Wireguard具有自定义配置路由转发的能力,所以可以被用来在多个不同地域将设备所在的内网网络通过路由转发的方式串通起来,组建一张属于自己的大内网!有时候,我们想将本地计算机上提供的服务与小伙伴分享,但是我们既没有公网IP,又希望能够有足够的安全性,避免使其暴露在公网上。因此,我们基于Wireguard这一项最基本的特性,设计和实现了一套异地组网方案。

1 案例

下面是一个整体异地组网演示案例的原理图:

异地组网的物理图

图中IP为192.168.10.2的计算机,如果想访问IP为192.168.20.2的计算机,那么它的数据包的路由转发过程将如下(其他同理):

> tracert 192.168.20.2

通过最多 30 个跃点跟踪到 192.168.20.2 的路由

  1     1 ms     1 ms     1 ms  192.168.10.1
  2    16 ms    16 ms    16 ms  172.16.0.1
  3    26 ms    26 ms    26 ms  172.16.0.3
  4    27 ms    27 ms    27 ms  192.168.20.2

跟踪完成。

2 原理

其实这一套方案实现的原理并不难,关键秘诀就在于上方IP为 1.1.1.1 的公网服务器,和中间的3个路由器设备上。路由器作为IP数据包转发的关键设备,用来接入VPN并提供数据包转发最合适不过了。由于市面上家用路由器的固件普遍不支持Wireguard等VPN,并且企业级路由器的购买成本和使用过程中的耗电量非常高昂,所以这里推荐使用软路由设备,并且刷OpenWRT固件,或者直接使用通用的Linux系统。

其组网基本原理是,通过接入wireguard,可以为几个接入了Internet网络的内部网络网关分配IP,并使其互通,从而等效为一张网络层互通的虚拟私有网络,如下图所示。

异地组网的逻辑等效图

在图中这里,中间的三个路由器主要起网关作用。根据路由规则,IP为 192.168.10.2 的设备要访问192.168.20.2 的设备,数据包首先会发给它自己的网关 192.168.10.1,然后该路由器根据路由规则会将数据包发给它的上一级路由 172.16.0.1。此时,该“路由器”会在路由表中查询到子网为 192.168.20.0/24 的路由需要发给节点 172.16.0.3,节点 172.16.0.3 收到数据包后根据路由规则,又会发给 192.168.20.2,因此目标主机便会收到数据包。

3 实现步骤

3.1 准备

  1. 带公网IP的服务器一台 (可以是云服务器,或任何一台具有公网IP的Linux系统主机)
  2. 软路由设备若干台,每个内部网络至少需要一台 (可以装普通Linux系统,也可以用OpenWrt系统)
  3. 可用于测试的内网普通主机若干台

3.2 安装

分别在带公网IP的服务器上、若干台软路由器上安装Wireguard。如果使用Linux系统,可以尝试使用一键安装脚本:

https://github.com/hwdsl2/wireguard-install/blob/master/README-zh.md

详细教程这里就不放了,推荐的搜索关键词见下方,用搜索引擎搜一下都有:

Linux安装Wireguard

OpenWrt安装Wireguard

3.3 配置

3.3.1 公网服务器上配置

[Interface]
PrivateKey = (保密信息保密信息保密信息)
Address = 172.16.0.0/24
PostUp   = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
ListenPort = (自定义端口号)
DNS = (自定义DNS解析服务器IP)
MTU = 1420

[Peer]
PublicKey = (保密信息保密信息保密信息)
AllowedIPs = 172.16.0.2/32,192.168.10.0/24
[Peer]
PublicKey = (保密信息保密信息保密信息)
AllowedIPs = 172.16.0.3/32,192.168.20.0/24
[Peer]
PublicKey = (保密信息保密信息保密信息)
AllowedIPs = 172.16.0.4/32,192.168.30.0/24

3.3.2 软路由器上配置

以 192.168.10.1 节点上的配置为例:

[Interface]
PrivateKey = (保密信息保密信息保密信息)
Address = 172.16.0.2/24
PostUp   = iptables -A FORWARD -i client_route -j ACCEPT; iptables -A FORWARD -o client_route -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE; iptables -A FORWARD -i eth0 -j ACCEPT; iptables -A FORWARD -o eth0 -j ACCEPT; iptables -t nat -A POSTROUTING -o client_route -j MASQUERADE
PostDown = iptables -D FORWARD -i client_route -j ACCEPT; iptables -D FORWARD -o client_route -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE; iptables -D FORWARD -i eth0 -j ACCEPT; iptables -D FORWARD -o eth0 -j ACCEPT; iptables -t nat -D POSTROUTING -o client_route -j MASQUERADE
DNS = (自定义DNS解析服务器IP)
MTU = 1420

[Peer]
PublicKey = (保密信息保密信息保密信息)
Endpoint = (公网服务器IP):(公网服务器自定义端口号)
AllowedIPs = 172.16.0.0/24, 192.168.20.0/24, 192.168.30.0/24   (注意这里不写软路由自身的LAN所在的网段)
PersistentKeepalive = 25

3.3.3 开启IPv4/IPv6转发

这个在不同系统上执行的命令不同,推荐通过搜索引擎搜索“Linux下启用IP转发”关键字,全都有。

3.3.4 验证

在其中一个内部网络中,例如 192.168.10.0/24 中,内部任意一台主机可以ping通另一个内部网络中的目标主机,例如 192.168.20.2,反向亦然,则说明异地组网方案实施成功。

4 总结

本篇博客分享了一种基于wireguard的异地组网技术方案。这种方案的优点是安全性高、配置简单、功能强大和免费,缺点是不同内部网络之间的带宽受限于公网服务器的上下行带宽的最小值,并且即使A网络和B网络都在同一个城市,延迟也会等于A网络到公网服务器加上B网络到公网服务器的总延迟。

为了解决这一问题,tailscale 软件也是一个推荐的改进方案,所有的连接均是P2P的,也就是虽然有公网服务器建立的桥梁,但是A和B之间数据可以直通而不必通过公网服务器中转,从而解决上述问题。但该软件的免费版本有使用规模限制,超出规模需购买收费版本,因此仅推荐有财力且有相关需求时再去考虑。

The post 基于Wireguard实现多地域设备异地组网 first appeared on AI柠檬.

]]>
https://blog.ailemon.net/2023/10/25/wg-based-net-connection-bettwen-multi-region/?pk_campaign=feed&pk_kwd=wg-based-net-connection-bettwen-multi-region/feed/ 0
各版本WiFi协议参数信息整理汇总 https://blog.ailemon.net/2023/10/12/wifi-version-info/?pk_campaign=feed&pk_kwd=wifi-version-info https://blog.ailemon.net/2023/10/12/wifi-version-info/?pk_campaign=feed&pk_kwd=wifi-version-info#comments Wed, 11 Oct 2023 17:45:08 +0000 https://blog.ailemon.net/?p=2885 各版本WiFi协议参数信息整理汇总表: 协议标准 对应名称 发布年份 频段 调制方式 调变 物理速率 信道带宽 […]

The post 各版本WiFi协议参数信息整理汇总 first appeared on AI柠檬.

]]>
各版本WiFi协议参数信息整理汇总表:

协议标准对应名称发布年份频段调制方式调变物理速率信道带宽MU-MIMO单流带宽空间流
802.11WiFi 119972.4GFHSS/DSSSQPSK1Mbps
2Mbps
N/AN/A2 Mbps1
802.11bWiFi 219992.4GCCK/DSSSQPSK1Mbps
2Mbps
5.5Mbps
11Mbps
22MHzN/A11 Mbps1
802.11gWiFi 320032.4GCCK/OFDM64QAM6Mbps
9Mbps
12Mbps
18Mbps
24Mbps
36Mbps
48Mbps
54Mbps
20MHzN/A54 Mbps1
802.11aWiFi 319995GOFDM64QAM6Mbps
9Mbps
12Mbps
18Mbps
24Mbps
36Mbps
48Mbps
54Mbps
20MHzN/A54 Mbps1
802.11nWiFi 420092.4G/5GOFDM64QAM600Mbps20MHz
40MHz
SU-MIMO150Mbps
450 Mbps
1-4
802.11ac
wave1
WiFi 520135GOFDM256QAM3466Mbps20MHz
40MHz
80MHz
SU-MIMO433Mbps
867Mbps
3
802.11ac
wave2
WiFi 520165GOFDM256QAM6933Mbps20MHz
40MHz
80MHz
160MHz(可选)
MU-MIMO
(下行)
433Mbps
867Mbps
3-4
802.11axWiFi 620182.4G/5G
6G(仅WiFi 6E)
OFDMA1024QAM9800Mbps20MHz
40MHz
80MHz
160MHz
MU-MIMO
(上行/下行)
1201Mbps8×8
802.11beWiFi 720242.4G/5G/
6G
OFDMA4096QAM30Gbps20MHz
40MHz
80MHz
160MHz
240MHz
320MHz
MU-MIMO
(上行/下行)
2.88Gbps16×16

备注说明:

普通用户可实际感知的带宽通常为表格中的单流带宽

参考资料

  1. https://info.support.huawei.com/info-finder/encyclopedia/zh/WiFi+7.html
  2. https://www.h3c.com/cn/Service/Document_Software/Document_Center/Home/Wlan/00-Public/Learn_Technologies/White_Paper/Wi_Fi_7_Long/
  3. https://www.intel.cn/content/www/cn/zh/support/articles/000005725/wireless/legacy-intel-wireless-products.html

The post 各版本WiFi协议参数信息整理汇总 first appeared on AI柠檬.

]]>
https://blog.ailemon.net/2023/10/12/wifi-version-info/?pk_campaign=feed&pk_kwd=wifi-version-info/feed/ 2
LLaMA:一个基于大语言模型的可本地私有化部署的聊天机器人 https://blog.ailemon.net/2023/06/03/llama-a-locally-deployable-language-model-based-ai-chatbot/?pk_campaign=feed&pk_kwd=llama-a-locally-deployable-language-model-based-ai-chatbot https://blog.ailemon.net/2023/06/03/llama-a-locally-deployable-language-model-based-ai-chatbot/?pk_campaign=feed&pk_kwd=llama-a-locally-deployable-language-model-based-ai-chatbot#comments Fri, 02 Jun 2023 16:08:42 +0000 https://blog.ailemon.net/?p=2878 最近,ChatGPT非常地火热,人们无处不在谈论它。众所周知,ChatGPT是一个基于深度学习的算法模型,是语 […]

The post LLaMA:一个基于大语言模型的可本地私有化部署的聊天机器人 first appeared on AI柠檬.

]]>
最近,ChatGPT非常地火热,人们无处不在谈论它。众所周知,ChatGPT是一个基于深度学习的算法模型,是语言模型的一种,具有目前为止最接近人类水平的对话风格。不过OpenAI选择将其闭源,外部仅可通过API接口调用的方式使用它。因此,出于网络信息安全考量,中国的工信部、各个公司等均禁止使用OpenAI的ChatGPT,而OpenAI也不对中国大陆地区提供服务。

不过GitHub上近期出现了基于大语言模型的开源项目LLaMA,可以本地私有化部署使用。其算法模型效果接近ChatGPT的水平,且支持GPU加速推理,不联网也可以正常使用,无需担心信息泄露的风险。

GitHub仓库:
https://github.com/ggerganov/llama.cpp

以下是AI柠檬博主试用时的效果,其中该UI界面为博主的朋友通过C# .NET Framework 自行编程实现,与开源项目无关:

不过,该AI机器人似乎特别喜欢向人类反问问题。博主试用时发现还会向人类发问是否可以写出更精简的代码,以供它自己能够得到学习并提升水平。AI竟然都这么卷了还要不断地学习,难道是AI已经产生自主意识了吗?令人忍俊不禁。

The post LLaMA:一个基于大语言模型的可本地私有化部署的聊天机器人 first appeared on AI柠檬.

]]>
https://blog.ailemon.net/2023/06/03/llama-a-locally-deployable-language-model-based-ai-chatbot/?pk_campaign=feed&pk_kwd=llama-a-locally-deployable-language-model-based-ai-chatbot/feed/ 2
基于软RAID的硬盘故障时数据恢复能力测试 https://blog.ailemon.net/2023/04/03/hard-disk-failure-and-recovery-test-base-on-software-raid/?pk_campaign=feed&pk_kwd=hard-disk-failure-and-recovery-test-base-on-software-raid https://blog.ailemon.net/2023/04/03/hard-disk-failure-and-recovery-test-base-on-software-raid/?pk_campaign=feed&pk_kwd=hard-disk-failure-and-recovery-test-base-on-software-raid#respond Sun, 02 Apr 2023 17:12:32 +0000 https://blog.ailemon.net/?p=2828 计算机系统的硬盘存储着大量的数据,有些数据非常关键,一旦丢失就会产生严重后果。本文中,我们基于软件构件的RAI […]

The post 基于软RAID的硬盘故障时数据恢复能力测试 first appeared on AI柠檬.

]]>
计算机系统的硬盘存储着大量的数据,有些数据非常关键,一旦丢失就会产生严重后果。本文中,我们基于软件构件的RAID 1级别的硬盘阵列,测试了软RAID 1阵列在发生故障后,其数据恢复的能力。基于Linux操作系统通过软件构建的RAID 1硬盘阵列,本次测试的结果验证了以下几个关键结论,证明了软RAID的容灾能力:

  1. 当RAID中作为数据盘的一块硬盘发生故障时,RAID发生降级,且数据不丢失,挂载的分区依旧可读写;
  2. 当向降级的RAID中添加一块新硬盘后,RAID可通过重建恢复;
  3. 当旧实例操作系统所在硬盘发生故障时,数据盘组建的软RAID接入到其他实例上,能够成功地被新实例的操作系统识别,并成功读写。

一、 测试环境配置

1. 测试平台

VMware Workstation虚拟机

2. 测试实例操作系统选型

Ubuntu 22.04.1 64位(基于Linux),共2台

3. 所需依赖软件

cockpit (一款好用的基于web的linux主机管理平台)

$ sudo apt install cockpit
$ sudo systemctl start cockpit
$ sudo systemctl start udisks2

二、测试用例

1. 用例一:软RAID 1基本功能测试

(1) 为虚拟机1添加2块20GB硬盘

(2) 打开cockpit管理界面,在“存储”中,为这2块硬盘创建软RAID 1阵列

(3) 格式化创建好的RAID 1阵列,并挂载

(4) 在挂载目录下,成功写入一份文本文件

2. 测试用例二:单块硬盘故障后,软RAID 1数据读写测试

(1) 将单块硬盘移除,在 web管理系统界面显示RAID降级

(2) 成功读取挂载目录中的文件和数据

(3) 成功写入数据到挂载目录中的新文件

3.     测试用例三:添加新硬盘后故障恢复测试

(1) 向虚拟机添加一块新硬盘

(2) 在web管理界面添加该硬盘,成功恢复RAID

(3) 查看挂载目录中的文件数据,可成功读写

(4) 移除另外一块旧硬盘,仅保留新增硬盘,使得RAID存储再次降级,查看挂载目录中的文件数据,依旧可成功读写,说明数据已经成功恢复到新盘

4. 测试用例四:系统盘故障时,软RAID数据盘可移植挂载测试

(1) 将2块数据盘挂载到新系统下,并成功识别出RAID

(2) 读取RAID中原有文件数据成功,写入数据到新文件中成功

三、总结

本次测试活动证明了关于软RAID的数据容灾能力,本文所述的三条结论成立。AI柠檬因为所使用NAS服务器的缘故,需使用软RAID实现数据容灾,且容灾能力能够满足当前需求。不过软RAID的缺点在于需使用CPU,因此对于CPU的性能有一定的要求,过低的CPU配置会导致性能瓶颈。因此,如果有条件可以使用硬件阵列卡实现RAID是最佳的选择,只不过对使用者的技术能力有着较高的要求。

The post 基于软RAID的硬盘故障时数据恢复能力测试 first appeared on AI柠檬.

]]>
https://blog.ailemon.net/2023/04/03/hard-disk-failure-and-recovery-test-base-on-software-raid/?pk_campaign=feed&pk_kwd=hard-disk-failure-and-recovery-test-base-on-software-raid/feed/ 0
Java读取并解析wav格式文件 https://blog.ailemon.net/2022/11/07/java-read-and-parse-wave-format-file/?pk_campaign=feed&pk_kwd=java-read-and-parse-wave-format-file https://blog.ailemon.net/2022/11/07/java-read-and-parse-wave-format-file/?pk_campaign=feed&pk_kwd=java-read-and-parse-wave-format-file#respond Sun, 06 Nov 2022 16:01:00 +0000 https://blog.ailemon.net/?p=2801 本文将主要介绍如何使用Java语言读取文件并解析wave格式,并以代码形式进行展开。代码主要包含三个功能类,分 […]

The post Java读取并解析wav格式文件 first appeared on AI柠檬.

]]>
本文将主要介绍如何使用Java语言读取文件并解析wave格式,并以代码形式进行展开。代码主要包含三个功能类,分别为:Wave、DataParseUtils和Common。

本代码已用于GitHub上开源的ASRT语音识别系统的Java语言SDK项目: https://github.com/nl8590687/ASRT_SDK_Java

class Wave:

public class Wave {
    public short[] samples;
    public byte[] sampleBytes;
    public int sampleRate;
    public int channels;
    public int sampleWidth;

    public Wave(){}

    public Wave(short[] samples, int sampleRate, int channels, int sampleWidth) {
        this.samples = samples;
        this.sampleRate = sampleRate;
        this.channels = channels;
        this.sampleWidth = sampleWidth;
        // short[] 转 byte[]
        this.sampleBytes = this.samplesToBytes(samples);
    }

    public Wave(byte[] sampleBytes, int sampleRate, int channels, int sampleWidth) {
        this.sampleBytes = sampleBytes;
        this.sampleRate = sampleRate;
        this.channels = channels;
        this.sampleWidth = sampleWidth;
        // byte[] 转 short[]
        this.samples = this.bytesToSamples(sampleBytes);
    }

    public boolean deserialize(byte[] wavBytes) {
        try
        {
            byte[] riff = new byte[4];
            byte[] riffSize = new byte[4];
            byte[] waveID = new byte[4];
            byte[] junkID = new byte[4];
            boolean hasjunk = false;
            byte[] junklength = new byte[4];

            byte[] fmtID = new byte[4];
            byte[] cksize = new byte[4];
            int waveType = 0; // 无符号int整数,在获取时需要进行字节转码 (Byte.toUnsignedInt(byte x))
            byte[] channel = new byte[2];
            byte[] sample_rate = new byte[4];
            byte[] bytespersec = new byte[4];
            byte[] blocklen_sample = new byte[2];
            byte[] bitNum = new byte[2];
            byte[] unknown = new byte[2];
            byte[] dataID = new byte[4];  //52
            byte[] dataLength = new byte[4];  //56 个字节

            int p = 0; //模拟流的指针位置

            System.arraycopy(wavBytes, p, riff, 0, 4); // RIFF
            p += 4;

            if (DataParseUtils.convertFoutUnsignLong(riff[3], riff[2], riff[1], riff[0]) != 0x52494646) //0x52494646
            {
                Exception e = new Exception("该文件不是WAVE文件");
                throw e;
            }

            /*if (riff[0]!=82 || riff[1]!=73  || riff[2]!=70  || riff[3]!=70) //0x52494646
            {
                Exception e = new Exception("该文件不是WAVE文件");
                throw e;
            }*/

            System.arraycopy(wavBytes, p, riffSize, 0, 4); // 文件剩余长度
            p += 4;

            if (DataParseUtils.convertFoutUnsignLong(riffSize[3], riffSize[2], riffSize[1], riffSize[0]) != wavBytes.length - p)
            {
                //Exception e = new Exception("该WAVE文件损坏,文件长度与标记不一致");
                //throw e;
            }

            System.arraycopy(wavBytes, p, waveID, 0, 4);
            p += 4;

            if (DataParseUtils.convertFoutUnsignLong(waveID[3], waveID[2], waveID[1], waveID[0]) != 0x57415645)
            {
                Exception e = new Exception("该文件不是WAVE文件");
                throw e;
            }

            byte[] tmp = new byte[4];
            System.arraycopy(wavBytes, p, tmp, 0, 4);
            p += 4;

            if (DataParseUtils.convertFoutUnsignLong(tmp[3], tmp[2], tmp[1], tmp[0]) == 0x4A554E4B)
            {
                //包含junk标记的wav
                junkID = tmp;
                hasjunk = true;

                System.arraycopy(wavBytes, p, junklength, 0, 4);
                p += 4;

                long junklen = DataParseUtils.convertFoutUnsignLong(junklength[3], junklength[2], junklength[1], junklength[0]);


                //将不要的junk部分读出
                p += (int)junklen;

                //读fmt 标记
                System.arraycopy(wavBytes, p, fmtID, 0, 4);
                p += 4;
            }
            else if (DataParseUtils.convertFoutUnsignLong(tmp[3], tmp[2], tmp[1], tmp[0]) == 0x666D7420)
            {
                fmtID = tmp;
            }
            else
            {
                Exception e = new Exception("无法找到WAVE文件的junk和fmt标记");
                throw e;
            }


            if (DataParseUtils.convertFoutUnsignLong(fmtID[3], fmtID[2], fmtID[1], fmtID[0]) != 0x666D7420)
            {
                //fmt 标记
                Exception e = new Exception("无法找到WAVE文件fmt标记");
                throw e;
            }

            System.arraycopy(wavBytes, p, cksize, 0, 4);
            p += 4;

            long p_data_start = DataParseUtils.convertFoutUnsignLong(cksize[3], cksize[2], cksize[1], cksize[0]);
            int p_wav_start = (int)p_data_start + 8;
            byte[] tmp_waveType = new byte[2];
            System.arraycopy(wavBytes, p, tmp_waveType, 0, 2);
            p += 2;
            waveType = DataParseUtils.convertTwoUnsignInt(tmp_waveType[0], tmp_waveType[1]);

            if (waveType != 1)
            {
                // 非pcm格式,暂不支持
                Exception e = new Exception("WAVE文件不是pcm格式,暂时不支持");
                throw e;
            }

            //声道数
            System.arraycopy(wavBytes, p, channel, 0, 2);
            p += 2;

            //采样频率
            System.arraycopy(wavBytes, p, sample_rate, 0, 4);
            p += 4;

            int fs = (int)DataParseUtils.convertFoutUnsignLong(sample_rate[0], sample_rate[1], sample_rate[2], sample_rate[3]);

            //每秒钟字节数
            System.arraycopy(wavBytes, p, bytespersec, 0, 4);
            p += 4;

            //每次采样的字节大小,2为单声道,4为立体声道
            System.arraycopy(wavBytes, p, blocklen_sample, 0, 2);
            p += 2;

            //每个声道的采样精度,默认16bit
            System.arraycopy(wavBytes, p, bitNum, 0, 2);
            p += 2;

            System.arraycopy(wavBytes, p, tmp, 0, 2);
            p += 2;
            //寻找da标记
            while (DataParseUtils.convertTwoUnsignInt(tmp[1], tmp[0]) != 0x6461)
            {
                System.arraycopy(wavBytes, p, tmp, 0, 2);
                p += 2;
            }

            System.arraycopy(wavBytes, p, tmp, 0, 2);
            p += 2;

            if (DataParseUtils.convertTwoUnsignInt(tmp[1], tmp[0]) != 0x7461)
            {
                //ta标记
                Exception e = new Exception("无法找到WAVE文件data标记");
                throw e;
            }

            //wav数据byte长度
            byte[] data_size_byte = new byte[4];

            System.arraycopy(wavBytes, p, data_size_byte, 0, 4);
            p += 4;

            long DataSize = DataParseUtils.convertFoutUnsignLong(data_size_byte[0], data_size_byte[1], data_size_byte[2], data_size_byte[3]);
            //计算样本数
            long NumSamples = (long)DataSize / 2;

            if (NumSamples == 0)
            {
                NumSamples = (wavBytes.length - p) / 2;
            }

            short[] data = new short[(int) NumSamples];

            for (int i = 0; i < NumSamples; i++)
            {
                //读入2字节有符号整数
                byte[] tmp_sample = new byte[2];
                System.arraycopy(wavBytes, p, tmp_sample, 0, 2);
                p += 2;
                data[i] = (short)DataParseUtils.convertTwoUnsignInt(tmp_sample[0],tmp_sample[1]);
            }

            this.samples = data;
            this.sampleBytes = this.samplesToBytes(this.samples);
            this.sampleRate = fs;
            this.channels = DataParseUtils.convertTwoUnsignInt(channel[0],channel[1]);
            this.sampleWidth = DataParseUtils.convertTwoUnsignInt(bitNum[0],bitNum[1]) / 8;
            return true;
        }
        catch (Exception ex)
        {
            System.out.println(ex);
            return false;
        }
    }

    private byte[] serialize() {
        return null;
    }

    public byte[] getRawSamples() {
        return this.sampleBytes;
    }

    protected byte[] samplesToBytes(short[] samples){
        byte[] sampleBytes = new byte[samples.length * 2];
        for(int i = 0; i < samples.length; i++){
            byte[] sample = DataParseUtils.convertShortToBytes(samples[i], false);
            for(int j = 0; j < 2; j++){
                sampleBytes[2*i+j] = sample[j];
            }
        }
        return sampleBytes;
    }

    protected short[] bytesToSamples(byte[] sampleBytes){
        short[] data = new short[sampleBytes.length / 2];
        for (int i = 0; i < sampleBytes.length / 2; i++)
        {
            //读入2字节有符号整数
            byte[] tmp_sample = new byte[2];
            System.arraycopy(sampleBytes, 2 * i, tmp_sample, 0, 2);
            data[i] = (short)DataParseUtils.convertTwoUnsignInt(tmp_sample[0],tmp_sample[1]);
        }
        return data;
    }
}

class DataParseUtils:

class DataParseUtils {
    /**
     * 有符号,int 占 2 个字节
     */
    public static int convertTwoSignInt(byte b1, byte b2) { // signed
        return (b2 << 8) | (b1 & 0xFF);
    }

    /**
     * 有符号, int 占 4 个字节
     */
    public static int convertFourSignInt(byte b1, byte b2, byte b3, byte b4) {
        return (b4 << 24) | (b3 & 0xFF) << 16 | (b2 & 0xFF) << 8 | (b1 & 0xFF);
    }

    /**
     * 无符号,int 占 2 个字节
     */
    public static int convertTwoUnsignInt(byte b1, byte b2)      // unsigned
    {
        return (b2 & 0xFF) << 8 | (b1 & 0xFF);
    }

    /**
     * 无符号, int 占 4 个字节
     */
    public static long convertFoutUnsignLong(byte b1, byte b2, byte b3, byte b4) {
        return (long) (b4 & 0xFF) << 24 | (b3 & 0xFF) << 16 | (b2 & 0xFF) << 8 | (b1 & 0xFF);
    }

    public static byte[] convertShortToBytes(Short shortNumber, boolean big) {
        byte[] bytes = new byte[2];
        bytes[0] = (byte) (shortNumber & 0xff);
        bytes[1] = (byte) (shortNumber >> 8 & 0xff);
        if (big){
            byte tmp = bytes[0];
            bytes[0] = bytes[1];
            bytes[1] = tmp;
        }
        return bytes;
    }
}

class Common:

import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;

public class Common {
    public static byte[] readBinFile(String filename) {
        FileInputStream input = null;
        try {
            List<Byte> byteList = new ArrayList();
            input = new FileInputStream(filename);
            byte[] buffer = new byte[1024];
            while (true) {
                int len = input.read(buffer);
                if (len == -1) {
                    break;
                }
                for(int i = 0; i < len; i++){
                    byteList.add(buffer[i]);
                }
            }
            byte[] byteArr = new byte[byteList.size()];
            for(int i = 0; i< byteArr.length; i++){
                byteArr[i] = byteList.get(i);
            }
            return byteArr;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                input.close();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
}

参考资料Refference

  1. AI柠檬,ASRT开源语音识别项目Java SDK. GitHub, https://github.com/nl8590687/ASRT_SDK_Java

The post Java读取并解析wav格式文件 first appeared on AI柠檬.

]]>
https://blog.ailemon.net/2022/11/07/java-read-and-parse-wave-format-file/?pk_campaign=feed&pk_kwd=java-read-and-parse-wave-format-file/feed/ 0
NAS+云服务器打造一套个人私有云解决方案 https://blog.ailemon.net/2022/09/05/nas-plus-cloud-server-to-build-personal-cloud-solution/?pk_campaign=feed&pk_kwd=nas-plus-cloud-server-to-build-personal-cloud-solution https://blog.ailemon.net/2022/09/05/nas-plus-cloud-server-to-build-personal-cloud-solution/?pk_campaign=feed&pk_kwd=nas-plus-cloud-server-to-build-personal-cloud-solution#respond Sun, 04 Sep 2022 16:13:54 +0000 https://blog.ailemon.net/?p=2789 本文介绍了AI柠檬在自己的家里打造的一套个人的私有云解决方案。这套方案使用了一个本地部署的网络附加存储(NAS […]

The post NAS+云服务器打造一套个人私有云解决方案 first appeared on AI柠檬.

]]>
本文介绍了AI柠檬在自己的家里打造的一套个人的私有云解决方案。这套方案使用了一个本地部署的网络附加存储(NAS)服务器和一个云服务器实例打造,并在本地NAS服务器上部署安装若干应用服务软件,可以解决个人数据存储的存取便捷性容量扩展性数据安全性等实际使用中的痛点。

1 系统结构设计方案

图1 NAS服务器与云服务器、网络设备和终端设备的系统方案图

NAS服务器用于最终的个人私有云的数据存储以及相关应用服务的部署。家庭用的无线路由器、交换机等网络设备用于内部网络的连接,云服务器用于支撑NAS服务器通过内网穿透技术接入公网,使得在外部环境下访问公网上的云服务器端口即可转发流量到内部的NAS服务器上。基于此,任何终端设备(PC台式机、笔记本电脑、平板电脑、手机等)均可通过网络随时随地访问到同一套数据存储服务。

2 NAS服务器上的应用服务部署方案

如果NAS服务器选择现有的诸如某晖的机器,或者安装有TrueNAS操作系统,那么这些服务的部署会非常简单。如果采用Ubuntu、CentOS、Fedora或者EulerOS等操作系统,那么需要你对Linux类操作系统有一定的了解,然后通过手工操作或基于Docker部署等方式安装相关应用服务。

图2 NAS服务器上的应用服务部署方案图

不过需要注意的是,Samba服务的端口作为危险端口不应该直接暴露于公网,仅用于内网,但也可以通过VPN方式(注意,国内云服务器不允许个人私自搭建VPN服务)从外部环境接入。作为替代,NextCloud服务的文件存储能力,可以更好地同时保证使用便捷性和数据安全性,其附带的扩展插件能提供远远超出Samba服务能力的更多有价值的功能。

3 域名DNS解析方案

如果使用域名访问NextCloud服务,那么一般需要解决内部网和外部网的DNS解析问题。否则,DNS解析到公网IP时会导致内部网络访问速度偏慢,DNS解析到内网IP时会导致设备在外部网络无法访问到NAS。因此,可以针对内网环境的访问需求部署一个DNS解析服务,将特定的域名解析为内网IP,而公网的DNS解析全部解析为云服务器的公网IP,其他域名则全部中继上一级默认的DNS服务器。终端设备在内部环境接入网络作DHCP时的DNS服务器会被指定为该内网服务器,并在随后DNS解析时针对NAS所使用到的特定域名解析为内网IP地址,其他域名全部递归解析。

图3 域名DNS解析方案图

需要注意的是,自己的DNS服务器不应部署于有公网IP的服务器上,否则可能会影响公网上其他用户的正常DNS解析(产生DNS污染),并且国内政策也不允许这么做,只应该将其部署于自己的内部网络中。

4 总结

本文基于AI柠檬博主在文章《我是如何搭建NAS私有云服务器的》[1]中所介绍的相关技术迁移到家庭中部署的实践经验,从理论上总结了一套个人用的极客型私有云解决方案。在实践中,该方案的有效性已经得到验证。对于相关技术感兴趣的朋友们,可以阅读一下相关资料。

相关资料

  1. AI柠檬, 我是如何搭建NAS私有云服务器的[Z], AI柠檬博客, 2020. https://blog.ailemon.net/2020/10/22/how-i-setup-private-nas-server/
  2. AI柠檬, AI柠檬的个人数据治理之路[Z], AI柠檬博客, 2021. https://blog.ailemon.net/2021/10/13/the-way-of-ailemon-personal-data-governance/

The post NAS+云服务器打造一套个人私有云解决方案 first appeared on AI柠檬.

]]>
https://blog.ailemon.net/2022/09/05/nas-plus-cloud-server-to-build-personal-cloud-solution/?pk_campaign=feed&pk_kwd=nas-plus-cloud-server-to-build-personal-cloud-solution/feed/ 0
Golang后端实现WebSocket接收发送Demo https://blog.ailemon.net/2022/04/25/golang-websocket-demo/?pk_campaign=feed&pk_kwd=golang-websocket-demo https://blog.ailemon.net/2022/04/25/golang-websocket-demo/?pk_campaign=feed&pk_kwd=golang-websocket-demo#comments Sun, 24 Apr 2022 16:01:00 +0000 https://blog.ailemon.net/?p=2768 文件目录: main.go index.html

The post Golang后端实现WebSocket接收发送Demo first appeared on AI柠檬.

]]>
文件目录:

websocket-demo
-- public/
--|-- index.html
-- main.go
-- go.mod
-- go.sum

main.go

package main

import (
	"log"
	"net/http"
	"time"

	"github.com/gorilla/websocket"
)

var clients = make(map[*websocket.Conn]bool)
var broadcast = make(chan Message)

var upgrader = websocket.Upgrader{}

type Message struct {
	Message string `json:"message"`
}

func main() {
	fs := http.FileServer(http.Dir("public"))
	http.Handle("/", fs)

	http.HandleFunc("/ws", handleConnections)

	go handleMessages()

	log.Println("http server started on :8000")
	err := http.ListenAndServe(":8000", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

//注册成为 websocket
func handleConnections(w http.ResponseWriter, r *http.Request) {

	ws, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Fatal(err)
	}
	defer ws.Close()

	clients[ws] = true

	//不断的从页面上获取数据 然后广播发送出去
	for {
		// 发送数据
		time.Sleep(time.Second * 3)
		msg := Message{Message: "这是向页面发送的数据 " + time.Now().Format("2006-01-02 15:04:05")}
		broadcast <- msg

		// 接收数据
		var msgRecv Message
		err := ws.ReadJSON(&msgRecv)
		if err != nil {
			log.Printf("error: %v", err)
			delete(clients, ws)
			break
		}
		log.Println(msgRecv)
	}
}

//广播发送至页面
func handleMessages() {
	for {
		msg := <-broadcast
		for client := range clients {
			err := client.WriteJSON(msg)
			if err != nil {
				log.Printf("client.WriteJSON error: %v", err)
				client.Close()
				delete(clients, client)
			}
		}
	}
}


index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <title>Sample of websocket with golang</title>
    <script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>

    <script>
        $(function() {
            var ws = new WebSocket('ws://' + window.location.host + '/ws');
            ws.onmessage = function(e) {
                $('<li>').text(event.data).appendTo($ul);
            ws.send('{"message":"这是来自html的数据"}');
            };
            var $ul = $('#msg-list');
        });
    </script>
</head>
<body>
<ul id="msg-list"></ul>
</body>
</html>

The post Golang后端实现WebSocket接收发送Demo first appeared on AI柠檬.

]]>
https://blog.ailemon.net/2022/04/25/golang-websocket-demo/?pk_campaign=feed&pk_kwd=golang-websocket-demo/feed/ 3