博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
WebSocket 学习笔记--IE,IOS,Android等设备的兼容性问题与代码实现
阅读量:5757 次
发布时间:2019-06-18

本文共 12476 字,大约阅读时间需要 41 分钟。

一、背景

 

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

 

 二、协议分析

2.1 WebSocket的请求包

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

IE11:

GET /chat HTTP/1.1Origin: http://localhostSec-WebSocket-Key: 98JFoEb6pMLFYhAQATn6hw==Connection: UpgradeUpgrade: WebsocketSec-WebSocket-Version: 13User-Agent: Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like GeckoHost: 127.0.0.1:1333Cache-Control: no-cacheCookie: s_pers=%20s_20s_nr%3D1390552565333-Repeat%7C1422088565333%3B

FireFox 26.0:

GET /chat HTTP/1.1Host: 127.0.0.1:1333User-Agent: Mozilla/5.0 (Windows NT 6.3; rv:26.0) Gecko/20100101 Firefox/26.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3Accept-Encoding: gzip, deflateSec-WebSocket-Version: 13Origin: http://localhostSec-WebSocket-Key: kO4aF1Gpm1mBwOr6j30h0Q==Connection: keep-alive, UpgradePragma: no-cacheCache-Control: no-cacheUpgrade: websocket

Google Chrome 33.0.1707.0 :

GET /chat HTTP/1.1Upgrade: websocketConnection: UpgradeHost: 127.0.0.1:1333Origin: http://192.168.137.1Pragma: no-cacheCache-Control: no-cacheSec-WebSocket-Key: NvxdeWLLsLXkt5DirLJ1yA==Sec-WebSocket-Version: 13Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits, x-webkit-deflate-frameUser-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.1Upgrade: WebSocketConnection: UpgradeHost: 127.0.0.1:1333Origin: http://localhostSec-WebSocket-Key1: 25 58   510 64   > =  7Sec-WebSocket-Key2: 13q9% 974M${fdj6 2`Qn32�����R�q

 

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

Sec-WebSocket-Version: 13

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

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

 

WebSocket握手协议 

 

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的浏览器内核

在android的webview中实现websocket 

但同事说,这些方法用过了,就是现在测试的效果,跟真正的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 处理代码,包括握手、打包数据等:

//         /// 打包握手信息        /// 
http://www.hoverlees.com/blog/?p=1413 Safari 早期版本不支持标准的version 13,握手不成功。 /// 据测试,最新的IOS 7.0 支持 ///
///
/// Sec-WebSocket-Accept ///
数据包
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()); } /// /// 生成Sec-WebSocket-Accept /// /// 客户端握手信息 ///
Sec-WebSocket-Accept
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); } /// /// 解析客户端数据包 /// /// 服务器接收的数据包 /// 有效数据长度 ///
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); } /// /// 打包服务器数据 /// /// 数据 ///
数据包
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代码:

    
Web sockets test Server IP:

 但是上面的代码依然无法处理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://impkx.baihongyu.com/

你可能感兴趣的文章
javathreadpattern_01_singleThreadPattern
查看>>
双机热备软件在Linux环境下的配置方法
查看>>
LVS负载均衡原理和算法详解
查看>>
scp 问题
查看>>
Java基础学习总结(16)——Java制作证书的工具keytool用法总结
查看>>
嵌入式 Linux进程间通信(八)——共享内存
查看>>
Spring常用注解
查看>>
Oracle instr()函数替代like实现模糊查询
查看>>
H3C RIP
查看>>
My SQL的内连接,外链接查询
查看>>
JAVA的异常(三):runtime异常
查看>>
2013.10.20]Eclipse配置Git全过程-----------附用EGit不能push的问题解决
查看>>
删除整张表数据但是空间没有减少
查看>>
我的友情链接
查看>>
IBM服务器直连存储系统不启动【经验分享】
查看>>
Android 动画之Interpolator插入器
查看>>
JavaScript DOM编程艺术 边读边感(1)
查看>>
win7下安装Ubuntu后修改启动项顺序三方法
查看>>
shell判断进程启动时间后kill进程
查看>>
linux双网卡绑定
查看>>