跳转到主内容
思享编程网:思考分享,玩转编程世界!

飞信2010分析 – 使用HTTP模式和服务器通信

飞信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这两种模式下,如何和在线的好友建立会话。

相关文章