WebSocket 学习笔记--IE,IOS,Android等设备的兼容性问题与代码实现

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介:

一、背景

 

公司最近准备将一套产品放到Andriod和IOS上面去,为了统一应用的开发方式,决定用各平台APP嵌套一个HTML5浏览器来实现,其中数据通信,准备使用WebSocket的方式。于是,我开始在各大浏览器上测试。

 

 二、协议分析

2.1 WebSocket的请求包


首先把原来做Socket通信的程序拿出来,跟踪下浏览器在WebSocket应用请求服务端的时候发的数据包的内容:

IE11:

复制代码
GET /chat HTTP/1.1
Origin: http://localhost
Sec-WebSocket-Key: 98JFoEb6pMLFYhAQATn6hw==
Connection: Upgrade
Upgrade: Websocket
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko
Host: 127.0.0.1:1333
Cache-Control: no-cache
Cookie: s_pers=%20s_20s_nr%3D1390552565333-Repeat%7C1422088565333%3B
复制代码

FireFox 26.0:

复制代码
GET /chat HTTP/1.1
Host: 127.0.0.1:1333
User-Agent: Mozilla/5.0 (Windows NT 6.3; rv:26.0) Gecko/20100101 Firefox/26.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Sec-WebSocket-Version: 13
Origin: http://localhost
Sec-WebSocket-Key: kO4aF1Gpm1mBwOr6j30h0Q==
Connection: keep-alive, Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
复制代码

Google Chrome 33.0.1707.0 :

复制代码
GET /chat HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: 127.0.0.1:1333
Origin: http://192.168.137.1
Pragma: no-cache
Cache-Control: no-cache
Sec-WebSocket-Key: NvxdeWLLsLXkt5DirLJ1yA==
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits, x-webkit-deflate-frame
User-Agent: Mozilla/5.0 (Windows NT 6.3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1707.0 Safari/537.36
复制代码

Apple Safari 5.1.7(7534.57.2)Windows 版本

复制代码
GET /chat HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Host: 127.0.0.1:1333
Origin: http://localhost
Sec-WebSocket-Key1: 25 58   510 64   > =  7
Sec-WebSocket-Key2: 13q9% 974M${fdj6 2`Qn32

�����R�q
复制代码

 

分析了下这几种不同的浏览器,除了 Safari,其它均使用了最新的 WebSocket版本 即 
Sec-WebSocket-Version: 13

但是 Safrai 使用的还是老式的 WebSocket 7.5-7.6版本。

有下面一些文章,对于WebSocket的版本进行了说明:

WebSocket握手总结 http://www.hoverlees.com/blog/?p=1413

基于Websocket草案10协议的升级及基于Netty的握手实现         http://blog.csdn.net/fenglibing/article/details/6852497

WebSocket握手协议 http://blog.163.com/liwei1987821@126/blog/static/17266492820133190211333/

 

2.2,IE 对WebSocket的支持问题

 

这里有必要单独拿出来说,IE10以后才支持HTML5,因此也要这个版本后的浏览器才支持WebSocket,所以默认的Win7下面的浏览器都没法支持。我测试用的是Win8.1的IE11,可以支持WebSocket,效果跟FireFox、Chrome一样,但有一个恼火的问题,IE的WebSocket它会自动向服务器端发起“心跳包”,而此时服务端使用SockeAsyncEventArgs 组件,会出问题,需要客户端多次发数据之后,才会取到正确的客户端请求的数据。

另外,.NET 4.5内置支持了WebSocket,但要求操作系统是Win8或者 Server2012以上的版本。所以如果生产环境的服务器没有这么新,那么WebSocketServer只有自己写了。

 

2.3,IOS系统上WebSoket问题

Apple 内置的浏览器就是 Safrai,那么IOS上面的浏览器 支持的 WebSocket 版本怎么样呢 ?

找了下同事的 iPhone 4s,IOS 7.0.1 的版本 ,经过测试 ,正常,跟其它浏览器一样,但不知道其它版本的IOS下面的浏览器支持得 怎么样。这就奇怪了,为何Windows 桌面版本的Safrai 不行呢 ?

 

2.4,安卓上的WebSocket问题

 

很不幸,目前安卓最新的版本 ,内置的浏览器插件仍然不支持WebSocket,而下载的QQ浏览器等是可以支持的。但是安卓上面的 App默认使用的都是 Andriod内核的浏览器插件,因此它们没法支持WebSocket。但是,既然是系统上面运行的 APP了,为何不直接走Socket 通信方式呢?同事说是为了2个平台能够使用同一套Web应用,毕竟应用嵌套在一个浏览器里面对于开发维护还是最方便的。

所以,解决的路径还是想办法让安卓的默认浏览器插件能够支持WebSocket,查找了下资料,大概有这些资料:

android怎么集成支持websocket的浏览器内核 http://www.oschina.net/question/1049351_116337

在android的webview中实现websocket http://xuepiaoqiyue.blog.51cto.com/4391594/1285791

但同事说,这些方法用过了,就是现在测试的效果,跟真正的WebSocket 兼容得不好,使用我的程序测试可以握手连接,但是解析内容上不成功。后来分析,是同事的程序对数据有特殊格式的要求,只要按照他的要求去分析,那么是可以解析得到正确的结果的。

 

三、WebSocket 服务端和客户端实现

 

最新的WebSocket 13 版本支持的服务端代码:

SocketServer 对于WebSocket信息的处理:

复制代码
private void ProcessReceive(SocketAsyncEventArgs e)
        {
            // check if the remote host closed the connection
            AsyncUserToken token = (AsyncUserToken)e.UserToken;
            if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
            {
                //increment the count of the total bytes receive by the server
                Interlocked.Add(ref m_totalBytesRead, e.BytesTransferred);
                Console.WriteLine("The server has read a total of {0} bytes", m_totalBytesRead);

                //echo the data received back to the client
                //增加自定义处理
                string received = System.Text.Encoding.UTF8.GetString(e.Buffer, e.Offset, e.BytesTransferred);
                Byte[] sendBuffer = null;

                //IE:Upgrade:   Websocket
                //FireFox:      Upgrade: websocket
                //Chrome:       Upgrade: websocket
                if (received.IndexOf("Upgrade: websocket", StringComparison.OrdinalIgnoreCase) > 0)
                {
                    //Web Socket 初次连接
                    token.Name = "WebSocket";
                    Console.WriteLine("Accept WebSocket.");
                    sendBuffer=PackHandShakeData(GetSecKeyAccetp(received));
                    
                }
                else
                {
                    if (token.Name == "WebSocket")
                    {
                        string clientMsg;
                        if (e.Offset > 0)
                        {
                            byte[] buffer = new byte[e.BytesTransferred];
                            Array.Copy(e.Buffer, e.Offset, buffer, 0, buffer.Length);
                            clientMsg = AnalyticData(buffer, buffer.Length);

                            Console.WriteLine("--DEBUG:Web Socket Recive data offset:{0}--",e.Offset);
                        }
                        else
                        {
                            clientMsg = AnalyticData(e.Buffer, e.BytesTransferred);
                        }

                        Console.WriteLine("接受到客户端数据:" + clientMsg);
                        if (!string.IsNullOrEmpty(clientMsg))
                        {
                            //解析来的真正消息,执行服务器处理
                            //To Do
                            //
                            //发送数据
                            string sendMsg = "Hello," + clientMsg;
                            Console.WriteLine("发送数据:“" + sendMsg + "” 至客户端....");

                            sendBuffer = PackData(sendMsg);
                        }
                        else
                        {
                            sendBuffer = PackData("心跳包");
                        }
                        
                    }
                    else
                    {
                        Console.WriteLine("服务器接收到的数据:[{0}],将进行1000ms的处理。CurrentThread.ID:{1}", received, System.Threading.Thread.CurrentThread.ManagedThreadId);
                        System.Threading.Thread.Sleep(1000);
                        Console.WriteLine("线程{0} 任务处理结束,发送数据", System.Threading.Thread.CurrentThread.ManagedThreadId);

                        //  格式化数据后发回客户端。 
                        sendBuffer = Encoding.UTF8.GetBytes("Returning " + received);
                    }
                }

                //设置传回客户端的缓冲区。 
                e.SetBuffer(sendBuffer, 0, sendBuffer.Length);
                //结束
                bool willRaiseEvent = token.Socket.SendAsync(e);
                if (!willRaiseEvent)
                {
                    ProcessSend(e);
                }

            }
            else
            {
                CloseClientSocket(e);
            }
        }
复制代码

下面是一些相关的WebSocket 处理代码,包括握手、打包数据等:

复制代码
// <summary>
        /// 打包握手信息
        /// <remarks> http://www.hoverlees.com/blog/?p=1413 Safari 早期版本不支持标准的version 13,握手不成功。
        /// 据测试,最新的IOS 7.0 支持
        /// </remarks>
        /// </summary>
        /// <param name="secKeyAccept">Sec-WebSocket-Accept</param>
        /// <returns>数据包</returns>
        private static byte[] PackHandShakeData(string secKeyAccept)
        {
            var responseBuilder = new StringBuilder();
            responseBuilder.Append("HTTP/1.1 101 Switching Protocols" + Environment.NewLine);
            responseBuilder.Append("Upgrade: websocket" + Environment.NewLine);
            responseBuilder.Append("Connection: Upgrade" + Environment.NewLine);
            responseBuilder.Append("Sec-WebSocket-Accept: " + secKeyAccept + Environment.NewLine + Environment.NewLine);
            //如果把上一行换成下面两行,才是thewebsocketprotocol-17协议,但居然握手不成功,目前仍没弄明白!
            //responseBuilder.Append("Sec-WebSocket-Accept: " + secKeyAccept + Environment.NewLine);
            //responseBuilder.Append("Sec-WebSocket-Protocol: chat" + Environment.NewLine);

            return Encoding.UTF8.GetBytes(responseBuilder.ToString());
        }

        /// <summary>
        /// 生成Sec-WebSocket-Accept
        /// </summary>
        /// <param name="handShakeText">客户端握手信息</param>
        /// <returns>Sec-WebSocket-Accept</returns>
        private static string GetSecKeyAccetp(string handShakeText) //byte[] handShakeBytes, int bytesLength
        {
            //string handShakeText = Encoding.UTF8.GetString(handShakeBytes, 0, bytesLength);
            string key = string.Empty;
            Regex r = new Regex(@"Sec\-WebSocket\-Key:(.*?)\r\n");
            Match m = r.Match(handShakeText);
            if (m.Groups.Count != 0)
            {
                key = Regex.Replace(m.Value, @"Sec\-WebSocket\-Key:(.*?)\r\n", "$1").Trim();
            }
            byte[] encryptionString = SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
            return Convert.ToBase64String(encryptionString);
        }

        /// <summary>
        /// 解析客户端数据包
        /// </summary>
        /// <param name="recBytes">服务器接收的数据包</param>
        /// <param name="recByteLength">有效数据长度</param>
        /// <returns></returns>
        private static string AnalyticData(byte[] recBytes, int recByteLength)
        {
            if (recByteLength < 2) { return string.Empty; }

            bool fin = (recBytes[0] & 0x80) == 0x80; // 1bit,1表示最后一帧  
            if (!fin)
            {
                return string.Empty;// 超过一帧暂不处理 
            }

            bool mask_flag = (recBytes[1] & 0x80) == 0x80; // 是否包含掩码  
            if (!mask_flag)
            {
                return string.Empty;// 不包含掩码的暂不处理
            }

            int payload_len = recBytes[1] & 0x7F; // 数据长度  

            byte[] masks = new byte[4];
            byte[] payload_data;

            if (payload_len == 126)
            {
                Array.Copy(recBytes, 4, masks, 0, 4);
                payload_len = (UInt16)(recBytes[2] << 8 | recBytes[3]);
                payload_data = new byte[payload_len];
                Array.Copy(recBytes, 8, payload_data, 0, payload_len);

            }
            else if (payload_len == 127)
            {
                Array.Copy(recBytes, 10, masks, 0, 4);
                byte[] uInt64Bytes = new byte[8];
                for (int i = 0; i < 8; i++)
                {
                    uInt64Bytes[i] = recBytes[9 - i];
                }
                UInt64 len = BitConverter.ToUInt64(uInt64Bytes, 0);

                payload_data = new byte[len];
                for (UInt64 i = 0; i < len; i++)
                {
                    payload_data[i] = recBytes[i + 14];
                }
            }
            else
            {
                Array.Copy(recBytes, 2, masks, 0, 4);
                payload_data = new byte[payload_len];
                //修改,WebSocket如果自动触发了心跳,之后再发送数据,可能出错,增加下面的判断
                if (recBytes.Length < 6 + payload_len)
                    Array.Copy(recBytes, 6, payload_data, 0, recBytes.Length - 6); 
                else
                    Array.Copy(recBytes, 6, payload_data, 0, payload_len);

            }

            for (var i = 0; i < payload_len; i++)
            {
                payload_data[i] = (byte)(payload_data[i] ^ masks[i % 4]);
            }

            return Encoding.UTF8.GetString(payload_data);
        }


        /// <summary>
        /// 打包服务器数据
        /// </summary>
        /// <param name="message">数据</param>
        /// <returns>数据包</returns>
        private static byte[] PackData(string message)
        {
            byte[] contentBytes = null;
            byte[] temp = Encoding.UTF8.GetBytes(message);

            if (temp.Length < 126)
            {
                contentBytes = new byte[temp.Length + 2];
                contentBytes[0] = 0x81;
                contentBytes[1] = (byte)temp.Length;
                Array.Copy(temp, 0, contentBytes, 2, temp.Length);
            }
            else if (temp.Length < 0xFFFF)
            {
                contentBytes = new byte[temp.Length + 4];
                contentBytes[0] = 0x81;
                contentBytes[1] = 126;
                contentBytes[2] = (byte)(temp.Length & 0xFF);
                contentBytes[3] = (byte)(temp.Length >> 8 & 0xFF);
                Array.Copy(temp, 0, contentBytes, 4, temp.Length);
            }
            else
            {
                // 暂不处理超长内容  
            }

            return contentBytes;
        }  
复制代码

测试配套的客户端 HTML代码:

复制代码
<html>
<head>
    <meta charset="UTF-8">
    <title>Web sockets test</title>
    <script type="text/javascript">
        var ws;
        function ToggleConnectionClicked() {
            try {
                var ip = document.getElementById("txtIP").value;
                ws = new WebSocket("ws://" + ip + ":1333/chat"); //连接服务器    ws://localhost:1818/chat    
                    
                    ws.onopen = function(event){alert("已经与服务器建立了连接\r\n当前连接状态:"+this.readyState);};
                    ws.onmessage = function(event){alert("接收到服务器发送的数据:\r\n"+event.data);};
                    ws.onclose = function(event){alert("已经与服务器断开连接\r\n当前连接状态:"+this.readyState);};
                    ws.onerror = function(event){alert("WebSocket异常!");};
                } catch (ex) {
                    alert(ex.message);      
                }
        };

        function SendData() {
            try{
                ws.send("张三.");
            }catch(ex){
                alert(ex.message);
            }
        };

        function seestate(){
            alert(ws.readyState);
        }
       
    </script>
</head>
<body>
   Server IP:<input type="text" id="txtIP"  value="127.0.0.1"/> <button id='ToggleConnection' type="button" onclick='ToggleConnectionClicked();'>连接服务器</button><br /><br />
    <button id='ToggleConnection' type="button" onclick='SendData();'>发送我的名字:beston</button><br /><br />
    <button id='ToggleConnection' type="button" onclick='seestate();'>查看状态</button><br /><br />
</body>
</html>
复制代码

 但是上面的代码依然无法处理IE的“心跳”数据引起的问题。此时需要修改一下WebSocket对接受到数据的处理方式,如果客户端发送的是无效的数据,比如IE的心跳数据 ,那么直接过滤,不写入任何数据,将服务端的代码做下面的修改即可:

复制代码
               if (sendBuffer != null)
                {
                    //设置传回客户端的缓冲区。 
                    e.SetBuffer(sendBuffer, 0, sendBuffer.Length);
                    //结束
                    bool willRaiseEvent = token.Socket.SendAsync(e);
                    if (!willRaiseEvent)
                    {
                        ProcessSend(e);
                    }
                }
                else
                {
                    ProcessSend(e);
                }
复制代码

这样,就得到了最终正确的结果了。此问题困扰了我好几天。下面是运行结果图:

 

 


    本文转自深蓝医生博客园博客,原文链接:http://www.cnblogs.com/bluedoctor/p/3534087.html,如需转载请自行联系原作者



相关文章
|
8天前
|
IDE Android开发 iOS开发
探索Android与iOS开发的差异:平台选择对项目成功的影响
【9月更文挑战第27天】在移动应用开发的世界中,Android和iOS是两个主要的操作系统平台。每个系统都有其独特的开发环境、工具和用户群体。本文将深入探讨这两个平台的关键差异点,并分析这些差异如何影响应用的性能、用户体验和最终的市场表现。通过对比分析,我们将揭示选择正确的开发平台对于确保项目成功的重要作用。
|
9天前
|
Web App开发 JavaScript 前端开发
添加浮动按钮点击滚动到网页底部的纯JavaScript演示代码 IE9、11,Maxthon 1.6.7,Firefox30、31,360极速浏览器7.5.3.308下测试正常
添加浮动按钮点击滚动到网页底部的纯JavaScript演示代码 IE9、11,Maxthon 1.6.7,Firefox30、31,360极速浏览器7.5.3.308下测试正常
|
5天前
|
开发框架 移动开发 Android开发
安卓与iOS开发中的跨平台解决方案:Flutter入门
【9月更文挑战第30天】在移动应用开发的广阔舞台上,安卓和iOS两大操作系统各自占据半壁江山。开发者们常常面临着选择:是专注于单一平台深耕细作,还是寻找一种能够横跨两大系统的开发方案?Flutter,作为一种新兴的跨平台UI工具包,正以其现代、响应式的特点赢得开发者的青睐。本文将带你一探究竟,从Flutter的基础概念到实战应用,深入浅出地介绍这一技术的魅力所在。
23 7
|
8天前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台解决方案
【9月更文挑战第27天】在移动应用开发的广阔天地中,安卓和iOS两大操作系统如同双子星座般耀眼。开发者们在这两大平台上追逐着创新的梦想,却也面临着选择的难题。如何在保持高效的同时,实现跨平台的开发?本文将带你探索跨平台开发的魅力所在,揭示其背后的技术原理,并通过实际案例展示其应用场景。无论你是安卓的忠实拥趸,还是iOS的狂热粉丝,这篇文章都将为你打开一扇通往跨平台开发新世界的大门。
|
2天前
|
开发工具 Android开发 iOS开发
移动应用开发的艺术:探索Android与iOS的操作系统特性
【9月更文挑战第33天】在数字时代的浪潮中,移动应用已成为我们日常生活不可或缺的一部分。本文将深入探讨两个主流移动操作系统——Android和iOS——的独特特性,并分析它们如何影响移动应用的开发过程。我们将通过比较这两个系统的设计哲学、用户界面(UI)设计、开发工具以及市场策略,来揭示开发者如何在这些不同的平台上打造出色的用户体验。无论你是开发者还是对移动技术感兴趣的读者,这篇文章都将为你提供宝贵的见解。
|
9天前
|
开发工具 Android开发 iOS开发
安卓与iOS开发环境对比:选择适合你的平台
【9月更文挑战第26天】在移动应用开发的广阔天地中,安卓和iOS是两大巨头。它们各自拥有独特的优势和挑战,影响着开发者的选择和决策。本文将深入探讨这两个平台的开发环境,帮助你理解它们的核心差异,并指导你根据个人或项目需求做出明智的选择。无论你是初学者还是资深开发者,了解这些平台的异同都至关重要。让我们一起探索,找到最适合你的那片开发天地。
|
9天前
|
搜索推荐 Android开发 数据安全/隐私保护
探索安卓与iOS的未来发展
本文旨在探讨移动操作系统领域的两大巨头——安卓和iOS的未来发展。通过对技术革新、用户体验优化以及市场竞争格局的综合分析,揭示两者在各自生态系统中的创新路径与潜在挑战。
21 0
|
9天前
|
安全 Android开发 数据安全/隐私保护
探索安卓与iOS的安全性差异:一场永无止境的较量
在移动操作系统的双寡头格局中,安卓(Android)与iOS以其独特的魅力和优势各自占据了半壁江山。然而,正如硬币的两面,它们在安全性方面的差异也成为了用户、开发者乃至整个行业关注的焦点。本文旨在深入剖析安卓与iOS在安全性上的不同表现,探讨其背后的原因,并展望未来两者在安全性领域的发展趋势。通过对比分析,我们将揭示这场永无止境的较量中,谁更能为用户的数据安全保驾护航。
|
3月前
|
前端开发 网络协议 JavaScript
在Spring Boot中实现基于WebSocket的实时通信
在Spring Boot中实现基于WebSocket的实时通信
|
2月前
|
开发框架 网络协议 Java
SpringBoot WebSocket大揭秘:实时通信、高效协作,一文让你彻底解锁!
【8月更文挑战第25天】本文介绍如何在SpringBoot项目中集成WebSocket以实现客户端与服务端的实时通信。首先概述了WebSocket的基本原理及其优势,接着详细阐述了集成步骤:添加依赖、配置WebSocket、定义WebSocket接口及进行测试。通过示例代码展示了整个过程,旨在帮助开发者更好地理解和应用这一技术。
88 1
下一篇
无影云桌面