飞信2010分析 – 使用HTTP模式和服务器通信 2010年8月1日 | 分类: 飞信哪些事儿 | 标签: 在 飞信2010分析 – SIPC验证 文章里,简单的提到了飞信使用HTTP传输模式。
HTTP模式是所有传输方式中效率最低,是在其他传输方式都建立失败后才会采用HTTP传输模式,可见飞信在网络环境适应上下了很大的功夫的。
在第一步获取自适应的配置中,曾经提到下面几个参数: 含义 位置 结果举例 SIPC-PROXY /config/server/sipc-proxy 221.130.46.141:8080 SIPC-SSL /config/server/sipc-ssl-proxy 221.130.46.141:443 HTTP-TUNNEL /config/server/http-tunnel HTTP://221.130.46.141/ht/sd.aspx 飞信建立连接主要就是这三个传输方式。
来看下飞信的建立连接的顺序: 第一步: 尝试连接建立 SIPC-PROXY连接 。
即SIPC服务器端口为8080的连接。
这是最稳定的,效率最高的传输方式。
第二步: 如果第一步失败,则尝试建立SIPC-SSL连接。
即SIPC服务器端口为443的连接。
和SIPC-PROXY连接方式完全一致,就是端口变了。
虽说是SSL端口,却仍然没有加密; 第三步: 如果第二步失败,则尝试建立HTTP-TUNNEL连接。
即HTTP代理连接。
采用长连接的模式,效率比较低,而且不太稳定。
不过只要能访问HTTP,就可以连接上飞信服务器。
第四步: 如果第三步建立失败,则飞信客户端和飞信服务器建立连接失败。
可以看出如果要分析HTTP模式,则需要把SIPC-PROXY和SIPC-SSL的IP和端口堵上。
一般都是相同的IP不同的端口,可以使用防火墙来完成。
HTTP分析可以使用HTTPAnalyzer帮助分析。
在我的飞信资源整合那篇文章中有这两个工具的下载。
在分析之前,还是简单的介绍长连接模式。
因为HTTP模式下就是使用了长连接的方式。
我们知道HTTP是一个请求-回复类型的协议。
在发起一个请求之后,服务器需要返回一个回复。
可以理解为是单向的,请求只能是浏览器发送,服务器只能被动的接受请求处理请求,并不能主动的和客户端发送消息。
在Socket通信模型中,客户端和服务器的通信是双向的,客户端可以主动的向服务器发送信息,服务器也可以主动的向客户端发送信息。
这在即时通信里面很有必要,假如其他用户主动的发消息给你,服务器可以在第一时间内主动的发消息通知你。
飞信是需要双向通信的模式,但HTTP服务器是不能主动的给客户端发送信息的。
于是产生了长连接的技术,也就是Comet,来模拟服务器主动给客户端发送消息。
长连接其实很简单,客户端主动的发起一个HTTP请求给服务器,服务器收到这个请求后,会查看是否有消息可以返回给客户端,如果有把消息放入回复体内,立即返回给客户端。
如果没有,就阻塞掉这个请求,不返回,直到服务器有可以返回的消息或者超时才结束这个请求。
客户端检查这个请求,如果是超时,就立即发起另外一个请求,如果不是超时,就处理服务器的返回的数据,处理完了继续发起下一个请求。
这样服务器就始终保留了一个客户端的连接,如果收到消息后就可以立即返回给客户端,完全可以模拟Socket双向的通信模式。
只不过这种模式效率低,而且不太稳定,服务器资源占用率大(因为要维持多个连接),不过在一些特别的情景下需要使用。
目前WebQQ和WebFetion等即时通信的WEB都是采用了这样的通信模式。
详细的长连接信息请参见IBM工程师写的文章 – Comet:基于 HTTP 长连接的“服务器推”技术 。
分析很简单的,不罗嗦过程,直接写结果吧。
HTTP模式下,假如HTTP://221.130.46.141/ht/sd.aspx为获取自适应配置返回的HTTP-TUNNEL地址: 下面是一个HTTP请求的例子: 展开源代码 查看源代码 打印 关于
01.
POST /ht/sd.aspx?t=s&i=2 HTTP/1.1
02.
Cookie: ssic=CRIOAAA0L0hiEKIY+8MORa3QNc4OJcB3rACWXefS7rmhOaQ5fifHEGsFh8+wPK91WlYD9M4zEqvgSG/hiReF2X30HurJRVRinJLO12MtuQdrY9lN4fpni9AvwA6Qj/FDkKr0wT8AAA==
03.
Accept: */*
04.
Pragma: xz4BBcV9df9de4e-0cd9-49c5-bf32-6adc28fcee63
05.
Host: 221.130.46.141
06.
Content-Length: 134
07.
Content-Type: application/oct-stream
08.
User-Agent: IIC2.0/PC 4.0.0000
09.
Connection: Keep-Alive
10.
Cache-Control: no-cache
11.
12.
R fetion.com.cn SIP-C/4.0
13.
F: 685592830
14.
I: 1
15.
Q: 1 R
16.
CN: 258dbcc7cc8a1633c677ebb0abaa5168
17.
CL: type="pc",version="4.0.3340"
18.
19.
SIPP
20.
------------------------------------
21.
HTTP/1.1 200 OK
22.
Connection: close
23.
Date: Sun, 01 Aug 2010 03:14:12 GMT
24.
Server: Microsoft-IIS/6.0
25.
X-Powered-By: ASP.NET
26.
X-AspNet-Version: 2.0.50727
27.
Cache-Control: private
28.
Content-Type: application/oct-stream
29.
Content-Length: 4
30.
31.
SIPP
就着这个例子,逐步的分析。
请求的方式为POST。
在URL上面有两个参数: t : 请求的模式,取值有 i,s,d,下面会有详细的说明; i : 请求的序号,随着请求的次数递增; 还传递了一个Cookie: ssic。
ssic是SSI登录成功后,会在HTTP头中使用标准set-cookie方式返回; 在请求头中还有一个Pragma域,在整个请求中不变,可能是标志唯一的客户端。
前面七个字符固定为xz4BBcV,后面是一个GUID,可以随机生成 其他可以保持不变。
请求体就是 SIPC信令包+SIPP。
请注意这里一定要加上SIPP四个字符,如果没有SIPC信令包,也要发送SIPP四个字符。
信令包的内容和过程和SIPC-PROXY连接发送的内容是完全一样的。
但有一个包有点区别,就是第二步验证的时候 后面的值不同,在SIPC-PROXY和SIPC-SSL连接中这个值为1FFF,在HTTP模式下这个值为AFF。
这个值的含义,是表明了客户端支持的特性。
如果有时间可能会写文章来分析这个值代表的含义,但现在只需要知道,这个值影响的后面和在线的好友建立对话的方式。
请求的回复头没有什么有价值的东西,回复体就是 SIPC信令包+SIPP 。
注意这里还是有个SIPP四个字符。
如果有信令包需要返回给客户端就把信令包直接放在回复体中。
服务器也可以不返回任何信令包,只返回一个SIPP。
基本的格式就是这样。
下面看下过程。
简单的过程和建立一个对话很相似:建立连接 -> 发送消息,接收消息,发送消息,接收消息…..-> 关闭连接。
1、建立连接的过程很简单,参数t的值为i,POST的内容只有一个SIPP四个字符,如果返回200,建立连接成功; 2、发送和接收消息,请求体放置了需要发送的SIPC信令包,回复体中放置需要接收的SIPC信令包。
注意这里可能有多个信令包可以放入一个请求体或者回复体中。
如果没有任何SIPC信令包可以发送,仍需发送空的请求包(即只含有SIPP四个字符的请求体)给服务器建立长连接。
服务器有消息就会把消息放入回复体中,立即返回这个请求,如果没有就阻塞这个请求,直到超时。
3、关闭连接的过程也很简单,参数的t为d,POST的内容只有一个SIPP四个字符,如果返回200,关闭连接成功; 下面是SSISignV4Test例子中的HttpTranfer.java的部分代码,实现了HTTP传输方式,各位可以参考下,完整的例子可以在首页的公告找到下载地址。
展开源代码 查看源代码 打印 关于
01.
publicvoidstartTransfer() throwsException
02.
{
03.
Runnable r = newRunnable(){
04.
publicvoidrun()
05.
{
06.
try{
07.
ByteWriter writer = newByteArrayWriter();
08.
//最开始执行了一个i请求
09.
writer.write("SIPP".getBytes());
10.
if(!tryExecuteRequest("i", requestId++, writer, 1))
11.
thrownewException("Init Http Transfer failed..");
12.
13.
while(!closeFlag) {
14.
writer.clear();
15.
BytesEntry entry = bytesEntryQueue.poll(5,TimeUnit.SECONDS); //等待五秒,如果没有元素也返回
16.
if(entry!=null) {
17.
writer.writeBytes(entry.getBytes(), entry.getOffset(), entry.getLength()); //
18.
while(bytesEntryQueue.size()>0) {
19.
entry = bytesEntryQueue.poll();
20.
writer.writeBytes(entry.getBytes(), entry.getOffset(), entry.getLength());
21.
}
22.
}
23.
writer.write("SIPP".getBytes());
24.
25.
//尝试发送这个请求,如果超过指定次数,传递传输异常
26.
if(!tryExecuteRequest("s",requestId++,writer,3)) {
27.
closeFlag = true;
28.
thrownewException("execute http request failed..");
29.
}
30.
}
31.
32.
//结束
33.
writer.clear();
34.
writer.write("SIPP".getBytes());
35.
tryExecuteRequest("d", requestId++, writer, 1);
36.
37.
}catch(Throwable e) {
38.
thrownewRuntimeException(e);
39.
}
40.
}
41.
};
42.
43.
this.runThead = newThread(r);
44.
this.runThead.setName("HttpTransfer");
45.
this.runThead.start();
46.
}
47.
48.
/**
49.
* 执行一个请求
50.
* @param s 类型(i,s,d)
51.
* @param i 请求的序号
52.
* @param writer 数据
53.
* @throws IOException
54.
*/
55.
privatevoidexecuteRequest(String s, inti,ByteWriter writer) throwsIOException
56.
{
57.
String turl = this.url+"?";
58.
turl += "t="+s;
59.
turl += "&i="+ Integer.toString(i);
60.
61.
URL realURL = newURL(turl);
62.
HttpURLConnection connection = (HttpURLConnection) realURL.openConnection();
63.
connection.setRequestMethod("POST");
64.
connection.setRequestProperty("User-Agent", "IIC2.0/PC 4.0.3340");
65.
connection.setRequestProperty("Pragma", this.pragma);
66.
connection.setRequestProperty("Content-Type", "application/oct-stream");
67.
connection.setRequestProperty("Accept", "*/*");
68.
connection.setRequestProperty("Cookie", "ssic="+this.ssic);
69.
connection.setDoOutput(true);
70.
OutputStream out = connection.getOutputStream();
71.
out.write(writer.toByteArray(), 0, writer.size());
72.
out.flush();
73.
74.
if(connection.getResponseCode()==200) {
75.
intcontentLength = connection.getContentLength();
76.
if(contentLength>4) {
77.
InputStream in = connection.getInputStream();
78.
writer.clear();
79.
while(contentLength>4){
80.
writer.writeByte(in.read());
81.
contentLength--;
82.
}
83.
if(writer.size()>0) {
84.
this.bytesReceived(writer.toByteArray(), writer.size());
85.
}
86.
}
87.
}else{
88.
thrownewIOException("Invalid response stateCode="+connection.getResponseCode());
89.
}
90.
}
其实HTTP模式下的连接很简单,就是传输SIPC信令包的方式不同而已,其他的没有太大的变化。
可以想象HTTP传输方式就是把SIPC-PROXY传输的信令包换了一种方式传输而已。
但有一点有很大的区别,就是HTTP模式下和PROXY,SSL模式下和在线好友建立对话的方式完全不同。
这个会在下几篇文章中详细的分析。
HTTP模式就分析到这,下一篇分析在SIPC-PROXY,SIPC-SSL这两种模式下,如何和在线的好友建立会话。
