目录

  1. 1. 什么是NAS
  2. 2. 存储介质
  3. 3. 下载工具
    1. 3.1 DownloadMaster
      1. 3.1.1 安装
      2. 3.1.2 支持协议
      3. 3.1.3 使用方法
      4. 3.1.4 综合评价
    2. 3.2 Transmission
    3. 3.3 Aria2
      1. 3.3.1 安装
      2. 3.3.2 支持协议
      3. 3.3.3 使用方法
        1. 3.3.3.1 启用
        2. 3.3.3.2 如何下载
        3. 3.3.3.3 各类网盘插件
      4. 3.3.4 综合评价
    4. 3.4 远程迅雷
      1. 3.4.1 安装
      2. 3.4.2 支持协议
      3. 3.4.3 使用方法
      4. 3.4.4 综合评价
    5. 3.5 迅雷下载宝
  4. 4. 共享工具
    1. 4.1 DLAN
    2. 4.2 Samba
    3. 4.3 NFS
    4. 4.4 FTP

上篇文章说到梅林固件的基础配置,大部分路由器系统都可以参考。这次专门来说一说路由器实战应用–搭建一个小型NAS。

1. 什么是NAS

Network Attached Storage 是一种专用数据存储服务器。它以数据为中心,将存储设备与服务器彻底分离,集中管理数据,从而释放带宽、提高性能、降低总拥有成本、保护投资。

说简单点就是以存储为核心功能的电脑。既然以存储为核心功能,那么就是能接入多块硬盘或其他存储介质,能下载、上传、共享和管理数据。

目前市面上家用最多的就是Synology(群晖)了,它可能长这样:

1

入门级群晖国行价格基本2000左右,还不算硬盘。水货也在1500以上。价格相对还是比较贵的。

这里需要提一下,这里的群晖即是产品也是系统。为什么会这样说?我们自己完全用一台闲置的电脑,或者自己组装一套设备,来安装“群晖”这个系统,做一个DIY群晖NAS。但事实上官方不允许这样做,这牵扯到版权和专利甚至法律上的问题。和PC机不能安装Mac OS一个道理,群晖系统有严格的检测和绑定机制。当然,人民的力量总是不可阻挡的,于是和“黑苹果”一样,有了“黑群晖”。某宝上买一套组装好的“黑裙”也要500上下,不带硬盘。即便如此,对于我这种轻度使用用户来说有点浪费。所以才引出了本篇文章,基于路由器搭建一个小型的NAS。

2. 存储介质

既然要搭建NAS,总要有存储介质吧,最方便的无疑是移动硬盘了。R6400带有一个USB2.0接口和一个USB3.0接口。考虑到读写速率,还是使用USB3.0接口最靠谱。

上一篇我们也说到USB3.0与2.4G频段会有干扰,所以梅林固件默认把3.0接口设置成2.0协议。按照上一篇的解决方案,先停用“降低USB3.0干扰”,然后用锡箔纸包一下移动硬盘的USB接口处,屏蔽干扰。2

21

3. 下载工具

有了存储介质后,我们需要安装下载工具,作为一个NAS,下载工具不仅要支持多种协议如HTTP/FTP/BT/MetaLink,还要满足远程下载,脱机下载等诸多方式。梅林固件提供了三种较为常用的下载工具:下载大师、Transmission和Aria2。还可以使用远程迅雷和下载宝等其他方式。

3.1 DownloadMaster

下载大师是华硕路由器或梅林固件自带的下载器,安装简单使用方便。

3.1.1 安装

可以在“USB相关应用”中启用“Download Master”即可,默认会安装到存储介质中。

4

3.1.2 支持协议

FTP、HTTP、BT、MetaLink

3.1.3 使用方法

开启下载大师后,默认8081端口。内网可以直接使用192.168.0.1:8081进入。界面简洁。

5

如果需要外网远程控制,在“一般设置”里打开“WAN”网络。同时建议修改端口到不常用的区段。参考第一篇基本设置里,开放公网ip和DDNS后可以在外网通过域名+端口访问DownloadMaster。

6

3.1.4 综合评价

除了ed2k,和thunder,支持大部分下载协议。用来刷PT非常方便,根据PT站要求可能需要关闭DHT,不会ban,稍稍消耗路由资源。偶尔造成路由假死,CPU爆百等现象。推荐安装。

3.2 Transmission

群晖装Transmission的比较多,即使群晖有自带下载工具。Trans主要用来泡PT。梅林上太吃性能了,我没有安装。有兴趣的可以自己了解下:

http://koolshare.cn/forum.php?mod=viewthread&tid=88040&highlight=transmission

3.3 Aria2

Aria2原本是linux下的一个下载工具,项目开源在 https://github.com/aria2/aria2 现在逐步扩展到mac、win乃至android平台下。

安装和使用非常简单,只需一个aria2c可执行文件和aria2.conf配置文件就能使用。

3.3.1 安装

梅林“Software Center”直接安装Aria2

3.3.2 支持协议

FTP、HTTP、BT、MetaLink

3.3.3 使用方法

3.3.3.1 启用

  • 软件中心开启Aria2,CPU限制建议40-50,下载目录可以自定义,也可以使用默认的downloads

    10

  • RPC设置里,端口可以默认也可以修改成其他不常用的区段,一定要填写RPC密码

    11

  • 下载限制里,最大任务最多填5,最大线程5-10,其他可以不变。调整越大,下载任务和速度越快,也越吃路由性能

    12

  • 最后BT设置这里,DHT监听端口和BT监听端口最好改一下,否则可能没速度,如果泡PT,记得关DHT

    13

    如果速度较慢,可以根据需要添加Tracker。当然再怎样速度也比不过迅雷了。

3.3.3.2 如何下载

Aria只是一个程序,通过设置RPC可以作为一个服务。上面的步骤可以理解成我们搭建好一个WebAPI,现在我们需要用UI来展示它。

14

梅林的Aria2插件提供三个UI,我个人建议使用yaaw和webui-aria2就可以了。在重申一遍,这几个网址仅仅是UI,是一个壳子,需要通过配置,连接路由器上的Aria RPC服务才能使用。如果在内网,填路由内网ip 192.168.0.1即可,如果是外网,参考上一篇的外网ip或DDNS的域名。

  • yaaw

    界面简洁,我一般在手机上使用,主要还提供批量删除方法,配置方法如下所示

    15

    地址格式为“token”+“:”+RPC密码“xxx”+”@”+路由器ip,可用DDNS的域名代替+RPC端口

  • webui-aria2

    界面漂亮,适合PC端使用,配置在“Setting”的“Connection Settings”里,当然你可以切换到中文界面

    16

    webui-aria2不需要像yaaw那样把token写到连接中,它专门提供了位置填写token

只要拥有外网ip,或者DDNS,就能在公司、甚至在路途中,通过电脑或手机连接到路由器上的Aria,操作下载任务。

PS:Aria也很吃路由性能,几个任务就能让CPU飙到80+,CPU繁忙时外网甚至内网都无法用UI连接到RPC服务,这时候需要在“Software Center”中,关闭再打开Aria,或者直接在Aria中点下提交(实际上也是重启),就可以连接到Aria服务了。

3.3.3.3 各类网盘插件

Aria最好用的一点其实是各类下载任务的导出,尤其像百度网盘,可以实现“不限速”下载。下载任务导出就是通过Aria的RPC,把下载连接发送给路由上的Aria服务,从而启动下载。下面介绍一下各类导出插件的使用(全部以Chrome浏览器为例):

  • 百度网盘导出

    项目在https://github.com/acgotaku/BaiduExporter ,Chrome商店的已经下架。安装使用和解除Chrome提示停止开发者插件的方式这里不再多说,Readme中已经详细说明。

    使用这个插件可以绕过百度的200k限速,Aria的配置中,线程数给的越多,下载速度就越快。

    需要提醒的是,百度网盘会检测第三方下载程序,有过流量异常,可能会把你的百度账号限流,所以很多网友反应用Aria下载百度网盘有时候速度会变成100k,50k或者10k。据说1-2周时间会解除限速。当然如果你有百度网盘的会员,这些都不用看了。

    解决限速的方式很简单,使用Aria导出时,不要登陆百度账号,以游客方式下载,百度的检测机制就无从下手。也就是说你可以直接在别人的分享连接中下载,或者保存到自己的网盘,将连接共享出去,再退出账号,进入共享连接导出下载!

    安装完成后,百度网盘会多出一个“导出下载”,使用前先“设置”,填写的内容和前面讲到的UI连接RPC一样,设置完成直接点ARIA2 RPC就能把任务导出到Aria中。

    17

  • 迅雷离线导出

    下文会提到迅雷封杀了第三方的远程下载,但离线下载还能用。Chrome插件在https://chrome.google.com/webstore/detail/thunderlixianassistant/eehlmkfpnagoieibahhcghphdbjcdmen 迅雷离线不能下载被禁的资源,下载速度也相对较慢。

  • 115网盘导出

    115的最大优势就是能下载迅雷不能下的“被禁”资源,还能加速哦!当然天下没有免费的馅饼,你得开会员。通过115离线下载BT或者磁链,再导出到Aria。

    插件项目在https://github.com/acgotaku/115/ Chrome商店的插件还没下架https://chrome.google.com/webstore/detail/115exporter/ojafklbojgenkohhdgdjeaepnbjffdjf

    安装和使用和百度网盘类似。

3.3.4 综合评价

Aria安装起来稍显麻烦。但功能强大,可以配合下载大师一起使用。尤其是对付各类网盘非常好用。Aria相比迅雷和Trans,对路由的资源消耗相对较少,但Aria很容易无法连接上RPC服务,上面也说到通过关闭打开或者提交的方式重启Aria可解决。

3.4 远程迅雷

原本算是梅林上的下载神器了。然而迅雷一纸封杀令“7月17日起陆续停止提供第三方远程下载服务”,让远程迅雷直接在“软件中心”上下架了。有网友整理了最新的离线安装版,由于封杀分时间进行,所以现在还能使用,趁着末班车,看下怎么玩吧。

3.4.1 安装

参考http://koolshare.cn/forum.php?mod=viewthread&tid=111038下载离线包

在路由器“Software Center”的“离线安装”界面上传并安装

7

在“Software Center”启用“远程迅雷”,成功启用后会显示出一个“激活码”,随后打开“远程迅雷控制台”,将激活码填写在指定位置,即可使用。

8

3.4.2 支持协议

迅雷支持的这里都支持

3.4.3 使用方法

填写“激活码”后相当于把远程迅雷和路由器绑定起来,在外网,可以使用DDNS的域名登陆路由器,进入“Software Center”,找到“远程迅雷”再打开“迅雷控制台”。这样无疑太麻烦。既然设备已经绑定,直接访问http://yuancheng.xunlei.com/ 登陆账号就可以使用了。

9

3.4.4 综合评价

支持的协议最多,下载也最稳定。吃路由资源,容易造成路由假死或直接崩溃。随着迅雷封杀第三方远程下载,除了迅雷自家的下载宝以及合作的小米路由,其他的路由估计不久后就不能再使用迅雷了。当然,远程迅雷被禁的资源是不能下载的哦,另外即使有会员下载速度也不如桌面版。

3.5 迅雷下载宝

迅雷官方推出的下载器,159大洋,这个是硬件产品。自家软件完美兼容,可以当小型NAS用,不想折腾直接入手这个吧。配合迅雷会员和快鸟使用起来还是很方便,不过据说下载速度还是没法和桌面软件比。被禁的资源仍然不能下载。

4. 共享工具

有了下载工具,最后还差一步,将下载的内容共享出来,供内网设备乃至外网访问。梅林提供4中方式:DLAN、Samba、NFS和FTP。

这四种方式可以在“USB相关应用”的“服务器中心”中开启:

18

4.1 DLAN

DLNA的全称是DIGITAL LIVING NETWORK ALLIANCE(数字生活网络联盟),其宗旨是Enjoy your music,photos and videos,anywhere anytime,DLNA(Digital Living Network Alliance)由索尼、英特尔、微软等发起成立。旨在解决个人PC,消费电器,移动设备在内的无线网络和有线网络的互联互通。

19

如图所示,开启UPnP就能启动DLAN,目前大多智能电视都能识别DLAN,媒体服务器名称可以自由修改。建议命名中包含DLAN,用于和后面的Samba区别。

DLAN和UPnP是什么关系?这个牵扯到基本概念了。DLAN是一套标准,UPnP是DLAN中的一个基本协议。开启UPnP媒体服务器后,就能在智能电视上找到你的移动硬盘了,想看什么看什么,想怎么看就怎么看。电脑也可以在网上邻居查到流媒体服务器,能查看但不能管理。至于手机,安装支持DLAN的播放软件就可以。

4.2 Samba

Samba本是linux下实现共享的软件,路由固件本身就基于Linux,所以这里直接可以拿来用。与DLAN不同,samba只是一个用于共享的软件,在处理流媒体上,速度可能不如DLAN。由于有的电视或电视盒子不一定支持DLAN或Samba。所以这两个可以都打开,能用哪个用哪个。

电脑通过网络邻居能发现Samaba的设备,登陆需要用户名密码,就是路由器的账号和密码,登陆后不仅能查看,还能修改和删除。由此可见,DLAN仅作为流媒体服务,不需要账号密码。而Samba是共享工具,操作还是按共享的流程走。

20

4.3 NFS

NFS(Network File System)即网络文件系统,是FreeBSD支持的文件系统中的一种,它允许网络中的计算机之间通过TCP/IP网络共享资源。

实际应用比较少,这里不过多介绍。

4.4 FTP

FTP这个不用多说了吧。单纯为了看移动硬盘中的资源的话,DLAN和Samba足够用。如果想远程管理器中的数据,或者远程通过移动硬盘安装路由器的某些插件,可能会用到FTP。“启用”FTP就可以内网访问,如果想外网也能访问,就打开“Enable WAN access”。用XFTP或者浏览器直接访问路由的公网ip或DDNS的域名就可以。账号密码也是路由的账号和密码。

基于路由器搭建小型NAS能满足一些轻量的需求,在路由器上不论用哪种下载工具,都会消耗路由的性能,所以毕竟不是长久之计。根据自己的实际需求,选择适合自己的方式才是王道,又或者你·根·本·不·差·钱

下一篇会讲路由的另一个实战应用,PS串流到外网。出差没法玩PS?上班午休时想来一把铁拳?乘车坐地铁体验一把“高端”手游?到时给你好看。

评论和共享

目录

  1. 1. 光猫改桥接
  2. 2. WIFI设置
  3. 3. 双拨
  4. 4. 改上传带宽
  5. 5. 公网IP
  6. 6. DDNS
  7. 7. QoS
  8. 8. USB3.0干扰
  9. 9. 插件中心

最近小区光纤升级到200m,家里的百兆路由带不动,于是入手洋垃圾R6400,刷了梅林7.5,果然路由固件深似海,折腾好几个晚上总算排除万难。心路历程将分为三部分:常用设置篇、搭建NAS篇、PS4串流篇。

1. 光猫改桥接

这是后续折腾的大前提,电信宽带默认光猫拨号,光猫拨号的好处主要是节省用户配置,用户插路由器不用设置即可上网,对一些不是很懂电脑的人尤其是家中的老人尤其方便。但缺点也很明显,电信提供的光猫大多是二手翻新,还有各种杂牌,性能相对差一些。同时路由器不能获取IP,后面很多操作没的玩。

看自己的光猫是不是桥接模式很简单:

  • 自己有没有在路由器上设置ppoe拨号,若没有设置ppoe,那光猫就是路由模式而非桥接模式
  • 查看路由器wan口获取的ip,如果是内网192的ip,那光猫就是路由模式而非桥接模式

光猫改桥接可以叫电信的师傅过来帮改,也可以看一下光猫的型号然后上网搜索一下改桥接方式。部分光猫可以通过光猫背后的账号密码登陆修改,还有一部分需要超级用户才能登陆,总之解决不了就喊人。我之前用过中兴F612这款光猫,需要超级密码才能进入后台,网上各种教程还需要拆机跳线。最后直接一个地址搞定:http://192.168.1.1/bridge_route.gch

光猫改桥接后,路由承担拨号,所以在路由上重新设置ppoe拨号吧,账号密码忘了就打10000去重置。

2. WIFI设置

Koolshare上很多帖子都是介绍刷完固件要换区,换成全区或澳大利亚,同时调整发射功率到100%,Koolshare官方组也解释过其实默认的国区50%的发射功率就可以了。部分电子设备可能不兼容外区的频道。同时调大发射功率会产生更大的噪点,反而起到反效果。(换区和发射功率可以在“无线网络”的“专业设置”中调整)

关于2.4G:有设备搜不到2.4G信号,此时请检查无线设置中的2.4G信号,信道设置在13可能会造成部分设备无法搜索到信号,通常建议使用1或6信道。此外,频道带宽也有可能影响信号的搜索,在不确定设备支持情况时请使用20Mhz,保证最大兼容。注意,此时2.4G的连接速率会比40Mhz低一半。

关于5G:有支持5G的设备设备搜不到5G信号。部分用户购买的国际版手机可能工作在国内不支持的信道。遇到此类问题,请不要把信道设置为36-64;一般情况下正常终端应该都会支持149-165,您可以尝试将信道设置在此范围内然后再试。

【频段】: 频段类似公路,2.4G的频段传输远,抗干扰强,速度低,使用广,好比国道车少车慢。5G的频段传输近,抗干扰弱,速度高,类似高速公路车多车速快容易车祸。不管哪个频段,他们都有固定的频率范围,也就是公路的宽度是固定的。

【频宽与频道】:频宽类似车道,频道类似具体的几号车道,而数据可以理解成车。上面说了,2.4G频段的公路宽度是固定的,你周围的邻居都在和你使用这一条公路。我们自建一个20Mhz频宽的车道,这个车道相对窄,不容易和其他人的车道发生碰撞,也就是抗干扰强。但车道窄,车就少,数据传输就慢。如果自建一个40Mhz的车道,能跑的车多,但你更容易和周边的人产生交叉共用车道,一旦发生车祸,反而产生很极端的效果。

1

【2.4G的设置】:官方建议选20Mhz的频宽,主要考虑传输远,抗干扰强,设备兼容性高。理论下载速度65M/s,实测平均58M/s。考虑家中的设备兼容性和周围信道的分析,最终我选择了40Mhz的频宽,下载速度可达到120M/s,同时稳定性也达标。

信道的分析可用手机下载 Wifi Analytics

3

或在PC上使用inSSIDer

2

又或是在梅林的“无线网络”中进入“Site Survey”

4

很多人建议只选择1/6/11中的一个,但周围如果这三个信道占用较多,就不要生搬硬套。通过频道信道测试工具找出占用最少的信道作为使用。

【5G的设置】:比较简单,80Mhz的频宽保证速度,信道选149-161中使用最少的。5G穿墙衰减的很快,如果房间较大,还需要合理调整路由器的位置,或者加AP增加信号。我这边路由放客厅,卧室门一关信号直接掉一格。

3. 双拨

通过梅林固件,对宽带进行两次ppoe拨号,理论上上传下载带宽实现乘以二的效果。当然实际上会出现以下几种情况:

  • 服务商限制多拨:可以看查看工单,如果没有写season,或者season为1,那就是服务商限制只能单拨。或者打电话咨询10000号,广东电信也可以使用上网账号密码在https://gd.189.cn/TS/new/login.html?loginOldUri=/TS/index.htm 进行查询,同样找season或者拨号终端数量的字眼。
    1. 如果限制多拨,基本可以放弃尝试了,但也可以继续研究,说不定有意外惊喜。有网友反应服务商限制多拨,梅林双拨后也显示第二个拨号联网中断,但网速确实翻倍。
      1. 如果没有限制,那么恭喜你,可以继续下面的步骤。
  • 服务商端口限速:服务商可能会对端口进行限速,也可能只对上传或者下载进行限速。这个可以打电话问电信,但很可能客服也不清楚。所以自己尝试一下就知道了。
    1. 端口限速:如果有端口限速,即使可以多拨,网速也不会乘以二,也就是说双拨没有意义。广州电信是端口限速。
    2. 部分限速:如果上传或下载限速,双拨后,没有限速的部分网速乘以二,限速的部分不变。很多情况是限制下载,不限制上传,所以双拨后可以double一下上行带宽,泡PT也不错。
    3. 不限速:如果没有端口限速,那么双拨后,用测速软件能看到上行和下行带宽都乘以二,这里可能会出现一种情况,测速显示网速不变,但使用迅雷等p2p下载时,网速会变成2倍。

还有网友反应宽带是公网IP可能会引起不能双拨,但也有例外,这种玄学只有亲自尝试才可以。双拨的方法可以看Koolshare网友的教程 http://koolshare.cn/forum.php?mod=viewthread&tid=48835

我这里简单描述一下:

  1. 连线方式:有三种,方案三最简单,如果方案三不行,再尝试方案二,方案一过于复杂不建议尝试

    5

  2. 设置路由器IP:不要和光猫冲突,光猫一般 是192.168.1.1,路由可设置成192.168.0.1或其他。基础问题不在多说

    6

  3. 开启双线路:注意如果是方案三,不勾选Multiple PPPd support,如果是方案二,需要勾选Multiple PPPd support

    7

  4. 为主线路和副线路设置ppoe拨号

    8

    9

  5. 如果是方案方案二,需要将Lan1伪装成Wan口,和Lan2做互联。方案三不需要此步骤。

    10

  6. 测试双拨结果

    11

4. 改上传带宽

由于我的宽带被限制多拨,同时还听闻广州电信端口限速,所以双拨只是小玩了一下。但我200M的宽带上行只有4M,上行带宽太低导致PS4串流到公网效果会差很多。本想通过双拨解决,无奈没有结果。

KoolShare的帖子http://koolshare.cn/forum.php?mod=viewthread&tid=115638 中反映,最早一批办理100M宽带的同学,上行都是2M,后来办的变成4M。现在直接去营业厅办理200M宽带的上行是8M,但通过100M升级的宽带仍是4M上传。这太扯了,升级的老用户还tm吃亏?于是打了4-5个电话给10000,经过不懈努力,终于将4M上行变成8M上行。所以同学们,这种事自己别亏着。查询上下行带宽可在第3节查询是否可以双拨的链接里查看,也可以直接用电信的测试网站http://10000.gd.cn/ 查看。

5. 公网IP

众所周知,ip也就是ipv4在全球范围内早就不够用了,除了企业宽带,服务商会分配固定ip,其他家用宽带都是分动态ip,每次重启路由或重新拨号时都会分一个新的ip过来。这里说的ip都是公网ip。不知从什么时候起,这种动态的公网ip都不够了,服务商干脆在一个动态ip下做NAT,分给多个用户用。

如何查询自己的宽带是真·公网ip:看一下路由器获取的wan口ip和百度里输入”IP”查到的ip(也可在http://www.ip138.com/ 查询)是否是同一个ip。如果不一样,说明自己被运营商NAT了。

真·公网ip有什么用:只有真·公网ip才可以通过DMZ或者端口映射,在互联网上访问家里的路由、摄像头、NAS、VPN等服务或设备。只有真·公网ip才可以通过 DDNS也就是动态域名解析,将域名和ip关联起来,在互联网上通过域名访问家中的各种服务或设备。这些在第二篇搭建NAS和第三篇PS4串流中都会讲到。

如何申请真·公网ip:广州电信现在很方便了,打电话给10000,让他分配公网ip即可。外地电信如果不好改,可以说家中要装摄像头,没有公网ip不能用。移动联通据说不太好改。不是不能改,看自己怂不怂啦。

没有真·公网怎么办:有时候真的拿不到公网ip,那只能用内网穿透。花生壳的花生棒提供这一服务,梅林固件插件中心也提供基于ngrok的“穿透DDNS”和“frpc穿透”这两款工具。花生棒收费,“穿透DDNS”和“frpc穿透”需要有线路条件良好的VPS,在VPS搭建服务端,然后在路由上配置客户端。这两款插件很吃路由cpu和内存,双CPU爆百分分钟的事,“frpc穿透”更需要安装虚拟内存,太过于繁琐。所以搞到公网ip才是王道啊。使用这两款插件可以参考:http://koolshare.cn/forum.php?mod=viewthread&tid=36908http://koolshare.cn/thread-65379-1-1.html

6. DDNS

搞到了公网ip,接下来做DDNS。DDNS全称Dynamic Domain Name Server也就是动态域名解析。N年前在自家电脑搭服务器或网站都会用到的花生壳就是这货,这里就牵扯到上一节中讲的ip不够分,所以每一次重拨都拿到一个新ip,这种情况不能直接对域名做dns解析,而是通过动态dns来实现。

梅林提供的DDNS有华硕自带的DDNS,也有花生壳和ChangeIP等第三方的DDNS。免费的服务解析效率都很慢,重新拨号后至少要等5-10分钟才能用域名访问。

12

图方便可以直接用华硕的DDNS,如果想自定义顶级域名,选择Custom,然后根据http://koolshare.cn/forum.php?mod=viewthread&tid=4422 这个教程来绑定自己的域名。注意这里选Custom后还是要注册花生壳或者ChangeIP或其他DDNS服务商,将账号token信息按教程填写并创建脚本。

若使用华硕的DDNS,ip动态分配后,如果解析尚未完成,可以在 http://iplookup.asus.com/nslookup.php 查询自己的域名的ip,临时用ip进行相关操作,待解析完成再用回域名。

上面的操作涉及路由器开放SSH登陆,xshell或winscp的使用,以及linux相关命令。允许路由进行SSH登陆可在“系统管理”的“系统设置”里修改,填写所需的端口(不建议使用默认端口)。xshell或winscp以及linux相关内容这里不再多说。

13

设置完DDNS后可以尝试配置一下在外网访问家中的路由。

14

如果允许外网访问路由,建议授权方式选成BOTH。HTTPS Lan port 是内网https的访问端口,可以用默认的443。内网环境下一般也不需使用Https。选择“从互联网设置R6400”,并设置端口,不建议使用默认端口。这时可以使用自己的域名如 http://xxx.asuscomm.com:xxx80https://xxx.asuscomm.com:xx443 访问家中的路由器了。外网建议使用https方式。

7. QoS

QoS(Quality of Service,服务质量)指一个网络能够利用各种基础技术,为指定的网络通信提供更好的服务能力, 是网络的一种安全机制, 是用来解决网络延迟和阻塞等问题的一种技术。百度百科说的有点大,这么说吧,骚年,你有试过舍友在下片导致你团战卡输么?有试过你在下载东西,家人喊看电视卡吗?

QoS对前者能为每一个用户分配带宽,保证每个人不会受其他人使用网络的影响。

15

对后者,QoS能为不同的使用场景分配带宽或优先级,保证各个使用场景都能按需分配流量。

16

17

家庭使用选择后者,“客制化”按照自己的需求拖拽即可实现优先级划分。

梅林固件提供带宽监控,主要监控用户网络的访问内容类型,按比例和流量列出。家用可以关掉这一功能。

8. USB3.0干扰

在“无线网络”的“专业设置”里,2.4G频段下有“降低USB3.0干扰”这一选项,默认是启用的。

18

也就是说这种情况下,即使你将设备接入路由器的USB3.0接口,实际上也是走2.0的协议,传输速率基本30M/s左右。为什么2.4G频段会和USB3.0产生干扰?知乎这里已经有很好的答案了:https://www.zhihu.com/question/28422159 抛开理论,总之就是3.0的线本身做好了屏蔽,但两端的接口基本处于暴露状态,干扰主要产生在接口处。

如果想用USB3.0,需将该选项停用,但此时2.4G的WIFI信号会变得很差。有几种解决方法:

  • 不用2.4G只用5G。你是逗我么?不可能不用2.4G,老设备要吃土。

  • 不用USB3.0,只用2.0接口。还是逗我?做NAS下载和高清播放2.0的传输速率要死人的。看一下跑得太快,裤子追不上人的情况。淡蓝色的部分就是设备吞吐阻塞。

    19

  • 使用高质量的双端屏蔽USB3.0延长线,设备尽量远离路由器1米左右。可以是可以,成本不小。

  • 最后还是高手在民间,自己做一个法拉第笼。用锡箔纸包裹usb接口和移动硬盘。包严实点。像这样……

    20

    这个包的有点过了,下面这样就行……

    21

9. 插件中心

梅林的插件中心提供很多实用插件,包括上文提到的“穿透DDNS”和“frpc穿透”。启用插件中心流程:

  • “系统管理”的“系统设置”,勾选下图中的两个选项

    22

  • 保存设置后重启路由,记得是重启路由!

  • 然后就可以进入Software Center,安装软件中心这个平台

    23

  • 装完平台后,才可以安装插件

我一般常用三个插件:

  • koolproxy:去广告神器,配置很简单,全局模式+http足够日常使用。电视盒子、手机、电脑看上网或视频能屏蔽大部分广告,当然也有一部分不能去除。启用https需安装证书,相对麻烦,并且即便启用https还有有广告去不掉,所以可以不启用https过滤。全局模式也可以换成视频模式,理论上全局模式会比视频模式影响网速。

    24

  • Aria2:下载工具,有了它就可以搭建小型NAS,磁链、BT、百度网盘都可以下载,由于迅雷宣布不支持所有第三方下载工具,所以目前不能使用迅雷下载。具体使用将在系列第二篇搭建小型NAS中详细说明。

  • Shadowsocks:科学上网,这也是刷固件的最终目标。这款插件已经于2017.7.17号在插件中心下架,原因大家都懂的,具体的使用和安装方法将在系列第三篇PS4串流中详细说明。

这次就先折腾到这,所有设置都是为后续打基础。下期将介绍基于梅林搭建小型NAS,在外地,在公司,甚至在路上用手机都可以添加下载任务,通过Samba服务在局域网共享,家中的电视、手机、平板都可以方便使用NAS中的资源。

评论和共享

VS2017安装Coded UI Test需满足一个大条件:Visual Studio的版本必须是Enterprise版,只有企业版才能安装Coded UI Test。

安装Coded UI Test,需在VS安装界面,进入“单个组件”,勾选“编码的UI测试”,如下图所示:

然而安装完成后却发现新建项目时找不到“编码的UI测试”项目模板:

解决的方法是在安装VS时,在“语言包”界面中勾选一下“英语”,只勾选安装就好,可以不设置VS成英文:

安装完成后可在新建项目中找到“编码的UI测试”项目模板:

评论和共享

前言

项目地址

Github

Docker Game

  • 4:20起床,debug
  • 在google中搜索100条docker相关的post
  • 在社交软件的时间线上写道”fuck docker”
  • 看10本docker的书
  • 穿docker的T-shirt
  • 不跟任何人说你搞不定docker
  • 说出自己搞定docker的日期并接受它
  • 宣誓自己学会了docker
  • 最后砸烂你的电脑

NEVER TOUCH NON-LINUX DOCKER!


Docker简介

  1. Docker就是一个启动很快的“虚拟机”,它仅提供必要的功能,体积小;它能更充分利用宿主的资源。
  2. Docker原配“只有”linux。Mac和Win所谓的支持Docker归根到底是通过在虚拟机上安装linux系统,再在linux上安装docker。(这句话不完全正确,参见[解决方案与心路历程8]
  3. Docker三个基本概念:Registry注册中心,Image镜像,Container容器
    • Registry包含多个Repo(仓库)和Tag(标签),从Registry下载Image的方式是pull <仓库名>:<标签名>,如pull ubuntu:16.04
    • Image是一个静态概念,相当于面向对象中的类,类不能直接使用,需要实例化。
    • Container是一个动态概念,相当于面向对象中的实例化,启动镜像生成容器就是实例化类的过程。只有容器才能被用户使用。
  4. 使用Docker的容器Container,任何的操作和数据在退出或重启后不会保存,只能通过Volume(卷)的方式保存常驻文件并和宿主机共享文件。可以理解成网吧的电脑每次重启都自动还原系统,只有U盘里面的东西不会变。
  5. Docker的容器Container所做的任何修改都不会影响到Image。除非用commit命令,将修改提交,才能将修改应用到Image,类似git的管理方式。

一、 旧的开发环境及可能产生的问题

传统的前端开发环境搭建方式可能产生的问题主要有两点:

  1. 新同事、新电脑或重装系统后,开发环境的搭建显得很繁复。
  2. 每个前端开发人员可能由于操作系统的不同,开发环境版本及插件版本不同造成开发环境不统一。

早几年,大公司都会采用开发机和多用户的方式来统一开发环境。每个开发人员只需要一台显示器和外设,全部连接到中型或大型开发机上进行开发。这种方式经济成本较高,管理成本也高。而现在更提倡轻量管理。节约成本的前提下如何解决上述的两个问题?

二、 解决方案与心路历程

寻找一个优秀解决方案的过程向来是艰辛的,下面是我的心路历程:

  1. 思维导图[/vm]:最初考虑用虚拟机的方式,每个开发人员安装虚拟机和统一的镜像文件,镜像包含了统一的开发环境,本地开发目录和虚拟机共享,即可在虚拟机中对开发目录进行操作。这种方式简单易行。缺陷主要是性能相对会降低,同时对虚拟机镜像的维护不方便。虚拟机镜像如同一个黑盒,历史维护记录,目录结构不清楚,很容易引起混乱,当前的维护人员不清楚之前的维护人员到底安装了哪些东西,如何配置的等等。

  2. 思维导图[/docker]:虚拟机的方案被Pass之后,目光聚焦到Docker,如[简介1]所述,Docker的轻量和高性能值得一试。于是查找相关资料,看到《用 Docker 快速配置前端开发环境》一文,见[附录1]。文中主要提及两点:

    • 使用Docker的图形化工具Kitematic管理Docker并基础平台
    • 使用linux的samba共享文件夹操作工作目录
  3. 思维导图[/docker/docker toolbox]:Kitematic是Docker的UI壳子,上文提及使用Docker Toolbox进行安装,Docker Toolbox是Docker官方开发的 Docker套装,里面有全套Docker环境,也有图形化工具 Kitematic。注意Docker Toolbox在win下仅支持win7 X64到win10,这里开始就是茫茫多的坑了。如下图所示,Docker Toolbox默认安装Docker Client和Docker Machine。什么是Docker Machine?

  4. 思维导图[/docker/docker toolbox/docker machine]:弄懂Docker Machine,先要弄清楚什么是boot2docker,如[简介2]所述,Docker原生只支持linux,Docker的诞生基于linux的底层虚拟化技术。为了能在非linux系统运行Docker,boot2docker才诞生。boot2docker实际上就是打包一个Virtual Box或Vagrant虚拟机,在虚拟机上安装一个最小化linux,在linux上安装Docker,同时boot2docker负责将使用者本机的docker命令和虚拟机linux里面的docker“无缝”连接起来。这就是boot2docker。由于boot2docker非原生非官方,存在诸多bug,如早期boot2docker兼容docker命令不全,由于Virtual Box和Vagrant不稳定经常导致崩溃等等。2015年Docker官方发布了Docker Machine,它的实现原理和boot2docker基本一样,但它能更好的替代boot2docker。直到如今,很少再看到使用boot2docker了,而是使用官方的Docker Toolbox打包的Docker Machine。当然我们并不是再也见不到boot2docker,在Docker Toolbox安装后的目录里,我们还是能看到boot2docker.iso这个文件,也就是说Docker Machine最终安装在虚拟机里面的linux系统,还是boot2docker当初创建、发布并维护至今的linux最小系统的镜像文件。

  5. 思维导图[/docker/docker toolbox/docker machine/platform]:如你所想,无论是boot2docker还是Docker Machine,都是在非linux环境搭建虚拟机,再在虚拟的linux中安装Docker。这就与[简介1]的特性相矛盾。事实如此,非linux的Docker几乎不会用于当正式的生产环境,多用于研究Docker技术。那么这条路要走到尽头了?win10的出现带来了一些曙光,微软官方称win10“兼容”Docker,而Windows Server2016 更是宣称支持原生Docker。win7、win10、server 2016,这三者对Docker的支持究竟有什么不同?

  6. 思维导图[/docker/docker toolbox/docker machine/platform/win10]:在第4节中,介绍了早几年,win10还没发布,win又缺乏对Docker的支持,于是采用了boot2docker和Docker Machine来支持Docker。他们的原理是基于Virtual Box或Vagrant虚拟机。也说到Virtual Box或Vagrant的不稳定性带来的问题。自win8开始,操作系统自带Hyper-V虚拟机。至此,市面上三大虚拟机软件齐聚一堂。

    • Virtual Box开源免费、安装包小、相对不稳定
    • Hyper-V作为win自带软件,在win下最稳定,但对于图形界面渲染性能较差
    • VMware workstation商用、收费、各方面都不错

      由于VMware收费和商用的性质,所以boot2docker和Docker Machine官方发布都仅支持开源免费的Virtual Box,民间不少开发者制作了boot2docker和Docker Machine支持VMware workstation、Hyper-V乃至其他虚拟机的插件,这里不再过多叙述。Hyper-V在经历win8、win8.1的磨合后,新的版本无论兼容性、稳定性还是运行效率,都有了更好的提升。于是微软在win10里,可以直接将docker安装运行在Hyper-V中,并在操作系统中直接加入了本机和Hyper-V中的Docker通讯交互的功能。也就是说win10基于Hyper-V,实现了boot2docker或Docker Machine的功能,基于Hyper-V的Docker更稳定。但是,说到底,win10的Docker还是跑在虚拟机下啊!穿个马甲还不认识你了???

  7. 思维导图[/docker/docker toolbox/docker machine/platform/win10/hyperv]:所以win10可以在docker官网直接下载docker安装包,同时win10也支持Docker Toolbox使用Docker Machine的方式(参见[解决方案与心路历程3])。这里需要提醒一下,win10支持两种安装Docker的方式,而在[解决方案与心路历程3]中也提到Kitematic是docker的一个可视化UI,两种Docker安装Kitematic的方式也不一样,具体可参考[附录2]。简述步骤如下:

    • Docker Toolbox安装包自带Kitematic,可勾选后安装
    • win10的Docker安装包,安装完成后可在任务栏右键Docker,下载并安装Kitematic

      既然win10的Docker比基于Virutal Box的Docker Machine性能更好,那么自然而然就想考虑使用win10的Docker。然而却遇到一些不符合实际环境的问题:

    • 团队中确实有一些开发人员仍在使用win7

    • Hyper-V和VMware workstation冲突。由于Hyper-V独占硬件虚拟化,所以不能和VMware workstation或Virtual Box共存,而VMware workstation却能和Virtual Box共存(可能会产生网络冲突情况,但可以解决)。

      [解决方案与心路历程6]中有提到Hyper-V的图形界面渲染性能比较差,再加上部分开发人员需要在虚拟机中安装其他windows系统进行兼容性调试测试,甚至安装“黑苹果”,这方面Hyper-V基本没法满足。还有些开发人员使用基于Virtual Box的安卓模拟器,Hyper-V也无法与之共存。所以在实际环境面前,基于Hyper-V的Docker基本走到头了。

  8. 思维导图[/docker/docker toolbox/docker machine/platform/server2016]:win10的Docker方案被Pass后,接着[解决方案与心路历程5],看看Windows Server 2016的原生Docker是什么。[附录3]中,微软官方介绍了Windows Container。主要是说Windows Server 2016上安装Docker可以切换到Windows Container模式。这是一个怎样的功能?[简介2]中说道Docker原生只支持linux,这句话现在不一定对了。Windows Container让Docker可以直接跑在Windows Server 2016下,不需要依赖任何linux虚拟机。但是,但是,但是!Windows Container的Docker容器目前只能跑.net或.net core的应用。不能安装其他操作系统的镜像。哈?半成品?Pass!

  9. 思维导图[/docker/docker toolbox/kitematic]:Docker在win平台的3种“生命形态”已经全部了解完了,根据实际环境,为了兼容win7和VMware workstation,只能选择回到Docker Toolbox,拥抱Docker Machine。[解决方案与心路历程2]以及[附录1]中提到使用Kitematic可以更方便的管理Docker。《用 Docker 快速配置前端开发环境》[附录1]一文中,介绍了使用Kitematic搭建前端开发环境的整个流程。但是文中也提到“Windows 的 Kitematic 有严重 bug,改动 Settings 下的任何选项都会导致所有配置项丢失”,该文自2016.8提出,至今(2017.6)仍存在这个Bug。不能用图形化界面配置,用Kitematic启动Docker后还要执行脚本甚至写Docker命令?我要这UI有何用?我要这界面又如何???还不如基于Docker Machine,直接写一个脚本,前端开发人员按顺序点,就可以使用。所以至此,彻底摒弃Kitematic,参考Docker Toolbox安装目录里面的start.sh,重写一个脚本,完成所有功能。同时,我开始怀疑使用Docker搭建开发环境还有没有意义,毕竟基于Virtual Box再安装Docker,已经失去了Docker的优势。

  10. 思维导图[/docker/docker toolbox/docker machine/image]:《用 Docker 快速配置前端开发环境》[附录1]一文中,基于官方的centos或者ubuntu,提前搭建好前端的开发环境,commit后生成新的Image,提供给前端开发人员使用。这种方式与[解决方案与心路历程1]中提到的虚拟机镜像方式几乎一模一样,经过几番折腾,我已经对Docker搭建前端开发环境产生怀疑。非linux平台下,使用Docker本来就不存在性能优势,还存在镜像不透明会导致管理混乱的问题。这样算下来,还不如使用虚拟机。考虑再三,决定不再使用打包Image的方式,而是采用Dockerfile的方式生成镜像。这才是Docker的天生优势,也是我决定继续完成这个项目的唯一理由。Dockerfile可以理解成一个批处理,里面包含生成镜像的所有指令,build Dockerfile时,会顺序执行这些指令,最终将镜像生成出来。详细的说明可参考[附录4]。使用Dockerfile,能让维护人员清晰的看到镜像生成的过程,修改和管理起来就非常清晰了。

  11. 思维导图[/docker/docker toolbox/docker machine/volume][简介4]提到Docker的容器不会保存数据,持久化数据需要通过Volume方式挂载。也举了一个形象的例子,网吧电脑和U盘,在这里Volume就是U盘。Volume的挂载规则如下:

    • 挂载方式为 -v [宿主机路径]:[Docker容器路径]。 这里得理解宿主机是谁?由于我们使用的Docker实际上安装在虚拟机上,也就是说这里的宿主机路径是Virtual Box中linux虚拟系统内的路径。
    • Docker容器的路径必须是绝对路径
    • 可以挂载多个Volume,也就是可以插多个U盘
    • 可以不指定宿主机路径,这时候会在宿主机的默认路径下自动生成对应的volume路径,如/var/lib/docker/volumes/XXX

    根据上面的规则,可以看出Docker中的持久化数据通过挂载Volume,实际上存在宿主机的文件夹中,实现了Docker和宿主据的资源共享。也就是说我们已经把前端开发环境封装在Docker的Image中了,但是项目的源码不能放在Docker中,只能通过Volume的方式挂载到Docker中。那么我们怎么实现在我们本机编辑项目代码,并在Docker中build呢?

    [解决方案与心路历程2]中也提到《用 Docker 快速配置前端开发环境》[附录1]一文,使用Samba来实现这一流程。Samba是linux环境下的文件共享技术,在制作Image时,安装samba,并做好配置,将挂载的volume文件夹共享出来,这时候我们在本机就可以通过//192.168.99.100/share的方式进入Docker的文件夹系统,实现与Docker的数据交互。这是一个正向思维:

    • 通过-v [宿主机路径]:[Docker容器路径],先创建Docker的Volume
    • 在Docker中安装samba,将Volume共享出来
    • 在本机,通过读取网络共享文件夹的方式进入Docker的文件夹

    使用这种方法,数据其实是保存在宿主据路径,也就是linux虚拟机上。好处就是磁盘格式就是linux的磁盘格式。缺点也很明显,数据在虚拟机上,本机只是远程访问虚拟机数据而已。数据安全性很差,Virtual Box的稳定性大家都懂的,一旦虚拟系统奔溃或被误删。整个本地项目源码就没了!虽然我们可以通过git等工具上传,但容易丢失本地数据,这个也很致命!所以我们必须换一种方式,通过逆向思维:

    • 将本机的工作目录共享给Virtual Box 虚拟机
    • 再将Virtual Box 虚拟机中的这个目录挂载到Docker上

    这种方式,数据是保存在本机上的。好处当然是安全,缺点就是磁盘格式可能是windows下的NTFS,可能会产生一些问题。往下看,就踩坑了。

  12. 思维导图[/docker/docker toolbox/docker machine/volume/virtual box]:为何要把本机目录共享给Virtual Box虚拟机?如下图所示,我们在安装好Docker Toolbox后,手动打开Virtual Box,查看名称为default的默认linux虚拟系统的配置。在共享文件夹一栏中可以看到,默认配置中,已经将本机C盘的User文件夹共享给linux虚拟系统。本机的其他目录并没有和虚拟机有连接。也就是说Docker通过Volume与宿主机共享文件,要想Docker和本机有数据交互,就先把本机目录共享给虚拟机(宿主机),再由虚拟机挂载到Docker上。

    通过手动挂载本机目录给Virtual Box,可以满足我的需求,但我更希望使用脚本的方式,自动完成这一工作,参考Virtual Box官方的文档,使用VBoxManage sharedfolder add命令可以实现脚本自动化挂载。详情参见[附录5]

  13. 思维导图[/docker/docker toolbox/docker machine/volume/virtual box/symlink]:成功将本机目录与虚拟机中的Docker连接后,本以为万事大吉,不料却又是一个个大坑。在Docker中,切换到共享目录,对现有的前端项目使用npm install安装所需的module,提示报错Error: EROFS, read-only file system ...,经过查阅资料,原来是Virtual Box自4.1.8版本起,为了安全性,禁止在虚拟机挂载共享目录中创建symlink,此时可以继续使用VBoxManage命令来解决,VBoxManage setextradata VM_NAME(虚拟机名称) VBoxInternal2/SharedFoldersEnableSymlinksCreate/SHARE_NAME(共享文件夹名称) 1,详情见[附录6]

  14. 思维导图[/docker/docker toolbox/docker machine/volume/virtual box/ntfs]:给虚拟机开启symlink后,继续遇到问题,仍提示symlink创建不成功,仔细想一下,NTFS是windows磁盘格式,它与linux的符号连接symlink本身就不一样,这个底层不兼容问题就是在[解决方案与心路历程11]中提到的又一个坑。再次查阅资料,通过第三方开源软件NTFS-3G挂载NTFS磁盘,可以使linux读写NTFS。但Virtual Box是虚拟机,没有物理磁盘一说,怎么挂载就成了问题。再考虑一下,既然linux中有NTFS-3G这类的软件支持NTFS,难道Virtual Box就没有想到?最终问题还是得到了解决,解决的方式很奇葩,用管理员权限打开Virtual Box就行了。详情见[附录7]

  15. 思维导图[/docker/docker toolbox/docker machine/volume/virtual box/uac]:在[解决方案与心路历程12]中,我们使用了VBoxManage sharedfolder add命令来实现自动挂载本机目录给虚拟机,所以当初的设置是打开脚本,提示用户输入本机工作目录。为了方便,可以直接拖拽这个文件夹到控制台中。然而,为了使用管理员权限打开Virutal Box,我不得不把管理员权限挂到脚本上,然后就会产生一个老问题,拖拽文件夹的功能不能用了。这是因为脚本是管理员权限,而文件夹是用户权限,权限不同级,不能实现拖拽。具体原理可以参考我之前的一篇文章[附录8]。解决的方法只有三种:

    • win7可以直接使用管理员登陆,除了安全性较差,没其他问题。由于全局管理员权限,可以实现拖拽。
    • win8/8.1/10,也可以使用管理员登陆,也是安全问题。但还有一个问题,使用admin登陆后,不能使用Metro UI
      (Modern)里面的应用。
    • 放弃拖拽,手动输入路径,这里就需要注意,由于linux和windows磁盘目录管理方式不同,比如本机的工作路径是e:\workplace,那么在脚本的启动终端git bash中,对应的路径应该是/e/workplace
  16. 思维导图[/other]:当然,整个过程遇到的坑并不仅是上述这些,再节选一部分问题和解决方案,说明如下:

    • 使用Kitematic时,启动卡在99%或100%的问题:我是手动删除Virtual Box里面的default虚拟系统,再重启解决。可以参考[附录9][附录10]
    • 在Docker中使用CentOS 7的systemctl提示权限不足或者提示Failed to get D-Bus connection: Operation not permitted的问题:Docker下的每种系统镜像基本都有坑,比如centos7就是这个问题,据说centos7.2会解决,但目前7.3仍是这个问题。解决方案见[附录11]
    • 在本机的git bash中使用-it与docker交互,提示the input device is not a TTY的问题:windows下使用sh可以通过git自带的git bash,windows在安装git时,可选择两种终端模式:

      默认使用MinTTY,在这种模式下,使用docker run -it XXX时,就会报上述错误。具体解决方案见[附录12]。要不就是重新安装git,更换终端模式为Windows' default,要不就是在执行交互模式的docker命令前加winpty,如winpty docker run -it XXX。为了统一起见,我在脚本启动docker容器并实现交互时,都在前面添加winpty

三、 项目简介(以Github最新Readme为准)

项目地址

Github

基本功能:

基于Docker Toolbox,在start.sh的基础上实现了以下功能:

  1. 自动启动Docker Machine(Virtual Box)
  2. 设置本地工作目录,自动共享至虚拟机Docker Machine(Virtual Box)的/develop
  3. 基于centos:latest和自定义的Dockerfile一键安装前端开发环境
  4. 一键启动并进入前端开发环境
  5. 进入Docker Machine终端
  6. 重启Docker Machine

镜像的修改和安装软件如下:

  1. 使用centos:latest
  2. 替换yum源为163软件源
  3. 安装常用工具如curl、gcc等
  4. 安装nginx,初始版本为1.13.1
  5. 安装nodejs,初始版本为v6.11.0
  6. 安装cnpm
  7. 安装pm2工具

项目目录结构如下:

  • setup
    • DockerToolbox.exe
  • source
    • CentOS7-Base-163.repo
    • nginx.conf
    • nginx-1.13.1.tar.gz
    • node-v6.11.0-linux-x64.tar.xz
  • boot2docker.iso
  • centos.tar
  • docker_machine_shell.sh
  • Dockerfile
  • nginx_config.conf
  • start.bat

支持的操作系统:

win7 x64及以上

其他说明及限制:

  • 在BOIS中开启虚拟化技术
  • win8及以上需关闭Hyper-V
  • 默认只开启了Docker的80端口,其他端口不可访问,如需修改请参考“开发者文档”

综述:

由于项目使用Docker Machine在非linux环境下安装Docker,不能提现出Docker的秒级启动特性,同时由于Docker Machine基于Virtual Box,稳定性和综合性能都有所损耗。使用Dockerfile创建前端开发环境所需的镜像文件,使维护和管理更加清晰方便是唯一的优势。 所以该项目更多用于团队内学习和了解Docker的使用。

四、 使用者文档(以Github最新Readme为准)

  1. 运行setup文件夹下的DockerToolbox.exe安装DockerToolbox。如果系统中已安装Virutal Box和Git,则不需要勾选,否则一定要注意勾选这两项。

  2. 运行start.bat,等待自动安装,直至出现如下的界面

  3. 输入1,回车,绿色文字提示输入工作目录。注意这里输入的是所有项目所在的文件夹。路径使用linux规范,如本机工作目录是e:\Repository,则应该输入/e/Repository。如果路径输入错误或路径不存在,则会提示,并要求重新输入。随后等待直至出现绿色文字提示Set Sharedfolder Success!。按回车继续。

  4. 输入2,回车,等待安装开发环境的镜像,直至出现绿色文字Setup Develop Dockerfile Sucess!。按回车继续。

  5. 输入3,回车,进入Docker容器,绿色文字提示WelCome to Docker! IP: 192.168.99.100,即为成功,使用这个IP就可以访问Docker。使用命令cd /develop,即可进入第3步中挂载的本地工作目录。注意,步骤1-4只需要执行一次,安装完成后,需要时只执行这一步骤即可进入Docker!除非需要更换工作目录,或更新、重装整个环境,按需要再次执行1-4步。

    • 通过cd命令进入所需的项目
    • 使用npm或cnpm install 必要的module
    • 根据实际要求编译或启动整个项目
    • 复制该Docker项目文件夹根目录的nginx_config.conf到工作目录的根目录,修改文件名为项目名称.conf(如e:\Repository\website.conf)
    • 编辑该文件,不要修改listen端口,修改server_name为所需的域名,修改location为本地nodejs服务路径。
    • 在控制台输入命令nginx -s reload,重启Docker中的nginx服务器,若无任何消息提示,则表示配置文件无误,重启完成。
    • 随后可在本机修改hosts文件,将nginx配置文件中填写的域名指向到Docker的IP。
    • 在本机浏览器输入域名,即可访问。
  6. 在控制台菜单输入4,即可进入Docker Machine的终端,一般用于维护,查看等进阶操作。

  7. 在控制台菜单输入5,可重启Docker Machine。某些时候重启以解决奔溃或者虚拟机异常等问题。

  8. 在控制台菜单输入6,退出控制台。

五 、 开发者文档(以Github最新Readme为准)

维护建议说明:

  • 建议维护:代表建议开发者定期更新或修改
  • 一般维护:代表开发者无需频繁维护或可以对该文件不予理会
  • 不建议维护:代表不建议开发者修改该文件

维护文件:

  1. [setup\DockerToolbox.exe](建议维护):可在docker官网下载,由于国内网络部分被墙,速度非常慢,需要ss或vpn访问。开发者可定期下载,更新替换项目中的DockerToolbox安装包。国内网络也可在代理网站下载

  2. [source\CentOS7-Base-163.repo](一般维护):centos7的163yum源

  3. [source\nginx-1.13.1.tar.gz](一般维护):nginx_1.13.1安装包

  4. [source\node-v6.11.0-linux-x64.tar.xz](一般维护):nodejs_6.11.0安装包

  5. [source\nginx.conf](一般维护):nginx配置,用于替换Docker中安装的nginx_1.13.1的默认配置文件。主要增加一行配置include /develop/*.conf;,将/develop文件夹下的所有配置文件加载进来。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    #user nobody;
    worker_processes 1;
    error_log logs/error.log;
    pid logs/nginx.pid;
    events {
    worker_connections 1024;
    }
    http {
    include mime.types;
    default_type application/octet-stream;
    access_log logs/access.log;
    sendfile on;
    keepalive_timeout 65;
    server {
    listen 80;
    server_name localhost;
    location / {
    root html;
    index index.html index.htm;
    }
    #error_page 404 /404.html;
    # redirect server error pages to the static page /50x.html
    #
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    root html;
    }
    }
    include /develop/*.conf;
    }
  6. [boot2docker.iso](建议维护):boot2docker发布的用于安装docker的linux最小系统。Docker Toolbox安装后,目录中已经有boot2docker.iso文件,但有可能版本不是最新,启动Docker Machine时会检查对应的boot2docker.iso的版本,如果不是最新,会从github下载,还是国内网速影响,经常会失败。所以开发者可以定期通过ss或vpn下载新版的boot2docker.iso,启动脚本时,会自动将该目录的boot2docker.iso拷贝到Docker Machine中。

  7. [centos.tar](不建议维护):制作的centos:latest 文件的导出包。如果需要更新最新的centos:latest版本,或者使用其他版本如centos:6.5。可以参考“使用者文档”,通过控制台选择4. Enter Docker Machine,进入Docker Machine终端:

    • 键入命令docker images 查看是否存在centos:latest镜像
    • 如果存在,键入命令docker rmi -f centos:latest删除现有的centos:latest
    • 键入命令docker pull centos:latestdocker pull centos:6.5下载最新的centos或指定版本的centos
    • 若下载非centos:latest,建议使用命令docker tag centos:6.5 centos:latest将其命名成latest,因为在[docker_machine_shell.sh]和[Dockerfile]中,很多指令基于centos:latest,以免引起不必要的麻烦。如果你很熟悉docker命令,可以修改[docker_machine_shell.sh]和[Dockerfile]的内容。
    • 键入命令docker save centos:latest > /e/centos.tar,就可以将centos:latest导出到E盘根目录,名称为centos.tar
  8. [docker_machine_shell.sh](不建议维护):基于Docker Toolbox安装目录的start.sh修改的脚本。代码中凡是添加了##Custom Addition Begin标注的都是自定义的内容并配有注释,其他的均是Docker Toolbox的start.sh源码。部分自定义源码片段如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ##Custom Addition Begin
    #check docker toolbox
    if [ -z "${DOCKER_TOOLBOX_INSTALL_PATH}" ]; then
    echo "Docker ToolBox is not installed. Please re-run the Toolbox Installer and try again."
    read -p "Press enter to continue."
    exit
    fi
    cd "${DOCKER_TOOLBOX_INSTALL_PATH}"
    ##Custom Addition End
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    ##Custom Addition Begin
    #use local boot2docker.iso
    echo -e "${GREEN}Copy local boot2docker.iso... ${NC}"
    currentFolder="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
    if [ ! -e "${currentFolder}/boot2docker.iso" ]; then
    echo -e "${GREEN}Can not find boot2docker.iso in ${currentFolder} ${NC}"
    else
    if [ ! -d "~/.docker/machine/cache" ]; then
    mkdir -p ~/.docker/machine/cache
    fi
    cp "${currentFolder}"/boot2docker.iso ~/.docker/machine/cache/
    fi
    ##Custom Addition End
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    ##Custom Addition Begin
    #Boot Manager
    STEP="Boot_Manager"
    Boot_Manager(){
    clear
    echo -e "${BLUE}-----Docker Machine Manager-----${NC}"
    echo -e "${BLUE}1. Set Sharedfolder ${NC}"
    echo -e "${BLUE}2. Setup Develop Environment ${NC}"
    echo -e "${BLUE}3. Enter Develop Environment ${NC}"
    echo -e "${BLUE}4. Enter Docker-Machine Bash ${NC}"
    echo -e "${BLUE}5. Restart Docker-Machine ${NC}"
    echo -e "${BLUE}6. Exit ${NC}"
    read choose
    case $choose in
    1) #1. Set Sharedfolder
    hasNoDir=true
    while ${hasNoDir}
    do
    echo -e "${GREEN}Input hostPath:${NC}"
    read hostPath
    hostPath=`echo $hostPath | sed "s/\"//g" | sed "s/'//g"`
    if [ -d "$hostPath" ]; then
    hasNoDir=false
    else
    hasNoDir=true
    echo -e "${GREEN}Can not find dir: ${hostPath} ${NC}"
    fi
    done
    if [ "${VM_STATUS}" == "Running" ]; then
    "${DOCKER_MACHINE}" stop "${VM}"
    fi
    #remove sharedfolder named develop
    "${VBOXMANAGE}" sharedfolder remove "${VM}" --name develop
    #add sharedfolder named develop
    "${VBOXMANAGE}" sharedfolder add "${VM}" --name develop --hostpath "${hostPath}" --automount
    #support symlink
    "${VBOXMANAGE}" setextradata "${VM}" VBoxInternal2/SharedFoldersEnableSymlinksCreate/develop 1
    #start vm
    "${DOCKER_MACHINE}" start "${VM}"
    yes | "${DOCKER_MACHINE}" regenerate-certs "${VM}"
    eval "$(${DOCKER_MACHINE} env --shell=bash --no-proxy ${VM})"
    echo -e "${GREEN}Set Sharedfolder Sucess! ${NC}"
    read -p "Press enter to continue."
    Boot_Manager
    ;;
    2) #2. Setup Develop Environment
    currentFolder="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
    if [ ! -e "${currentFolder}/Dockerfile" ]; then
    echo -e "${GREEN}Can not find dockerfile in ${currentFolder} ${NC}"
    read -p "Press enter to continue."
    Boot_Manager
    fi
    if [ ! -e "${currentFolder}/centos.tar" ]; then
    echo -e "${GREEN}Can not find centos.tar in ${currentFolder} ${NC}"
    read -p "Press enter to continue."
    Boot_Manager
    fi
    #rm image named centos:latest
    docker rmi -f centos:latest || true
    #rm image named centos:heygears
    docker rmi -f centos:heygears || true
    #load image
    docker load < "${currentFolder}/centos.tar"
    #build dockerfile to generate centos:heygears
    cd "${currentFolder}"
    docker build --no-cache --rm -t centos:heygears .
    cd -
    echo -e "${GREEN}Setup Develop Dockerfile Sucess! ${NC}"
    read -p "Press enter to continue."
    Boot_Manager
    ;;
    3) #3. Enter Develop Environment
    #rm container named heygears
    docker rm -f `docker ps -a -f name=heygears -q` || true
    #run centos:heygears in docker machine
    #--privileged=true means run docker with the highest privileges
    #-p 80:80 means expose port 80
    #-v /develop:/develop means mount docker machine's path "/develop" to docker "/develop" based on setp 1(Set Sharedfolder)
    docker-machine ssh "${VM}" 'docker run -d --name heygears --privileged=true -p 80:80/tcp -v /develop:/develop centos:heygears'
    #show docker ip
    echo -e "${GREEN}WelCome to Docker! IP: `docker-machine ip` ${NC}"
    #use winpty enter container
    winpty docker exec -it heygears bash
    Boot_Manager
    ;;
    4) #4. Enter Bash
    cat << EOF
    ## .
    ## ## ## ==
    ## ## ## ## ## ===
    /"""""""""""""""""\___/ ===
    ~~~ {~~ ~~~~ ~~~ ~~~~ ~~~ ~ / ===- ~~~
    \______ o __/
    \ \ __/
    \____\_______/
    EOF
    echo -e "${BLUE}docker${NC} is configured to use the ${GREEN}${VM}${NC} machine with IP ${GREEN}$(${DOCKER_MACHINE} ip ${VM})${NC}"
    echo "For help getting started, check out the docs at https://docs.docker.com"
    echo
    docker () {
    MSYS_NO_PATHCONV=1 docker.exe "$@"
    }
    export -f docker
    if [ $# -eq 0 ]; then
    echo "Start interactive shell"
    exec "$BASH" --login -i
    else
    echo "Start shell with command"
    exec "$BASH" -c "$*"
    fi
    ;;
    5) #5. Restart Docker-Machine
    if [ "${VM_STATUS}" == "Running" ]; then
    "${DOCKER_MACHINE}" stop "${VM}"
    fi
    "${DOCKER_MACHINE}" start "${VM}"
    yes | "${DOCKER_MACHINE}" regenerate-certs "${VM}"
    eval "$(${DOCKER_MACHINE} env --shell=bash --no-proxy ${VM})"
    ;;
    6) #6. exit
    exit
    ;;
    *) #other operation
    Boot_Manager
    ;;
    esac
    }
    Boot_Manager
    ##Custom Addition End
  9. [Dockerfile](建议维护):默认基于centos:latest,生成前端开发环境系统镜像的指令集。如需安装新版nginx或nodejs,执行其他系统命令,或安装其他软件,可以直接修改Dockerfile。使用者执行“使用者文档”步骤4,重新安装开发环境即可完成更新。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    FROM centos:latest
    MAINTAINER wurang
    #复制本地yum源、nodejs安装包、nginx安装包
    COPY source/CentOS7-Base-163.repo /etc/yum.repos.d
    COPY source/node-v6.11.0-linux-x64.tar.xz /usr/local
    COPY source/nginx-1.13.1.tar.gz /usr/local
    #nodejs 环境变量
    ENV PATH="/usr/local/node-v6.11.0-linux-x64/bin:$PATH"
    #nginx 环境变量
    ENV PATH="/usr/local/nginx/sbin:$PATH"
    #支持库
    #更新源、安装常用库、安装nginx、nodejs、cnpm、pm2、nginx
    RUN cd /etc/yum.repos.d/ \
    && mv CentOS-Base.repo CentOS-Base.repo.bak \
    && mv CentOS7-Base-163.repo CentOS-Base.repo \
    && yum clean all \
    && yum makecache \
    && yum -y update \
    && yum -y install gcc-c++ \
    && yum -y install pcre pcre-devel \
    && yum -y install zlib zlib-devel \
    && yum -y install openssl openssl--devel \
    && yum -y install autoconf libjpeg libjpeg-devel libpng libpng-devel \
    && yum -y install freetype freetype-devel curl curl-devel libxml2 libxml2-devel \
    && yum -y install glibc glibc-devel glib2 glib2-devel bzip2 bzip2-devel ncurses ncurses-devel \
    && cd /usr/local \
    && tar -zxvf nginx-1.13.1.tar.gz \
    && cd nginx-1.13.1 \
    && ./configure \
    && make \
    && make install \
    && cd /usr/local \
    && rm -rf /usr/local/nginx-1.13.1.tar.gz \
    && rm -rf /usr/local/nginx-1.13.1 \
    && xz -d node-v6.11.0-linux-x64.tar.xz \
    && tar -xvf node-v6.11.0-linux-x64.tar \
    && rm -rf node-v6.11.0-linux-x64.tar \
    && npm install -g cnpm --registry=https://registry.npm.taobao.org \
    && cnpm install -g pm2 \
    && yum clean all
    #复制nginx配置
    COPY /source/nginx.conf /usr/local/nginx/conf
    CMD ["nginx", "-g", "daemon off;"]
  10. [nginx_config.conf](一般维护):使用者基于该配置文件,在工作目录的根目录,为每一个项目配置nginx,通过代理,访问nodejs。

  11. [start.bat](不建议维护):为了解决NTFS磁盘在Virtual Box共享目录以及linux下的种种问题,需使用管理员权限打开Virtual Box。于是在[docker_machine_shell.sh]外,再包了一层批处理,自动使用windows管理员权限。

附录

  1. 用 Docker 快速配置前端开发环境
  2. Install Kitematic on Windows 10, 8, and 7 all Editions? GUI for Docker
  3. Windows Containers on Windows Server
  4. 使用 Dockerfile 定制镜像
  5. VBoxManage命令详解
  6. Creating symlinks in Virtual Box 4.1.8
  7. Correct way to setup Virtualbox 4.3 to use symlinks on guest
  8. 管理员权限打开VisualStudio无法访问网络磁盘的解决办法
  9. Stuck at 99%
  10. Windows 10 Starting Docker VM freezes @ 100%
  11. CentOS 7.2 Base Image Dockerfile with systemd Enabled
  12. Docker for Windows: Interactive Sessions in MinTTY Git Bash

评论和共享

1. 一些基本概念

a. 数据挖掘

从海量数据找寻有用信息。从事该类工作的技术是BI(商业智能)。简单说,在Excel中分析数据,找寻有用信息,使用SQL筛选数据,从而指导企业工作,这就是数据挖掘。

b. 机器学习

是计算机科学和统计学的交叉学科,基本目标是学习一个x->y的函数(映射),来做分类或者回归的工作。而目前,相对于深度学习来说,机器学习更多指传统传统模型和Pipeline模型。

Q: 什么是分类或者回归的工作?

A: 公交车上,一个长头发青年给小朋友让了座位,小朋友说谢谢姐姐,青年说不是姐姐,是哥哥,对小朋友来说,这个学习过程就是分类和回归。

Q: 为什么数据挖掘总和机器学习一起被提到?

A: 很多数据挖掘工作是可以通过机器学习提供的算法工具实现。

c. 深度学习

是机器学习中神经网络的衍生。在图像,语音等富媒体的分类和识别上取得了非常好的效果。也是最近一年跟随Alpha Go炒得最火的Topic

机器学习框架和深度学习框架之间的区别

  • 机器学习框架涵盖用于分类,回归,聚类,异常检测和数据准备的各种学习方法,并且其可以或可以不包括神经网络方法。
  • 深度学习或深度神经网络(DNN)框架涵盖具有许多隐藏层的各种神经网络拓扑。这些层包括模式识别的多步骤过程。网络中的层越多,可以提取用于聚类和分类的特征越复杂。
  • 一般来说,深层神经网络计算在GPU(特别是Nvidia CUDA通用GPU,大多数框架)上运行的速度要比CPU快一个数量级。一般来说,更简单的机器学习方法不需要GPU的加速。

2. 常见的机器学习框架

  • JAVA阵营:Spark MLlib是Spark的开源机器学习库,提供了通用的机器学习算法,如分类、回归、聚类和协同过滤(但不包括DNN)以及特征提取、转换、维数降低工具,以及构建、评估和调整机器学习管道选择和工具。

  • Python阵营:Scikit-learn是一个强大的,成熟的机器学习Python库。包含各种各样成熟的算法和集成图。它相对容易安装、学习和使用,带有很好的例子和教程。

3. 常见的深度学习框架

  • TensorFlow 是Google第二代深度学习框架
  • Caffe最初是一个强大的图像分类框架,但项目几乎停滞。最新活跃版本为Caffe2
  • MXNet类似TensorFlow
  • Torch7为数不多支持lua的框架,PyTorch是面向Python的API
  • Deeplearning4J支持java和scala,是Spark在深度学习领域的扩展

4. 机器学习使用与研究

a. 使用

对于使用者来说,可以不精通机器学习的数学模型与实现原理,只需熟悉在不同的业务场景使用不同的模型工具即可。

b. 研究

研究机器学习,是将现实需求转化成数学模型,并不断优化的过程。

5. Python与机器学习

Q: 为什么很多机器学习的框架都用到python?

A: Matlab应该是最适合做机器学习研究的工具,但考虑开发效率,价格,生态等因素,很多框架选用了python作为机器学习开发语言。

6. Scikit-Learn应用示例

使用机器学习框架究竟能解决什么样的问题,带来哪些便利?我们通过一个图像识别的例子来说明。

a. 业务需求

我们想让计算机识别一些人工手写的数字图片,计算机需要通过学习,判断图片中的内容是否是人工手写的数字,并将其识别出来。可以理解成车牌识别的简单版。

b. 字迹识别与MNIST数据集

这里我们需要用到一些数据,用于提供给计算机学习和测试最终结果。这个数据集是MNIST数据集。

MNIST数据集是混合的国家标准和技术 (简称 MNIST) 由红外研究员,作为基准来比较不同的红外算法创建数据集。

其基本思想是如果你有你想要测试红外的算法或软件的系统,可以运行您的算法或系统针对 MNIST 的数据集和比较您的结果与其他系统以前发布成果。

数据集包含的共 70,000 图像,其中有60,000 个训练图像 (用于创建红外模型) 和 10,000 个测试图像 (用于评估模型的精度)。

每个 MNIST 图像是一个单一的手写的数字字符的数字化的图片。每个图像是 28 x 28 像素大小。每个像素值是 0,表示白色,至 255,表示黑。中间像素值表示的灰度级。

我们这里用到一个简化版,存储在csv文件中,包含42000条数据,每条数据有785列,其中第1列为该数字图片的真实数字结果,第2至第785(28*28像素)列用来存该图片的灰度值。

该文件可在此处下载

c. 前期准备

有了数据之后,我们将通过传统的数学模型和用机器学习框架两种方式来实现我们的需求。由于程序均使用Python来实现,需要注意安装某些Python库,如NumpyPandas包括后面所需的Scikit-Learn

在windows下由于缺少编译环境,不能直接用pip install安装上述的库,可以在这里下载非官方已预编译的库进行安装。

d. 传统数学模型解决方案

关于数学模型实现和解释说明可以参考http://www.jianshu.com/p/4afc39897b3e

这里仅列出使用KNN算法实现的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import numpy as np
import pandas as pd
#import time
#归一化
def normalize(x):
norms = np.apply_along_axis(np.linalg.norm,1,x)
return x / np.expand_dims(norms,1)
def nearest_neighbor(norm_func,train_data,train_label,test_data):
train_data = norm_func(train_data)
test_data = norm_func(test_data)
cos = np.dot(train_data,np.transpose(test_data))
max_cos = np.argmax(cos,axis=0)
test_pred = train_label[max_cos]
return test_pred
'''
train_label=np.array([[1],[2],[3]])
b=np.array([1,2])
a=train_label[b]
得到的结果是
([[2],[3]])
说明数组可以作为读取另一个数组的index
'''
def validate(test_pred,test_label):
c=len(test_pred)
correct=(test_pred == test_label).sum()
return float(correct)/c#必须转变成浮点数再做除法,之前使用correct/c得到0
'''
train_label=np.array([[1],[2],[3,2]])
c = len(train_label)
结果为3
train_label=np.array([1,3,2])
c = len(train_label)
结果为3
train_label=np.array([[1,3,2]])
c = len(train_label)
结果为1
说明:在数组里面套数组的时候,len得到的是大数组里数组的个数,在只有一层数组的时候,得到的是数组中元素的个数
'''
if __name__ == '__main__':
train_num = 200
test_num = 300#测试数据起始是test_num-train_num
x = pd.read_csv('train.csv')#是将得到的所有数据做归一化处理还是要使用的数据做归一化处理
#x = normalize(r)#将所有数据做归一化处理
x_train = x.values[0:train_num,1:]#读取pandas中读取出来的数据,需要用data.train
x_train_label = x.values[0:train_num,0]#第一列是label,每幅图的数据是一行
x_test = x.values[train_num:test_num,1:]
x_test_label = x.values[train_num:test_num,0]
test_pred=nearest_neighbor(normalize,x_train,x_train_label,x_test)
prec=validate(test_pred,x_test_label)
print u"正确率为%.2f"%(prec)#浮点数是%f

条条大道通罗马,解决这个问题还可以使用其他的数学模型,对于机器学习研究人员来说,就是要将更多的问题模型化,并不断优化模型和方法。

e. Scikit-Learn解决方案

上一小结使用了最原始的方法来实现我们的需求,可以看到代码相对较多也相对复杂。同时我们还需要去建立一个数学关系。使用门槛太高,这大大降低了实用性。所以机器学习框架就是将这些常用模型封装起来,能让开发者更方便的调用。

下面就是使用Scikit-Learn封装的KNN算法实现需求的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import pandas as pd
from sklearn.decomposition import PCA
from sklearn.neighbors import KNeighborsClassifier
#导入Scikit learn库中的KNN算法
import time
if __name__ =="__main__":
train_num = 20000
test_num = 30000
data = pd.read_csv('train.csv')
train_data = data.values[0:train_num,1:]
train_label = data.values[0:train_num,0]
test_data = data.values[train_num:test_num,1:]
test_label = data.values[train_num:test_num,0]
t = time.time()
pca=PCA(n_components = 0.8)
train_x = pca.fit_transform(train_data)
test_x = pca.transform(test_data)
neighbors = KNeighborsClassifier(n_neighbors=4)
#找到一个点的K近邻,n_neighbors近邻的数目
neighbors.fit(train_x,train_label)
#对训练集的输入和输出进行训练
pre= neighbors.predict(test_x)
#对测试集的输入进行预测,返回预测出的标签
acc = float((pre==test_label).sum())/len(test_x)
print u'准确率:%f,花费时间:%.2fs' %(acc,time.time()-t)

更多介绍请参考http://www.jianshu.com/p/d8664bf9adf9

通过使用Scikit-Learn,大大简化了代码量和建模工作,让开发者能更多的关注使用场景,解决实际需求。

评论和共享

WPF Image异步加载控件

发布在 program

ImageLoadingControl使用说明

控件提供Source属性,可Binding图片Url或Path

1
2
3
<ImageLoadingControl:ImageLoadingControl HorizontalAlignment="Left" Height="200" Margin="30,68,0,0" VerticalAlignment="Top" Width="200" Source="{Binding ImageUrl1}"/>
<ImageLoadingControl:ImageLoadingControl HorizontalAlignment="Left" Height="200" Margin="303,68,0,0" VerticalAlignment="Top" Width="200" Source="{Binding ImageUrl2}"/>
<ImageLoadingControl:ImageLoadingControl HorizontalAlignment="Left" Height="200" Margin="556,68,0,0" VerticalAlignment="Top" Width="200" Source="{Binding ImageUrl3}"/>

1
2
3
4
5
6
private void button_Click(object sender, RoutedEventArgs e)
{
ImageUrl1 = @"https://pixabay.com/static/uploads/photo/2016/02/09/13/45/rock-carvings-1189288_960_720.jpg";
ImageUrl2 = @"https://pixabay.com/static/uploads/photo/2016/02/14/14/32/construction-1199586_960_720.jpg";
ImageUrl3 = @"c:\test.jpg";
}

下图分别显示了控件加载中,加载完成,加载失败三种状态:

控件代码和demo

评论和共享

升级Hexo3.X后发现Landscape-plus主题的分页出现一些问题,由于该主题的GitHub项目也处于长期未维护状态,所以只能自己排查修改。

一、Archive分页问题

进入Archives页面后,发现文章列表没有以标题“方块”展示,而是和主页一样以标题和内容展示:

对于这种情况,需要修改Hexo根目录的_config.yml,加入或修改参数如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Archives
## 2: Enable pagination
## 1: Disable pagination
## 0: Fully Disable
archive: 1
category: 1
tag: 1
#主页每页显示文章数
index_generator:
per_page: 10
#archive分页每页显示文章数
archive_generator:
per_page: 30
#tag分页每页显示文章数
tag_generator:
per_page: 30
#category分页每页显示文章数
category_generator:
per_page: 30

修改完成后,预览Archives页面正常:

二、Tag与Category分页问题

问题一中配置所示,设置了tag和category分页显示文章数为30,当该分页下文章总数超过这个设置时,正确的显示应该有分页跳转菜单,但目前Landscape-plus的版本存在问题,在这条issues中,也有网友提出了临时的解决方法,但要彻底从根本上解决问题,还需要修改代码:

打开 \themes\landscape-plus\layout\_partial\archive.ejs

如下图所示,加入红框中的代码:

代码可复制:

1
2
3
4
5
6
7
8
<% if (page.total > 1){ %>
<nav id="page-nav">
<%- paginator({
prev_text: '&laquo; Prev',
next_text: 'Next &raquo;'
}) %>
</nav>
<% } %>

修改完成后,预览Tag或category页面都恢复正常:

最后附上修改版的LandScape-Plus

评论和共享

在WPF中进行图片的相关操作是一件比较麻烦的事,并不是说它复杂,而是不注意的话很容易引起内存暴涨甚至溢出。关于BitmapImage使用的相关说明如下:

一、 创建方式

使用Uri设置BitmapImage会自动形成缓存,不关闭整个模块的话GC不会回收。 故如果在单个模块多次显示图片,不要使用这种方式:

var bitmap = new BitmapImage(new Uri(@"c:\test.bmp"));

建议通过流的方式加载图片:

1
2
3
4
5
6
7
8
9
10
11
12
13
byte[] imageData;
using (var fileStream = new FileStream(@"c:\test.bmp", FileMode.Open, FileAccess.Read))
using (var binaryReader = new BinaryReader(fileStream))
{
imageData = binaryReader.ReadBytes((int)fileStream.Length);
}
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.StreamSource = new MemoryStream(imageData);
bitmap.EndInit();

二、 释放方式

创建完BitmapImage对象后必须消除所有对该对象的引用后GC才会回收。

这里举个例子:

XAML:

1
2
3
4
5
6
7
<ListBox x:Name="listBox" HorizontalAlignment="Left" Height="411" Margin="45,24,0,0" VerticalAlignment="Top" Width="336">
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding ImgSource}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

后台代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class test
{
public ImageSource ImgSource { get; set; }
}
List<test> lstImage = new List<test>();
for (int i = 0; i < 100; i++)
{
test t = new test();
byte[] imageData;
using (var fileStream = new FileStream(lstTest[i], FileMode.Open, FileAccess.Read))
using (var binaryReader = new BinaryReader(fileStream))
{
imageData = binaryReader.ReadBytes((int)fileStream.Length);
}
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.StreamSource = new MemoryStream(imageData);
bitmap.EndInit();
t.ImgSource = bitmap;
lstImage.Add(t);
}
listBox.ItemsSource = lstImage;

这里创建了100个BitmapImage对象,存在List中,当需要释放BitmapImage内存的时候切记不可以
lstImage = null 或者 lstImage = new List<test>(),如果这样操作,BitmapImage对象还存在引用,GC不会回收这部分内存。一定要使用lstImage.Clear()或者使用lstImage.Remove()移除需要删除的BitmapImage后,GC才会回收。

如上图所示:

上为第一次加载列表后内存使用情况

中为使用lstImage.Clear()方法后内存使用情况

下为使用 lstImage = nulllstImage = new List<test>() 后再一次加载列表内存使用情况

三、 图片列表与分页显示

如果在一个模块或一个页面显示一个图片列表,包含大量的图片信息,就需要加入虚拟化与分页,在上面的例子中,使用了ListBox控件,在默认情况下ListBox自动开启虚拟化,如果要关闭虚拟化,则加入下面这段代码VirtualizingPanel.IsVirtualizing="False"

如上图所示:

上为不适用虚拟化,一次性加载列表中所有图片,内存使用情况

下为使用虚拟化后,内存使用情况,当列表滚动,图片开始逐一显示,内存也随之增长

使用虚拟化后可以解决一次性加载过多图片的问题,但已经加载到的图片不能及时自动释放,所以需要加入分页,下面是一个简单的例子:

XAML:

1
2
3
4
5
6
7
<ListBox x:Name="listBox" HorizontalAlignment="Left" Height="411" Margin="45,24,0,0" VerticalAlignment="Top" Width="336">
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding ImgSource}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

后台代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public class test
{
public ImageSource ImgSource { get; set; }
}
List<string> lstTest = new List<string>();
List<test> lstShow = new List<test>();
private void btnLoad_Click(object sender, RoutedEventArgs e)
{
lstTest.Clear();
for (int i = 0; i < 100; i++)
{
lstTest.Add(@"d:\1.bmp");
}
}
private void btnPage1_Click(object sender, RoutedEventArgs e)
{
lstShow.Clear();
for (int i = 0; i < 80; i++)
{
test t = new test();
byte[] imageData;
using (var fileStream = new FileStream(lstTest[i], FileMode.Open, FileAccess.Read))
using (var binaryReader = new BinaryReader(fileStream))
{
imageData = binaryReader.ReadBytes((int)fileStream.Length);
}
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.StreamSource = new MemoryStream(imageData);
bitmap.EndInit();
t.ImgSource = bitmap;
lstShow.Add(t);
}
listBox.ItemsSource = lstShow;
}
private void btnPage2_Click(object sender, RoutedEventArgs e)
{
lstShow.Clear();
for (int i = 80; i < 100; i++)
{
test t = new test();
byte[] imageData;
using (var fileStream = new FileStream(lstTest[i], FileMode.Open, FileAccess.Read))
using (var binaryReader = new BinaryReader(fileStream))
{
imageData = binaryReader.ReadBytes((int)fileStream.Length);
}
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.StreamSource = new MemoryStream(imageData);
bitmap.EndInit();
t.ImgSource = bitmap;
lstShow.Add(t);
}
listBox.ItemsSource = lstShow;
}
private void btnClear_Click(object sender, RoutedEventArgs e)
{
lstShow.Clear();
listBox.ItemsSource = lstShow;
}

在这个例子中,用一个列表lstTest存所有图片的路径,但不创建BitmapImage对象,只在打开分页的时候用另一个列表lstShow存储新的BitmapImage对象,同时清除旧的BitmapImage对象。这样即满足了BitmapImage内存管理原则。

最后值得一提的是在上面的例子中,如果只需要在列表中使用缩略图,则尽量用BitmapImage读缩略图的文件流,这样能大大减少内存消耗。

评论和共享

WPF项目转COM组件

发布在 program

最近有一个项目需求,要把WPF开发的程序打包成COM组件供其他程序使用,WPF工程转COM并不困难,但有一些细节还是需要记录一下:

首先需要把应用程序转成类库:

需要注意当应用程序转换成类库后App.xaml就需要删除了,如果在App.xaml中做了启动控制或者全局资源字典,需要重新规划,如全局资源的加载方式,重复启动的判断等等。

还需要注意要把主窗体改成UserControl,否则组件会以窗口形式打开。

然后将类库打包成COM组件:

勾选”COM互操作类型”

将Properties下的AssemblyInfo的ComVisible设置为True

新建一个winform用户控件,并在用户控件上放置ElementHost控件,然后编写后台代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[Guid("17F7AD9C-9814-4FB6-B0FA-B50F91FD8F3B")]
interface ITest
{
string SayHello();
}
[Guid("8F9CAB05-7E58-44B9-A8CF-FC6C02BF8EDB")]
public partial class MyHostUC : UserControl,ITest
{
public MyHostUC()
{
InitializeComponent();
MyWpfUC uc = new MyWpfUC(); //wpf用户控件
elementHost1.Child = uc;
}
public string SayHello()
{
return "hello";
}
}

其中凡是COM组件提供的对外接口都需要添加Guid的Attribute,Guid可用VS自带工具生成。

可以选择给组件添加强签名,在签名中勾选”为程序集签名”,新建,填写签名文件名称,根据需要选择给证书加密

需要注意如果给组件签名,那么要保证项目引用的所有dll都要有签名,否则无法完成注册,如果不选择签名,ie在调用COM组件时会根据安全等级提示或阻止加载。

最后注册并使用COM组件:

注册需要用VS命令行工具,管理员权限打开VS命令行提示工具,使用下面的命令:

regasm XXX.dll

这里可以看到与传统的COM组件regsvr32的注册方式不同,由于是使用.net开发,所以需要用regasm来注册。

注册完成后就可以在其它项目中使用,比如在IE中使用下面的方式:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<object classid="clsid:8F9CAB05-7E58-44B9-A8CF-FC6C02BF8EDB" id="test"
width="1280" height="800"></object>
</body>
</html>

注意这里面的classid不是com组件的id,而是接口入口的Guid.

如果是在其它winform或wpf项目中使用这个组件,首先添加这个组件:

在VS—工具—选择工具箱,进入.NetFrameWork 组件标签页,没错,不是COM组件,是.NetFrameWork组件,浏览编译并注册成功的dll,添加即可。

添加成功的控件在工具栏可以看到,还需要提醒一下,如果在wpf下使用这个控件,需要用WindowsFormsHost来做容器。

评论和共享

LinQtoEntity类型转换

发布在 program

使用EntityFrameWork时,经常会用到lambda表达式来做查询等操作,由于EF要根据表达式生成最终操作数据库的SQL,所以在表达式中加入其它方法如”parse”,”convert”可能会导致不被LinqToEntity识别,异常如下:

System.NotSupportedException: LINQ to Entities does not recognize the method int.Parse(System.String)

但在实际项目中往往会遇到实体字段类型与参数类型需要转换并比较的问题:

问题1: 字段int型与参数string型的比较

例:

实体People的Age属性为Int

参数alduts为String型,值为”18”

需要找出People表中所有Age大于18的实体集合

解答:

这类问题相对比较好解决,只需要在lambda表达式外部先对参数做类型转换

错误方法:

1
entities.People.Where(l=>l.Age > int.Parse(alduts));

正确方法:

1
2
int aldutsAge = int.Parse(alduts);
entities.People.Where(l=>l.Age > aldutsAge);

问题2: 字段string型与参数int型的比较

例:

实体People的Age属性为String

参数alduts为Int型,值为”18”

需要找出People表中所有Age大于18的实体集合

解答:

这类问题相对复杂,可考虑三种方式:

方法一:直接使用SQL

在EF4.0以后可通过EF直接用SQL操作数据库

entities.Database.ExecuteSqlCommand
entities.Database.SqlQuery

这里可以使用SQL的”CAST”方法将Age转换为int类型

select * from People where CAST(Age as int) > @alduts

需要注意的是CAST方法在多数数据库下都可用,而convert方法只在SQL SERVER中可用,所以写SQL时一定要考虑到数据库的通用性

方法二:考虑修改Age字段类型

在条件允许的前提下可修改Age字段类型为Int型,使用这种方法能彻底避免转换问题与数据库兼容问题

方法三:在CSDL自定义函数实现字段类型转换

由于使用”方法一”会导致实体操作方式不统一, 同时还会降低代码复用性,所以这里可以定义一个通用的转换方法。

步骤:

1.右键”实体模型edmx”,选择打开方式为“XML文本编辑器”

2.找到CSDL分支

3.在”Schema”分支下添加下面的代码:

<Function Name="ConvertToInt32" ReturnType="Edm.Int32">
  <Parameter Name="v" Type="String" />
  <DefiningExpression>
    CAST(v as Edm.Int32)
  </DefiningExpression>
</Function>
<Function Name="ConvertToDouble" ReturnType="Edm.Double">
  <Parameter Name="v" Type="String" />
  <DefiningExpression>
    CAST(v as Edm.Double)
  </DefiningExpression>
</Function>

其中ReturnType为返回值类型,支持的类型有:

这里可以看到DefiningExpression其实就是一个SQL。修改完成后保存退出。

4.使用转换方法,需加入EdmFunction的Attribute(EF6后更名为DbFunction)

1
2
3
4
5
6
7
8
9
10
11
[EdmFunction("testModel", "ConvertToInt32")]
public static int ConvertToInt32(string value)
{
throw new InvalidOperationException("Only valid when used as part of a LINQ query.");
}
[EdmFunction("testModel", "ConvertToDouble")]
public static double ConvertToDouble(string value)
{
throw new InvalidOperationException("Only valid when used as part of a LINQ query.");
}

其中参数1为实体的NamespaceName,参数2为FunctionName。

5.在LinqToEntity中使用转换方法

1
entities.People.Where(l=>ConvertToInt32(l.Age) > 18);

问题3: DateTime的比较

在LinqToEntity中,诸如Datetime的直接比较也是不允许的,但EF中提供了System.Data.Objects.EntityFunctions类(EF6.0后改为System.Data.Entity.DbFunctions),使用该类下的方法可以可以完成时间比较的操作,具体可参考MSDN

需要注意的是还有一个功能类似的类SQLFunctions,这两个类都是将LinqToEntity翻译成SQL,但SQLFunctions具体针对SQLServer,而EntityFunctions通用性更广。

评论和共享

作者的图片

Wu Rang

Everything begin with HelloWorld!


System Architect


Guangzhou