高性能网络 | 你所不知道的TIME_WAIT和CLOSE_WAIT

本文源于大家在公众号里面的留言,既然很多人都搞不清楚TIME_WAIT和CLOSE_WAIT,那么小胖哥今天还是抽个时间,统一帮大家理理概念吧。

你遇到过TIME_WAIT的问题吗?

我相信很多都遇到过这个问题。一旦有用户在喊:网络变慢了。第一件事情就是,netstat -a | grep TIME_WAIT | wc -l 一下。哎呀妈呀,几千个TIME_WAIT.

然后,做的第一件事情就是:打开Google或者Bing,输入关键词:too many time wait。一定能找到解决方案,而排在最前面或者被很多人到处转载的解决方案一定是:

打开 sysctl.conf 文件,修改以下几个参数:

  • net.ipv4.tcp_tw_recycle = 1
  • net.ipv4.tcp_tw_reuse = 1
  • net.ipv4.tcp_timestamps = 1

你也会被告知,开启tw_recylce和tw_reuse一定需要timestamps的支持,而且这些配置一般不建议开启,但是对解决TIME_WAIT很多的问题,有很好的用处。

接下来,你就直接修改了这几个参数,reload一下,发现,咦,没几分钟,TIME_WAIT的数量真的降低了,也没发现哪个用户说有问题,然后就没有然后了。

做到这一步,相信50%或者更高比例的开发就已经止步了。问题好像解决了,但是,要彻底理解并解决这个问题,可能就没这么简单,或者说,还有很长的路要走!

什么是TIME-WAIT和CLOSE-WAIT?

所谓,要解决问题,就要先理解问题。随便改两行代码,发现bug“没有了”,也不是bug真的没有了,只是隐藏在更深的地方,你没有发现,或者以你的知识水平,你无法发现而已。

大家知道,由于socket是全双工的工作模式,一个socket的关闭,是需要四次握手来完成的。

  • 主动关闭连接的一方,调用close();协议层发送FIN包
  • 被动关闭的一方收到FIN包后,协议层回复ACK;然后被动关闭的一方,进入CLOSE_WAIT状态,主动关闭的一方等待对方关闭,则进入FIN_WAIT_2状态;此时,主动关闭的一方 等待 被动关闭一方的应用程序,调用close操作
  • 被动关闭的一方在完成所有数据发送后,调用close()操作;此时,协议层发送FIN包给主动关闭的一方,等待对方的ACK,被动关闭的一方进入LAST_ACK状态
  • 主动关闭的一方收到FIN包,协议层回复ACK;此时,主动关闭连接的一方,进入TIME_WAIT状态;而被动关闭的一方,进入CLOSED状态
  • 等待2MSL时间,主动关闭的一方,结束TIME_WAIT,进入CLOSED状态

通过上面的一次socket关闭操作,你可以得出以下几点:

  1. 主动关闭连接的一方 - 也就是主动调用socket的close操作的一方,最终会进入TIME_WAIT状态
  2. 被动关闭连接的一方,有一个中间状态,即CLOSE_WAIT,因为协议层在等待上层的应用程序,主动调用close操作后才主动关闭这条连接
  3. TIME_WAIT会默认等待2MSL时间后,才最终进入CLOSED状态;
  4. 在一个连接没有进入CLOSED状态之前,这个连接是不能被重用的!

所以,这里凭你的直觉,TIME_WAIT并不可怕(not really,后面讲),CLOSE_WAIT才可怕,因为CLOSE_WAIT很多,表示说要么是你的应用程序写的有问题,没有合适的关闭socket;要么是说,你的服务器CPU处理不过来(CPU太忙)或者你的应用程序一直睡眠到其它地方(锁,或者文件I/O等等),你的应用程序获得不到合适的调度时间,造成你的程序没法真正的执行close操作。

这里又出现两个问题:

  1. 上文提到的连接重用,那连接到底是个什么概念?
  2. 协议层为什么要设计一个TIME_WAIT状态?这个状态为什么默认等待2MSL时间才会进入CLOSED

先解释清楚这两个问题,我们再来看,开头提到的几个网络配置究竟有什么用,以及TIME_WAIT的后遗症问题。

Socket连接到底是个什么概念?

大家经常提socket,那么,到底什么是一个socket?其实,socket就是一个 五元组,包括:

  1. 源IP
  2. 源端口
  3. 目的IP
  4. 目的端口
  5. 类型:TCP or UDP

这个五元组,即标识了一条可用的连接。注意,有很多人把一个socket定义成四元组,也就是 源IP:源端口 + 目的IP:目的端口,这个定义是不正确的。

例如,如果你的本地出口IP是180.172.35.150,那么你的浏览器在连接某一个Web服务器,例如百度的时候,这条socket连接的四元组可能就是:

[180.172.35.150:45678, tcp, 180.97.33.108:80]

源IP为你的出口IP地址 180.172.35.150,源端口为随机端口 45678,目的IP为百度的某一个负载均衡服务器IP 180.97.33.108,端口为HTTP标准的80端口。

如果这个时候,你再开一个浏览器,访问百度,将会产生一条新的连接:

[180.172.35.150:43678, tcp, 180.97.33.108:80]

这条新的连接的源端口为一个新的随机端口 43678。

如此来看,如果你的本机需要压测百度,那么,你最多可以创建多少个连接呢?我在文章《云思路 | 轻松构建千万级投票系统》里也稍微提过这个问题,没有阅读过本文的,可以发送“投票系统”阅读。

第二个问题,TIME_WAIT有什么用?

如果我们来做个类比的话,TIME_WAIT的出现,对应的是你的程序里的异常处理,它的出现,就是为了解决网络的丢包和网络不稳定所带来的其他问题:

第一,防止前一个连接【五元组,我们继续以 180.172.35.150:45678, tcp, 180.97.33.108:80 为例】上延迟的数据包或者丢失重传的数据包,被后面复用的连接【前一个连接关闭后,此时你再次访问百度,新的连接可能还是由180.172.35.150:45678, tcp, 180.97.33.108:80 这个五元组来表示,也就是源端口凑巧还是45678】错误的接收(异常:数据丢了,或者传输太慢了),参见下图:

  • SEQ=3的数据包丢失,重传第一次,没有得到ACK确认
  • 如果没有TIME_WAIT,或者TIME_WAIT时间非常端,那么关闭的连接【180.172.35.150:45678, tcp, 180.97.33.108:80 的状态变为了CLOSED,源端口可被再次利用】,马上被重用【对180.97.33.108:80新建的连接,复用了之前的随机端口45678】,并连续发送SEQ=1,2 的数据包
  • 此时,前面的连接上的SEQ=3的数据包再次重传,同时,seq的序号刚好也是3(这个很重要,不然,SEQ的序号对不上,就会RST掉),此时,前面一个连接上的数据被后面的一个连接错误的接收

第二,确保连接方能在时间范围内,关闭自己的连接。其实,也是因为丢包造成的,参见下图:

  • 主动关闭方关闭了连接,发送了FIN;
  • 被动关闭方回复ACK同时也执行关闭动作,发送FIN包;此时,被动关闭的一方进入LAST_ACK状态
  • 主动关闭的一方回去了ACK,主动关闭一方进入TIME_WAIT状态;
  • 但是最后的ACK丢失,被动关闭的一方还继续停留在LAST_ACK状态
  • 此时,如果没有TIME_WAIT的存在,或者说,停留在TIME_WAIT上的时间很短,则主动关闭的一方很快就进入了CLOSED状态,也即是说,如果此时新建一个连接,源随机端口如果被复用,在connect发送SYN包后,由于被动方仍认为这条连接【五元组】还在等待ACK,但是却收到了SYN,则被动方会回复RST
  • 造成主动创建连接的一方,由于收到了RST,则连接无法成功

所以,你看到了,TIME_WAIT的存在是很重要的,如果强制忽略TIME_WAIT,还是有很高的机率,造成数据粗乱,或者短暂性的连接失败。

那么,为什么说,TIME_WAIT状态会是持续2MSL(2倍的max segment lifetime)呢?这个时间可以通过修改内核参数调整吗?第一,这个2MSL,是RFC 793里定义的,参见RFC的截图标红的部分:


这个定义,更多的是一种保障(IP数据包里的TTL,即数据最多存活的跳数,真正反应的才是数据在网络上的存活时间),确保最后丢失了ACK,被动关闭的一方再次重发FIN并等待回复的ACK,一来一去两个来回。内核里,写死了这个MSL的时间为:30秒(有读者提醒,RFC里建议的MSL其实是2分钟,但是很多实现都是30秒),所以TIME_WAIT的即为1分钟:

所以,再次回想一下前面的问题,如果一条连接,即使在四次握手关闭了,由于TIME_WAIT的存在,这个连接,在1分钟之内,也无法再次被复用,那么,如果你用一台机器做压测的客户端,你一分钟能发送多少并发连接请求?如果这台是一个负载均衡服务器,一台负载均衡服务器,一分钟可以有多少个连接同时访问后端的服务器呢?

TIME_WAIT很多,可怕吗?

如果你通过 ss -tan state time-wait | wc -l 发现,系统中有很多TIME_WAIT,很多人都会紧张。多少算多呢?几百几千?如果是这个量级,其实真的没必要紧张。第一,这个量级,因为TIME_WAIT所占用的内存很少很少;因为记录和寻找可用的local port所消耗的CPU也基本可以忽略。

会占用内存吗?当然任何你可以看到的数据,内核里都需要有相关的数据结构来保存这个数据啊。一条Socket处于TIME_WAIT状态,它也是一条“存在”的socket,内核里也需要有保持它的数据:

  1. 内核里有保存所有连接的一个hash table,这个hash table里面既包含TIME_WAIT状态的连接,也包含其他状态的连接。主要用于有新的数据到来的时候,从这个hash table里快速找到这条连接。不同的内核对这个hash table的大小设置不同,你可以通过dmesg命令去找到你的内核设置的大小:
  2. 还有一个hash table用来保存所有的bound ports,主要用于可以快速的找到一个可用的端口或者随机端口:

由于内核需要保存这些数据,必然,会占用一定的内存。

会消耗CPU吗?当然!每次找到一个随机端口,还是需要遍历一遍bound ports的吧,这必然需要一些CPU时间。

TIME_WAIT很多,既占内存又消耗CPU,这也是为什么很多人,看到TIME_WAIT很多,就蠢蠢欲动的想去干掉他们。其实,如果你再进一步去研究,1万条TIME_WAIT的连接,也就多消耗1M左右的内存,对现代的很多服务器,已经不算什么了。至于CPU,能减少它当然更好,但是不至于因为1万多个hash item就担忧。

如果,你真的想去调优,还是需要搞清楚别人的调优建议,以及调优参数背后的意义!

TIME_WAIT调优,你必须理解的几个调优参数

在具体的图例之前,我们还是先解析一下相关的几个参数存在的意义。

net.ipv4.tcp_timestamps

RFC 1323 在 TCP Reliability一节里,引入了timestamp的TCP option,两个4字节的时间戳字段,其中第一个4字节字段用来保存发送该数据包的时间,第二个4字节字段用来保存最近一次接收对方发送到数据的时间。有了这两个时间字段,也就有了后续优化的余地。

tcp_tw_reuse 和 tcp_tw_recycle就依赖这些时间字段。

net.ipv4.tcp_tw_reuse

字面意思,reuse TIME_WAIT状态的连接。

时刻记住一条socket连接,就是那个五元组,出现TIME_WAIT状态的连接,一定出现在主动关闭连接的一方。所以,当主动关闭连接的一方,再次向对方发起连接请求的时候(例如,客户端关闭连接,客户端再次连接服务端,此时可以复用了;负载均衡服务器,主动关闭后端的连接,当有新的HTTP请求,负载均衡服务器再次连接后端服务器,此时也可以复用),可以复用TIME_WAIT状态的连接。

通过字面解释,以及例子说明,你看到了,tcp_tw_reuse应用的场景:某一方,需要不断的通过“短连接”连接其他服务器,总是自己先关闭连接(TIME_WAIT在自己这方),关闭后又不断的重新连接对方。

那么,当连接被复用了之后,延迟或者重发的数据包到达,新的连接怎么判断,到达的数据是属于复用后的连接,还是复用前的连接呢?那就需要依赖前面提到的两个时间字段了。复用连接后,这条连接的时间被更新为当前的时间,当延迟的数据达到,延迟数据的时间是小于新连接的时间,所以,内核可以通过时间判断出,延迟的数据可以安全的丢弃掉了。

这个配置,依赖于连接双方,同时对timestamps的支持。同时,这个配置,仅仅影响outbound连接,即做为客户端的角色,连接服务端[connect(dest_ip, dest_port)]时复用TIME_WAIT的socket。

net.ipv4.tcp_tw_recycle

字面意思,销毁掉 TIME_WAIT。

当开启了这个配置后,内核会快速的回收处于TIME_WAIT状态的socket连接。多快?不再是2MSL,而是一个RTO(retransmission timeout,数据包重传的timeout时间)的时间,这个时间根据RTT动态计算出来,但是远小于2MSL。

有了这个配置,还是需要保障 丢失重传或者延迟的数据包,不会被新的连接(注意,这里不再是复用了,而是之前处于TIME_WAIT状态的连接已经被destroy掉了,新的连接,刚好是和某一个被destroy掉的连接使用了相同的五元组而已)所错误的接收。在启用该配置,当一个socket连接进入TIME_WAIT状态后,内核里会记录包括该socket连接对应的五元组中的对方IP等在内的一些统计数据,当然也包括从该对方IP所接收到的最近的一次数据包时间。当有新的数据包到达,只要时间晚于内核记录的这个时间,数据包都会被统统的丢掉。

这个配置,依赖于连接双方对timestamps的支持。同时,这个配置,主要影响到了inbound的连接(对outbound的连接也有影响,但是不是复用),即做为服务端角色,客户端连进来,服务端主动关闭了连接,TIME_WAIT状态的socket处于服务端,服务端快速的回收该状态的连接。

由此,如果客户端处于NAT的网络(多个客户端,同一个IP出口的网络环境),如果配置了tw_recycle,就可能在一个RTO的时间内,只能有一个客户端和自己连接成功(不同的客户端发包的时间不一致,造成服务端直接把数据包丢弃掉)。

我尽量尝试用文字解释清楚,但是,来点案例和图示,应该有助于我们彻底理解。

我们来看这样一个网络情况:

  1. 客户端IP地址为:180.172.35.150,我们可以认为是浏览器
  2. 负载均衡有两个IP,外网IP地址为 115.29.253.156,内网地址为10.162.74.10;外网地址监听80端口
  3. 负载均衡背后有两台Web服务器,一台IP地址为 10.162.74.43,监听80端口;另一台为 10.162.74.44,监听 80 端口
  4. Web服务器会连接数据服务器,IP地址为 10.162.74.45,监听 3306 端口

这种简单的架构下,我们来看看,在不同的情况下,我们今天谈论的tw_reuse/tw_recycle对网络连接的影响。

先做个假定:

  1. 客户端通过HTTP/1.1连接负载均衡,也就是说,HTTP协议投Connection为keep-alive,所以我们假定,客户端 对 负载均衡服务器 的socket连接,客户端会断开连接,所以,TIME_WAIT出现在客户端
  2. Web服务器和MySQL服务器的连接,我们假定,Web服务器上的程序在连接结束的时候,调用close操作关闭socket资源连接,所以,TIME_WAIT出现在 Web 服务器端。

那么,在这种假定下:

  1. Web服务器上,肯定可以配置开启的配置:tcp_tw_reuse;如果Web服务器有很多连向DB服务器的连接,可以保证socket连接的复用。
  2. 那么,负载均衡服务器和Web服务器,谁先关闭连接,则决定了我们怎么配置tcp_tw_reuse/tcp_tw_recycle了

方案一:负载均衡服务器 首先关闭连接 

在这种情况下,因为负载均衡服务器对Web服务器的连接,TIME_WAIT大都出现在负载均衡服务器上,所以,在负载均衡服务器上的配置:

  • net.ipv4.tcp_tw_reuse = 1 //尽量复用连接
  • net.ipv4.tcp_tw_recycle = 0 //不能保证客户端不在NAT的网络啊

在Web服务器上的配置为:

  • net.ipv4.tcp_tw_reuse = 1 //这个配置主要影响的是Web服务器到DB服务器的连接复用
  • net.ipv4.tcp_tw_recycle: 设置成1和0都没有任何意义。想一想,在负载均衡和它的连接中,它是服务端,但是TIME_WAIT出现在负载均衡服务器上;它和DB的连接,它是客户端,recycle对它并没有什么影响,关键是reuse

方案二:Web服务器首先关闭来自负载均衡服务器的连接

在这种情况下,Web服务器变成TIME_WAIT的重灾区。负载均衡对Web服务器的连接,由Web服务器首先关闭连接,TIME_WAIT出现在Web服务器上;Web服务器对DB服务器的连接,由Web服务器关闭连接,TIME_WAIT也出现在它身上,此时,负载均衡服务器上的配置:

  • net.ipv4.tcp_tw_reuse:0 或者 1 都行,都没有实际意义
  • net.ipv4.tcp_tw_recycle=0 //一定是关闭recycle

在Web服务器上的配置:

  • net.ipv4.tcp_tw_reuse = 1 //这个配置主要影响的是Web服务器到DB服务器的连接复用
  • net.ipv4.tcp_tw_recycle=1 //由于在负载均衡和Web服务器之间并没有NAT的网络,可以考虑开启recycle,加速由于负载均衡和Web服务器之间的连接造成的大量TIME_WAIT

回答几个大家提到的几个问题

  1. 请问我们所说连接池可以复用连接,是不是意味着,需要等到上个连接time wait结束后才能再次使用?

所谓连接池复用,复用的一定是活跃的连接,所谓活跃,第一表明连接池里的连接都是ESTABLISHED的,第二,连接池做为上层应用,会有定时的心跳去保持连接的活跃性。既然连接都是活跃的,那就不存在有TIME_WAIT的概念了,在上篇里也有提到,TIME_WAIT是在主动关闭连接的一方,在关闭连接后才进入的状态。既然已经关闭了,那么这条连接肯定已经不在连接池里面了,即被连接池释放了。

2. 想请问下,作为负载均衡的机器随机端口使用完的情况下大量time_wait,不调整你文字里说的那三个参数,有其他的更好的方案吗?

第一,随机端口使用完,你可以通过调整/etc/sysctl.conf下的net.ipv4.ip_local_port_range配置,至少修改成 net.ipv4.ip_local_port_range=1024 65535,保证你的负载均衡服务器至少可以使用6万个随机端口,也即可以有6万的反向代理到后端的连接,可以支持每秒1000的并发(想一想,因为TIME_WAIT状态会持续1分钟后消失,所以一分钟最多有6万,每秒1000);如果这么多端口都使用完了,也证明你应该加服务器了,或者,你的负载均衡服务器需要配置多个IP地址,或者,你的后端服务器需要监听更多的端口和配置更多的IP(想一下socket的五元组)

第二,大量的TIME_WAIT,多大量?如果是几千个,其实不用担心,因为这个内存和CPU的消耗有一些,但是是可以忽略的。

第三,如果真的量很大,上万上万的那种,可以考虑,让后端的服务器主动关闭连接,如果后端服务器没有外网的连接只有负载均衡服务器的连接(主要是没有NAT网络的连接),可以在后端服务器上配置tw_recycle,然后同时,在负载均衡服务器上,配置tw_reuse。

  1. 如果想深入的学习一下网络方面的知识,有什么推荐的?

学习网络比学一门编程语言“难”很多。所谓难,其实,是因为需要花很多的时间投入。我自己不算精通,只能说入门和理解。基本书可以推荐:《TCP/IP 协议详解》,必读;《TCP/IP高效编程:改善网络程序的44个技巧》,必读;《Unix环境高级编程》,必读;《Unix网络编程:卷一》,我只读过卷一;另外,还需要熟悉一下网络工具,tcpdump以及wireshark,我的notes里有一个一站式学习Wireshark:https://github.com/dafang/notebook/issues/114,也值得一读。有了这些积累,可能就是一些实践以及碎片化的学习和积累了。

写在最后

这篇文章我断断续续写了两天,内容找了多个地方去验证,包括看到Vincent Bernat的一篇文章以及Vincent在多个地方和别人的讨论。期间,我也花了一些时间和Vincent探讨了几个我没在tcp源码里翻找到的有疑问的地方。

我力求比散布在网上的文章做到准确并尽量整理的清晰一些。但是,也难免会

有疏漏或者有错误的地方,高手看到可以随时指正,并和我讨论,大家一起研究!

感谢您阅读。

Mac下自动开启SSH 隧道

SSH遇到网络抖动,就会断开,影响翻墙梯的连接,找到了autossh这个神器,可以断线重连

0x0 安装autossh

brew install autossh

0x01 设置mac开机启动配置文件~/Library/LaunchAgents/homebrew.mxcl.autossh.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>homebrew.mxcl.autossh</string>
    <key>ProgramArguments</key>
    <array>
    <string>/usr/local/bin/autossh</string>
    <string>-M</string>
    <string>8111</string>
    <string>-N</string>
    <string>shuhai@8.8.8.8</string>
    <string>-p</string>
    <string>22</string>
    <string>-D</string>
    <string>localhost:8032</string>
    <string>-C</string>
    <string>-i</string>
    <string>/Users/shuhai/.ssh/id_rsa</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
  </dict>
</plist>

0x02

launchctl load   ~/Library/LaunchAgents/homebrew.mxcl.autossh.plist
launchctl unload ~/Library/LaunchAgents/homebrew.mxcl.autossh.plist

0x03
在翻墙工具中增加sockets5端口,如 SwitchyOmega 中配置为 SOCKETS5, 127.0.0.1, 8032

0x04
如果将plist放到 /Library/LaunchAgents/ 中,则需要给该文件增加权限

sudo chown root   /Library/LaunchAgents/homebrew.mxcl.autossh.plist
sudo schgrp wheel /Library/LaunchAgents/homebrew.mxcl.autossh.plist

Mac+DCloud中,添加友盟SDK、点乐积分墙

1. mac中无法识别魅族4的测试机,处理方法如下

修改文件 ~/.android/adb_usb.ini,在文件开头或末尾添加一行,内容是0x2a45。

adb kill-server
adb devices

2. 将DCloud完整包导入adt,保证DCloud Hello项目可完整运行,流程如下:
http://ask.dcloud.net.cn/docs#http://ask.dcloud.net.cn/article/38

3. 下载点乐积分墙,将sdk中的jar包解压出来,放到/libs/dlnetwork.jar
4. 按点乐的文档,AndroidManifest.xml中添加好权限和service,然后在DCLOUD中新建页面,调用点乐积分墙,代码如下:

```javascript
function dianle(){
mainActivity = plus.android.runtimeMainActivity();
dlnetwork = plus.android.importClass("com.dlnetwork.DevInit");
var d = new dlnetwork();
d.initGoogleContext(mainActivity, '34db9f40428a12701ad2738999b197ca', 'MY_CHANNLE_NAME');
d.showOffers(mainActivity);
}
```

5. Dcloud中已经集成了umeng的云打包,离线打包的时候或者想统计指定页面的,手工调用代码如下:
```javascript
function plusReady(){
//这里是其它初始化代码
//友盟统计
mainActivity = plus.android.runtimeMainActivity();
MobclickAgent = plus.android.importClass("com.umeng.analytics.MobclickAgent");
MobclickAgent.onPageStart("MainScreen");
}
```

解决Scala异常处理java.lang.OutOfMemoryError: Java heap space error

需求:百万、千万、4千万级日志对设备进行除重
环境:设备内存64G,scala单机版运行shell文件
日志:
20G 48000000.log
4.0G 10000000.log
396M 1000000.log

代码如下

import scala.io.Source
import scala.collection.mutable.ArrayBuffer
var text = Source.fromFile("/Users/shuhai/1000000.log").getLines;

//imei is required
var log = ArrayBuffer[String]();
while(text.hasNext) {
    var row = text.next
    if(row.contains("imei"))
        log += row
}

//println(log.head)
//println(log.length)

var act = log.filter{ (row) => row.contains("active") }
var imei = act.map(_.split(",").filter(_.contains("imei"))).map(_.mkString(","))
var clean= imei.map(_.replaceAll("\"","")).map(_.replaceAll(" ",""))
println("total:" + clean.length)

var count = clean.toList.distinct.length
println("distinct:" + count)

异常
➜ scala scala distinct_imei.scala
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOfRange(Arrays.java:3664)
at java.lang.String.(String.java:201)
at java.io.BufferedReader.readLine(BufferedReader.java:356)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at scala.io.BufferedSource$BufferedLineIterator.hasNext(BufferedSource.scala:72)
at Main$$anon$1.
(distinct_imei.scala:7)
at Main$.main(distinct_imei.scala:1)
at Main.main(distinct_imei.scala)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at scala.reflect.internal.util.ScalaClassLoader$$anonfun$run$1.apply(ScalaClassLoader.scala:70)
at scala.reflect.internal.util.ScalaClassLoader$class.asContext(ScalaClassLoader.scala:31)
at scala.reflect.internal.util.ScalaClassLoader$URLClassLoader.asContext(ScalaClassLoader.scala:101)
at scala.reflect.internal.util.ScalaClassLoader$class.run(ScalaClassLoader.scala:70)
at scala.reflect.internal.util.ScalaClassLoader$URLClassLoader.run(ScalaClassLoader.scala:101)
at scala.tools.nsc.CommonRunner$class.run(ObjectRunner.scala:22)
at scala.tools.nsc.ObjectRunner$.run(ObjectRunner.scala:39)
at scala.tools.nsc.CommonRunner$class.runAndCatch(ObjectRunner.scala:29)
at scala.tools.nsc.ObjectRunner$.runAndCatch(ObjectRunner.scala:39)
at scala.tools.nsc.ScriptRunner.scala$tools$nsc$ScriptRunner$$runCompiled(ScriptRunner.scala:170)
at scala.tools.nsc.ScriptRunner$$anonfun$runScript$1.apply(ScriptRunner.scala:187)
at scala.tools.nsc.ScriptRunner$$anonfun$runScript$1.apply(ScriptRunner.scala:187)
at scala.tools.nsc.ScriptRunner$$anonfun$withCompiledScript$1$$anonfun$apply$mcZ$sp$1.apply(ScriptRunner.scala:156)
at scala.tools.nsc.ScriptRunner$$anonfun$withCompiledScript$1.apply$mcZ$sp(ScriptRunner.scala:156)
at scala.tools.nsc.ScriptRunner$$anonfun$withCompiledScript$1.apply(ScriptRunner.scala:124)
at scala.tools.nsc.ScriptRunner$$anonfun$withCompiledScript$1.apply(ScriptRunner.scala:124)
at scala.tools.nsc.util.package$.trackingThreads(package.scala:43)
at scala.tools.nsc.util.package$.waitingForThreads(package.scala:27)
at scala.tools.nsc.ScriptRunner.withCompiledScript(ScriptRunner.scala:123)
at scala.tools.nsc.ScriptRunner.runScript(ScriptRunner.scala:187)

解决办法:

env JAVA_OPTS="-Xms256m -Xmx2048m" scala distinct_imei.scala

使用scala -help可以看到有一个-J的参数,也可以实现相同的效果

scala -J-Xms256m -J-Xmx2048m distinct_imei.scala

via:http://alvinalexander.com/scala/scala-repl-java.lang.outofmemoryerror-java-heap-space-error

php ssh2操作类

<?php

class Components_Ssh {
    private $host;
    private $user;
    private $pass;
    private $port;
    private $conn = false;
    private $error;
    private $stream;
    private $stream_timeout = 100;
    private $log;
    private $lastLog;

    public function __construct ( $host, $user, $pass, $port, $serverLog ) {
        $this->host = $host;
        $this->user = $user;
        $this->pass = $pass;
        $this->port = $port;
        $this->sLog = $serverLog;

        if ( $this->connect ()->authenticate () ) {
            return true;
        }
    }

    public function isConnected () {
        return ( boolean ) $this->conn;
    }

    public function __get ( $name ) {
        return $this->$name;
    }

    public function connect () {
        $this->logAction ( "Connecting to {$this->host}" );
        if ( $this->conn = ssh2_connect ( $this->host, $this->port ) ) {
            return $this;
        }
        $this->logAction ( "Connection to {$this->host} failed" );
        throw new Exception ( "Unable to connect to {$this->host}" );
    }

    public function authenticate () {
        $this->logAction ( "Authenticating to {$this->host}" );
        if ( ssh2_auth_password ( $this->conn, $this->user, $this->pass ) ) {
            return $this;
        }
        $this->logAction ( "Authentication to {$this->host} failed" );
        throw new Exception ( "Unable to authenticate to {$this->host}" );
    }

    public function sendFile ( $localFile, $remoteFile, $permision = 0644 ) {
        if ( ! is_file ( $localFile ) ) throw new Exception ( "Local file {$localFile} does not exist" );
        $this->logAction ( "Sending file $localFile as $remoteFile" );

        $sftp = ssh2_sftp ( $this->conn );
        $sftpStream = @fopen ( 'ssh2.sftp://' . $sftp . $remoteFile, 'w' );
        if ( ! $sftpStream ) {
            //  if 1 method failes try the other one
            if ( ! @ssh2_scp_send ( $this->conn, $localFile, $remoteFile, $permision ) ) {
                throw new Exception ( "Could not open remote file: $remoteFile" );
            }
            else {
                return true;
            }
        }

        $data_to_send = @file_get_contents ( $localFile );

        if ( @fwrite ( $sftpStream, $data_to_send ) === false ) {
            throw new Exception ( "Could not send data from file: $localFile." );
        }

        fclose ( $sftpStream );

        $this->logAction ( "Sending file $localFile as $remoteFile succeeded" );
        return true;
    }

    public function getFile ( $remoteFile, $localFile ) {
        $this->logAction ( "Receiving file $remoteFile as $localFile" );
        if ( ssh2_scp_recv ( $this->conn, $remoteFile, $localFile ) ) {
            return true;
        }
        $this->logAction ( "Receiving file $remoteFile as $localFile failed" );
        throw new Exception ( "Unable to get file to {$remoteFile}" );
    }

    public function cmd ( $cmd, $returnOutput = false ) {
        $this->logAction ( "Executing command $cmd" );
        $this->stream = ssh2_exec ( $this->conn, $cmd );

        if ( FALSE === $this->stream ) {
            $this->logAction ( "Unable to execute command $cmd" );
            throw new Exception ( "Unable to execute command '$cmd'" );
        }
        $this->logAction ( "$cmd was executed" );

        stream_set_blocking ( $this->stream, true );
        stream_set_timeout ( $this->stream, $this->stream_timeout );
        $this->lastLog = stream_get_contents ( $this->stream );

        $this->logAction ( "$cmd output: {$this->lastLog}" );
        fclose ( $this->stream );
        $this->log .= $this->lastLog . "\n";
        return ( $returnOutput ) ? $this->lastLog : $this;
    }

    public function shellCmd ( $cmds = array () ) {
        $this->logAction ( "Openning ssh2 shell" );
        $this->shellStream = ssh2_shell ( $this->conn );

        sleep ( 1 );
        $out = '';
        while ( $line = fgets ( $this->shellStream ) ) {
            $out .= $line;
        }

        $this->logAction ( "ssh2 shell output: $out" );

        foreach ( $cmds as $cmd ) {
            $out = '';
            $this->logAction ( "Writing ssh2 shell command: $cmd" );
            fwrite ( $this->shellStream, "$cmd" . PHP_EOL );
            sleep ( 1 );
            while ( $line = fgets ( $this->shellStream ) ) {
                $out .= $line;
                sleep ( 1 );
            }
            $this->logAction ( "ssh2 shell command $cmd output: $out" );
        }

        $this->logAction ( "Closing shell stream" );
        fclose ( $this->shellStream );
    }

    public function getLastOutput () {
        return $this->lastLog;
    }

    public function getOutput () {
        return $this->log;
    }

    public function disconnect () {
        $this->logAction ( "Disconnecting from {$this->host}" );
        // if disconnect function is available call it..
        if ( function_exists ( 'ssh2_disconnect' ) ) {
            ssh2_disconnect ( $this->conn );
        }
        else { // if no disconnect func is available, close conn, unset var
            @fclose ( $this->conn );
            $this->conn = false;
        }
        // return null always
        return NULL;
    }

    public function fileExists ( $path ) {
        $output = $this->cmd ( "[ -f $path ] && echo 1 || echo 0", true );
        return ( bool ) trim ( $output );
    }
}

读取公钥的脚本

#!/usr/bin/php
<?php
$server = ["192.168.1.2", "192.168.1.20", "192.168.1.200"];

$server = array_map(function($k){

	printf("%s%s%s%s", str_repeat("=", 30), $k, str_repeat("=", 30), PHP_EOL);

	$connection = ssh2_connect($k, 22);

	$sshDir = $_SERVER["HOME"] . "/.ssh/";

	ssh2_auth_pubkey_file($connection, 'shuhai', $sshDir.'id_rsa.pub', $sshDir.'id_rsa');


	ssh2_exec($connection, 'echo "eoe!@#admin" | sudo -s');

	$stream = ssh2_exec($connection, 'whoami');
	stream_set_blocking( $stream, true );
    echo stream_get_contents($stream);

	return;

	$stream = ssh2_exec($connection, 'php -v');
	stream_set_blocking( $stream, true );
    echo stream_get_contents($stream);

	$stream = ssh2_exec($connection, 'mysql --version');
    stream_set_blocking( $stream, true );
    echo stream_get_contents($stream);

}, $server);

15条给开发者的强大的jQuery使用窍门

原文:
15 Powerful jQuery Tips and Tricks for Developers

http://tutorialzine.com/2011/06/15-powerful-jquery-tips-and-tricks-for-developers/

1. 优化你的选择器用法

$('li[data-selected="true"] a')	// Fancy, but slow
$('li.selected a')	// Better
$('#elem')	// Best
var buttons = $('#navigation a.button');

// Some prefer prefixing their jQuery variables with $:
var $buttons = $('#navigation a.button');
$('a.button:animated');	// Does not use querySelectorAll()
$('a.button').filter(':animated');	// Uses it

2. 对象遍历方法

// Selecting all the navigation buttons:
var buttons = $('#navigation a.button');

// We can loop though the collection:
for(var i=0;i<buttons.length;i++){
    console.log(buttons[i]);	// A DOM element, not a jQuery object
}

// We can even slice it:
var firstFour = buttons.slice(0,4);

3. 选择器属性

$('#container li:first-child').selector    // #container li:first-child
$('#container li').filter(':first-child').selector    // #container li.filter(:first-child)

4. 创建jQuery对象

var container = $([]);
container.add(another_element);

5. ajax的全局配置,今天第一次看到,哈

// ajaxSetup is useful for setting general defaults:
$.ajaxSetup({
    url			: '/ajax/',
    dataType	: 'json'
});

$.ajaxStart(function(){
    showIndicator();
    disableButtons();
});

$.ajaxComplete(function(){
    hideIndicator();
    enableButtons();
});

/*
    // Additional methods you can use:
    $.ajaxStop();
    $.ajaxError();
    $.ajaxSuccess();
    $.ajaxSend();
*/

6. 是jQuery操作html5的data属性

<div id="d1" data-role="page" data-last-value="43" data-hidden="true"
    data-options='{"name":"John"}'>
</div>

操作方法:

$("#d1").data("role");			// "page"
$("#d1").data("lastValue");		// 43
$("#d1").data("hidden");		// true;
$("#d1").data("options").name;	// "John";

7. 文件存储插件

$.jStorage jQuery plugin:

// Check if "key" exists in the storage
var value = $.jStorage.get("key");
if(!value){
    // if not - load the data from the server
 	value = load_data_from_server();
 	// and save it
    $.jStorage.set("key",value);
}

// Use value

via:http://www.4wei.cn/archives/1002224

留下来的原因不是理想,而是习惯

每年春节期间,你总是要去见见老同学,一起喝个酒,吹个牛,回忆一下过去,调侃一下现在。酒至酣处,便会吐出心底的不如意,散席后,踉跄着相互祝福,然后道别。

每每此时,总会有在老家工作的哥们走过来,拍拍你的肩膀 ,“兄弟,好好干,大城市有发展”.听完后那你浑身冷汗,酒醒一半。 你感觉到没有在大城市混好,破灭了自己的理想,辜负了父母的期盼,对不起朋友的期盼,甚至拖了城市发展的后腿。刚毕业的时,

你有远大的理想,你憧憬着未来的生活,你积极工作,你热爱你的事业,你高昂着头,不服气的对自己说,别人在这个城市已经如何如何,你为什么不可以,于是你打算脚踏实地,一步一个脚印的向前进。

几年过去了,你发现你走的很踏实,但是你没走远。通货膨胀让你鼓起来的腰包迅速的瘪了下去,你发现身边很多人抄了近路,已经走的很远,你心里依然鄙夷的看着他们,但你已经打算加快追赶的脚步。然后忙碌在日常的工作中,忙于处理人际关系,忙于找到挣钱的出路,渐渐的迷失了。

然后你发现所谓的大城市机会多,不过是可以随时找到一份工作不至于失业,你的梦想依然离你很遥远,你几乎放弃了所有的业余时间,别说灯红酒绿,别说旅游休闲,你甚至已经半年没有玩过台球,没有去过KTV了。

你想起在群里的同学,他们经常谈论着家乡的美食,讲自己的故事,他们已经生儿育女,相互讨论着育儿心得,虽然他们钱并不多,但是却有着丰富的娱乐生活,他们经常打麻将,k歌,和朋友喝的烂醉,还有的组织车友会去自驾游。

于是你陷入深深的迷茫,拿出一根5块钱一包的香烟,叼在嘴里,站在阳台上看着大城市的夜色,霓虹闪烁,车水马龙,外面的繁华遮挡不住你内心的孤独。搬一把椅子到阳台上坐下,把头埋了下去,香烟一根接着一根。

半饷之后,你擦干泪眼,掐灭香烟,疲惫的站了起来,眼望窗外。外面的繁华已经落幕,你却坚定了你留下来的决心。

而你的内心深处却清楚的意识到,留下来的原因不是理想,而是习惯。

试用了几个开源产品,mark

http://www.oschina.net/project/top_cn_2012

花了半天把osc里几个用得着的项目跑了一遍,mar个k

http://aralejs.org/docs/develop-components.html
http://cyj.me/why-seajs/zh/

http://bsdn.org/projects/dorado7
http://bsdn.org/projects/dorado-jdbc-addon
http://www.bsdn.org/projects/dorado7/demo
http://bsdn.org/projects/dorado7/deploy/sample-center/com.bstek.dorado.sample.Main.d

http://www.ligerui.com/
http://www.j-ui.com/
http://nej.netease.com/
http://ui.operamasks.org/website/homepage.html
http://ui.operamasks.org/website/demos.html
http://qatrix.com/

http://runjs.cn/
http://runjs.cn/plugin

https://launchpad.net/ubuntu-tweak/

eoe开发者见面会-走进上海

终于踏上了上海的热土,天气确实没有我们想像中那么热,但现场与朋友们的热情,真心让人浑身暖阳阳的。

本次行程:
周四中午,我们发起活动,截至第二天中午,有50名开发者报名;
周五晚上,eoe社区三位管理员出发前往上海
周六早上,eoe社区三位管理员抵达上海,有三个社区版主稍后也到达上海,并参与社区签到欢迎工作,感谢bopaier等几位同学
周六中午,到下午1点,有45位eoer到达会场签到,我们共计送出了18件eoe纪念衫,20本android开发方面的图书,还有10个飞科的小家电,我不知道叫什么名字了;还有两位从苏州过来的帅哥美女,协助工作人员颁发奖品,感谢两位
周六晚上,11位社区版主+3名社区管理员共进晚餐,由于不接受场地预定,我们一行人沿街找吃饭的地儿。这一餐吃得太有纪念意义了。期间,大家还分享了一些行业间的情报,还对HTML5、WP、BB10做了探讨。

更多组图:【上海见面会】上海移动开发者大会精彩瞬间
http://www.eoeandroid.com/thread-196868-1-1.html

版主曝光:eoe上海移动开发者大会私照图
http://www.eoeandroid.com/thread-196812-1-1.html

在于各位版主的深度交谈中,我们了解到一些数据,跟大家分享。

1,在北上广深几个城市中,集中了大多数高帅富的开发者,很多开发者为了能接触到更多的项目、更新的技术会选择往大城市迁徙。
2,薪水和项目前景是大家关注的焦点,有一两年安卓开发经验的同学,普遍能拿到6-8k左右的薪水,更强一些的同学,拿到12-18K也很正常。
3,在版主队伍中发现了两位女开发者,来自苏州的Monica和jenifer。Monica是典型的苏杭美女,温婉秀美。而她们却能在以“劳累”、“高压”的IT行业游刃有余,我们这帮IT男,果断不敢再说“苦逼”字。
4,安卓开发者已经不避讳,大家都能感觉到移动开发的三大、四大甚至五大平台竞争的机会,在做安卓开发的同时,很多开发者也在做其它平台的开发,IOS、WP8、HTML5,甚至这次BB10也出来抢人。对于BB10开出的如1万美元的扶持计划,River在现场提出的一个问题,直接让台上的嘉宾无言以对。

在eoe联合Micro soft举办WP8训练营以后,在看到BB10提出的开发者扶持计划以后,我个人发现移动平台在渐渐出现一个趋势。
对于微软来说,WP8将会微软在移动开发平台的最重要的一战,对于黑莓来说,BB10算是最后一次挣扎。今年第二季度Android和iPhone在智能手机市场上的份额分别为68.1%和16.9%,Android和iPhone已经占尽所有优势。微软和黑莓向开发者承诺的开发者扶持计划,打造的本土化服务团队,算是狙击谷歌和苹果的狠招。

现在有句玩笑话,做WP开发,有问题可以打电话给微软北京总部寻求支持;做Android开发,有问题可以找eoe;做IOS开发,有问题可以飞去美国。为了吸引开发者,微软拿出了最好的本土化服务、诱人的收入分成与自由的支付方案,希望能吸引开发者的目光,从而转向WP8阵营。

大家肯定会问,Google会有什么招来应对,我建议大家看一下这篇文章,重点在Febber同学的分析:
谷歌布下的大局
http://news.eoe.cn/industry/2012/0914/3204.html

eoe即将迎来一年一届的开发者大会,上面的问题或许能得到最好的解答,有兴趣的同学可以关注eoe社区。eoe也会择期组织一些同学,进入Google中国,我们暂定这次活动为“eoe开发者见面会-走进Google”,欢迎大家关注。

一个城市,一种生活,一种生存状态,一段奋斗史

今天参加eoe的话题,写了这几年从来没有写过的一些文字,记下来。

http://www.eoeandroid.com/thread-191466-1-1.html
各位亲,不知道是你们写得太煽情,还是我有感而发,早上起床看到这个帖子,禁不住心酸,再也睡不着。

来北京快8年了,大学和工作都在北京。
现在,留在北京的朋友,同学,已然不多,留在北京的同学,也有了各自的生活,从未再凑到一起,QQ群里,也不知道谁的头像再也没有亮起。

谁会知道不经意的分别,却有可能是一生。

因为某些原因,被迫选择离家3000公里的北京,一年到头回家的时间不超过10天,大学几年,春节也难能回家。

我觉得自己是一个残酷的人,否则没法面对北京这个城市的生活。
忙碌,麻木,寂寞;没有朋友,没有亲人,没有友人小酌,也没有死党蹭饭耍无赖;合租的室友也不知道什么时候突然搬走;眼里,只有工作。

很多年以后的今天,也一直很庆幸上大学的时候,玩起了网页技术,慢慢开始了研究编程和开发,也成功在大学期间赚到了第一桶金,自己养活自己,毕业以后也没有像同学一样奔波于各大招聘会。
我觉得IT行业比其它行业的机会多得多,只要我们准备得够好,时间会为我们铺好一切。

高考的时候,也没有学习到晚上2点钟,也没有在蹲WC的时候看过书。刚开始学习编程的那几年,每天晚上看视频教程,看电子书,看源码,修改别人的代码,为在新学到的巧计兴奋不已。这样的满负荷学习状态一直维持了几年。程序员,性本恶,不对自己狠心,如何坚持,如何不动摇;

09年爆发猪流感,不幸被医院扣留,不过,也是在输液期间学完了一本600页的asp编程;大学时没有电脑,羡慕同学800元攒的一台说不出配置和型号的电脑,半夜等同学睡着了,跑到他们的宿舍学photoshop,兴奋呀,ps6.0好像是。那时候,我们还没有ps这样的课程;

当然,这样的好景只持续到同学看见我就把宿舍门锁死为止。
第二阵地是网吧,5块钱包宿,一瓶矿泉水,在网吧里用Dw写出了静态页面,asp,php。后来发现网吧的收费软件是可以破解的,然后就转战于昌平各大小网吧,用5毛钱,能上一天两夜;
在后来,有一些技术基础,开始混迹在一些软件论坛,慢慢的,接触到一些人的需求,慢慢的开始做起了小项目,小的5块钱,大的100块钱,也慢慢的,被大家所熟知。
不得不说,博客和论坛,是草根们成名、积累资源、学习知识的最好平台,没有之一。
比如eoe。
我选择了留在北京,是因为种种原因,在一些论坛里认识了现在很好的朋友。经朋友介绍,我认识了很多人,接触到很多公司、项目、需求。而这些,是我在其它地方不具备的资源。
年龄慢慢大了,渐渐的明白,我们这一代人的压力和责任,年老的父母,另一半,事业。
也渐渐明白,程序员,是一个对自己残忍的职业,因为你选择了终身不断学习的一个事业。
北京,是一个公平,回报的城市,她见证了你的成长,你的努力,你的付出。
我在北京,你在哪里?