('WebSocket简单使用1关于WebSocket我打算分如下几篇博文来探探路:WebSocket简单使用(一)-概念2WebSocket简单使用(二)-客户端3WebSocket简单使用(三)-服务器端4WebSocket简单使用(四)-完整实例5WebSocket简单使用(五)-模拟微信连接键盘功能背景:前几天老大突然发现微信有个链接键盘的功能,我仔细想了下或许是使用WebSocket来实现的,又加上最近HTML5的呼声愈来愈高,觉得有必要研究下HTML5了。HTML5作为下一代WEB标准,拥有许多引人注目的新特性,如Canvas、本地存储、多媒体编程接口、WebSocket等等。今天我们就来看看具有“WebTCP”之称的WebSocket.WebSocket的出现是基于Web应用的实时性需要而产生的。这种实时的Web应用大家应该不陌生,在生活中都应该用到过,比如新浪微博的评论、私信的通知,腾讯的WebQQ等。让我们来回顾下实时Web应用的窘境吧。在WebSocket出现之前,一般通过两种方式来实现Web实时用:轮询机制和刘技术;其中轮询有不同的轮询,还有一种叫Comet的长轮询。\uf0b7轮询:这是最早的一种实现实时Web应用的方案。客户端以一定的时间间隔向服务端发出请求,以频繁请求的方式来保持客户端和服务器端的同步。这种同步方案的缺点是,当客户端以固定频率向服务器发起请求的时候,服务器端的数据可能并没有更新,这样会带来很多无谓的网络传输,所以这是一种非常低效的实时方案。\uf0b7长轮询:是对定时轮询的改进和提高,目地是为了降低无效的网络传输。当服务器端没有数据更新的时候,连接会保持一段时间周期直到数据或状态改变或者时间过期,通过这种机制来减少无效的客户端和服务器间的交互。当然,如果服务端的数据变更非常频繁的话,这种机制和定时轮询比较起来没有本质上的性能的提高。\uf0b7流:常就是在客户端的页面使用一个隐藏的窗口向服务端发出一个长连接的请求。服务器端接到这个请求后作出回应并不断更新连接状态以保证客户端和服务器端的连接不过期。通过这种机制可以将服务器端的信息源源不断地推向客户端。这种机制在用户体验上有一点问题,需要针对不同的浏览器设计不同的方案来改进用户体验,同时这种机制在并发比较大的情况下,对服务器端的资源是一个极大的考验。上述方式其实并不是真正的实时技术,只是使用了一种技巧来实现的模拟实时。在每次客户端和服务器端交互的时候都是一次HTTP的请求和应答的过程,而每一次的HTTP请求和应答都带有完整的HTTP头信息,这就增加了每次传输的数据量。但这些方式最痛苦的是开发人员,因为不论客户端还是服务器端的实现都很复杂,为了模拟比较真实的实时效果,开发人员往往需要构造两个HTTP连接来模拟客户端和服务器之间的双向通讯,一个连接用来处理客户端到服务器端的数据传输,一个连接用来处理服务器端到客户端的数据传输,这不可避免地增加了编程实现的复杂度,也增加了服务器端的负载,制约了应用系统的扩展性。基于上述弊端,实现Web实时应用的技术出现了,WebSocket通过浏览器提供的API真正实现了具备像C/S架构下的桌面系统的实时通讯能力。其原理是使用JavaScript调用浏览器的API发出一个WebSocket请求至服务器,经过一次握手,和服务器建立了TCP通讯,因为它本质上是一个TCP连接,所以数据传输的稳定性强和数据传输量比较小。WebSocket协议WebSocket协议本质上是一个基于TCP的协议。为了建立一个WebSocket连接,客户端浏览器首先要向服务器发起一个HTTP请求,这个请求和通常的HTTP请求不同,包含了一些附加头信息,其中附加头信息”Upgrade:WebSocket”表明这是一个申请协议升级的HTTP请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的WebSocket连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。下面我们来详细介绍一下WebSocket协议,由于这个协议目前还是处于草案阶段,版本的变化比较快,我们选择目前最新的draft-ietf-hybi-thewebsocketprotocol-17版本来描述WebSocket协议。因为这个版本目前在一些主流的浏览器上比如Chrome,、FireFox、Opera上都得到比较好的支持。通过描述可以看到握手协议客户端发到服务器的内容:GET/chatHTTP/1.1Host:server.example.comUpgrade:websocketConnection:UpgradeSec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ==Origin:http://example.comSec-WebSocket-Protocol:chat,superchatSec-WebSocket-Version:13从服务器到客户端的内容:HTTP/1.1101SwitchingProtocolsUpgrade:websocketConnection:UpgradeSec-WebSocket-Accept:s3pPLMBiTxaQ9kYGzzhZRbK+xOo=Sec-WebSocket-Protocol:chat这些请求和通常的HTTP请求很相似,但是其中有些内容是和WebSocket协议密切相关的。我们需要简单介绍一下这些请求和应答信息,”Upgrade:WebSocket”表示这是一个特殊的HTTP请求,请求的目的就是要将客户端和服务器端的通讯协议从HTTP协议升级到WebSocket协议。其中客户端的Sec-WebSocket-Key和服务器端的Sec-WebSocket-Accept就是重要的握手认证信息了,这些内容将在服务器端实现的博文中讲解。相信通过上文的讲解你应该对WebSocket有了个初步认识了,如果有任何疑问欢迎交流。WebSocket简单使用(二)-客户端感谢黑桃K投递于12-08-24被阅读375次评论0条编辑1关于WebSocket我打算分如下几篇博文来探探路:WebSocket简单使用(一)-概念2WebSocket简单使用(二)-客户端3WebSocket简单使用(三)-服务器端4WebSocket简单使用(四)-完整实例5WebSocket简单使用(五)-模拟微信连接键盘功能背景:前几天老大突然发现微信有个链接键盘的功能,我仔细想了下或许是使用WebSocket来实现的,又加上最近HTML5的呼声愈来愈高,觉得有必要研究下HTML5了。如概念篇中介绍的握手协议,客户端是由浏览器提供了API,所以只要使用JavaScript来简单调用即可,而服务器端是要自己实现的,服务器端将在下个博文来讲。WebSocketJavaScript接口定义:[Constructor(inDOMStringurl,optionalinDOMStringprotocol)]interfaceWebSocket{readonlyattributeDOMStringURL;//readystateconstunsignedshortCONNECTING=0;constunsignedshortOPEN=1;constunsignedshortCLOSED=2;readonlyattributeunsignedshortreadyState;readonlyattributeunsignedlongbufferedAmount;//networkingattributeFunctiononopen;attributeFunctiononmessage;attributeFunctiononclose;booleansend(inDOMStringdata);voidclose();};WebSocketimplementsEventTarget;\uf0b7简单了解下接口方法和属性:readyState表示连接有四种状态:CONNECTING(0):表示还没建立连接;OPEN(1):已经建立连接,可以进行通讯;CLOSING(2):通过关闭握手,正在关闭连接;CLOSED(3):连接已经关闭或无法打开;\uf0b7url是代表WebSocket服务器的网络地址,协议通常是”ws”或“wss(加密通信)”,send方法就是发送数据到服务器端;\uf0b7close方法就是关闭连接;\uf0b7onopen连接建立,即握手成功触发的事件;\uf0b7onmessage收到服务器消息时触发的事件;\uf0b7onerror异常触发的事件;\uf0b7onclose关闭连接触发的事件;JavaScript调用浏览器接口实例如下:varwsServer=\'ws://localhost:8888/Demo\';//服务器地址varwebsocket=newWebSocket(wsServer);//创建WebSocket对象websocket.send("hello");//向服务器发送消息alert(websocket.readyState);//查看websocket当前状态websocket.onopen=function(evt){//已经建立连接};websocket.onclose=function(evt){//已经关闭连接};websocket.onmessage=function(evt){//收到服务器消息,使用evt.data提取};websocket.onerror=function(evt){//产生异常};WebSocket简单使用(三)-服务器端感谢黑桃K投递于12-08-24被阅读632次评论3条编辑关于WebSocket我打算分如下几篇博文来探探路:1WebSocket简单使用(一)-概念2WebSocket简单使用(二)-客户端3WebSocket简单使用(三)-服务器端4WebSocket简单使用(四)-完整实例5WebSocket简单使用(五)-模拟微信连接键盘功能背景:前几天老大突然发现微信有个链接键盘的功能,我仔细想了下或许是使用WebSocket来实现的,又加上最近HTML5的呼声愈来愈高,觉得有必要研究下HTML5了。握手协议的客户端数据已经由浏览器代劳了,服务器端需要我们自己来实现,目前市场上开源的实现也比较多如:\uf0b7KaazingWebSocketGateway(一个Java实现的WebSocketServer);\uf0b7mod_pywebsocket(一个Python实现的WebSocketServer);\uf0b7Netty(一个Java实现的网络框架其中包括了对WebSocket的支持);\uf0b7node.js(一个Server端的JavaScript框架提供了对WebSocket的支持);\uf0b7WebSocket4Net(一个.net的服务器端实现);其实在目前的.net4.5框架中已经实现了WebSocket,不用官方实现,我们自己来写个简单的。服务器端需要根据协议来握手、接收和发送。握手首先我们再来回顾下握手协议:客户端发到服务器的内容:GET/chatHTTP/1.1Host:server.example.comUpgrade:websocketConnection:UpgradeSec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ==Origin:http://example.comSec-WebSocket-Protocol:chat,superchatSec-WebSocket-Version:13从服务器到客户端的内容:HTTP/1.1101SwitchingProtocolsUpgrade:websocketConnection:UpgradeSec-WebSocket-Accept:s3pPLMBiTxaQ9kYGzzhZRbK+xOo=Sec-WebSocket-Protocol:chat关键是服务器端Sec-WebSocket-Accept,它是根据Sec-WebSocket-Key计算出来的:6取出Sec-WebSocket-Key,与一个magicstring“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”连接成一个新的key串;7将新的key串SHA1编码,生成一个由多组两位16进制数构成的加密串;8把加密串进行base64编码生成最终的key,这个key就是Sec-WebSocket-Key;实例代码如下://////生成Sec-WebSocket-Accept/// ///客户端握手信息///Sec-WebSocket-Accept privatestaticstringGetSecKeyAccetp(byte[]handShakeBytes,intbytesLength){stringhandShakeText=Encoding.UTF8.GetString(handShakeBytes,0,bytesLength);stringkey=string.Empty;Regexr=newRegex(@"Sec\\-WebSocket\\-Key:(.?)\\r\\n");Matchm=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"));returnConvert.ToBase64String(encryptionString);}如果握手成功,将会触发客户端的onopen事件。解析接收的客户端信息接收到客户端数据解析规则如下:\uf0b71byte1bit:frame-fin,x0表示该message后续还有frame;x1表示是message的最后一个frame3bit:分别是frame-rsv1、frame-rsv2和frame-rsv3,通常都是x04bit:frame-opcode,x0表示是延续frame;x1表示文本frame;x2表示二进制frame;x3-7保留给非控制frame;x8表示关闭连接;x9表示ping;xA表示pong;xB-F保留给控制frame\uf0b72byte1bit:Mask,1表示该frame包含掩码;0,表示无掩码7bit、7bit+2byte、7bit+8byte:7bit取整数值,若在0-125之间,则是负载数据长度;若是126表示,后两个byte取无符号16位整数值,是负载长度;127表示后8个byte,取64位无符号整数值,是负载长度\uf0b73-6byte:这里假定负载长度在0-125之间,并且Mask为1,则这4个byte是掩码\uf0b77-endbyte:长度是上面取出的负载长度,包括扩展数据和应用数据两部分,通常没有扩展数据;若Mask为1,则此数据需要解码,解码规则为1-4byte掩码循环和数据byte做异或操作。解析代码如下,但没有处理多帧和不包含掩码的包://////解析客户端数据包/// ///服务器接收的数据包///有效数据长度/// privatestaticstringAnalyticData(byte[]recBytes,intrecByteLength){if(recByteLength<2){returnstring.Empty;}boolfin=(recBytes[0]&0x80)==0x80;//1bit,1表示最后一帧if(!fin){returnstring.Empty;//超过一帧暂不处理}boolmask_flag=(recBytes[1]&0x80)==0x80;//是否包含掩码if(!mask_flag){returnstring.Empty;//不包含掩码的暂不处理}intpayload_len=recBytes[1]&0x7F;//数据长度byte[]masks=newbyte[4];byte[]payload_data;if(payload_len==126){Array.Copy(recBytes,4,masks,0,4);payload_len=(UInt16)(recBytes[2]<<8recBytes[3]);payload_data=newbyte[payload_len];Array.Copy(recBytes,8,payload_data,0,payload_len);}elseif(payload_len==127){Array.Copy(recBytes,10,masks,0,4);byte[]uInt64Bytes=newbyte[8];for(inti=0;i<8;i++){uInt64Bytes[i]=recBytes[9-i];}UInt64len=BitConverter.ToUInt64(uInt64Bytes,0);payload_data=newbyte[len];for(UInt64i=0;i///打包服务器数据//////数据///数据包 privatestaticbyte[]PackData(stringmessage){byte[]contentBytes=null;byte[]temp=Encoding.UTF8.GetBytes(message);if(temp.Length<126){contentBytes=newbyte[temp.Length+2];contentBytes[0]=0x81;contentBytes[1]=(byte)temp.Length;Array.Copy(temp,0,contentBytes,2,temp.Length);}elseif(temp.Length<0xFFFF){contentBytes=newbyte[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{//暂不处理超长内容}returncontentBytes;}这里只是简单介绍,下节来做个完整的实例。WebSocket简单使用(四)-完整实例感谢黑桃K投递于12-08-24被阅读666次评论0条编辑关于WebSocket我打算分如下几篇博文来探探路:1WebSocket简单使用(一)-概念2WebSocket简单使用(二)-客户端3WebSocket简单使用(三)-服务器端4WebSocket简单使用(四)-完整实例5WebSocket简单使用(五)-模拟微信连接键盘功能背景:前几天老大突然发现微信有个链接键盘的功能,我仔细想了下或许是使用WebSocket来实现的,又加上最近HTML5的呼声愈来愈高,觉得有必要研究下HTML5了。说是完整的实例,其实并不完整,这里需要说明,这个实例并没有实现并发,也没考虑到算法和资源管理,所谓的完整是有客户端和服务器端,并且能跑起来演示。直接上菜,关于理论请看前面三篇博文,TCP请另看相关知识。客户端代码:Websocketstest
连接服务器发送我的名字:beston查看状态
服务器端代码:usingSystem;usingSystem.Net;usingSystem.Net.Sockets;usingSystem.Security.Cryptography;usingSystem.Text;usingSystem.Text.RegularExpressions;namespaceWebSocket{classProgram{staticvoidMain(string[]args){intport=1818;byte[]buffer=newbyte[1024];IPEndPointlocalEP=newIPEndPoint(IPAddress.Any,port);Socketlistener=newSocket(localEP.Address.AddressFamily,SocketType.Stream,ProtocolType.Tcp);try{listener.Bind(localEP);listener.Listen(10);Console.WriteLine("等待客户端连接....");Socketsc=listener.Accept();//接受一个连接Console.WriteLine("接受到了客户端:"+sc.RemoteEndPoint.ToString()+"连接....");//握手intlength=sc.Receive(buffer);//接受客户端握手信息sc.Send(PackHandShakeData(GetSecKeyAccetp(buffer,length)));Console.WriteLine("已经发送握手协议了....");//接受客户端数据Console.WriteLine("等待客户端数据....");length=sc.Receive(buffer);//接受客户端信息stringclientMsg=AnalyticData(buffer,length);Console.WriteLine("接受到客户端数据:"+clientMsg);//发送数据stringsendMsg="您好,"+clientMsg;Console.WriteLine("发送数据:“"+sendMsg+"”至客户端....");sc.Send(PackData(sendMsg));Console.WriteLine("演示Over!");}catch(Exceptione){Console.WriteLine(e.ToString());}}//////打包握手信息/// ///Sec-WebSocket-Accept///数据包 privatestaticbyte[]PackHandShakeData(stringsecKeyAccept){varresponseBuilder=newStringBuilder();responseBuilder.Append("HTTP/1.1101SwitchingProtocols"+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);returnEncoding.UTF8.GetBytes(responseBuilder.ToString());}//////生成Sec-WebSocket-Accept/// ///客户端握手信息///Sec-WebSocket-Accept privatestaticstringGetSecKeyAccetp(byte[]handShakeBytes,intbytesLength){stringhandShakeText=Encoding.UTF8.GetString(handShakeBytes,0,bytesLength);stringkey=string.Empty;Regexr=newRegex(@"Sec\\-WebSocket\\-Key:(.?)\\r\\n");Matchm=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"));returnConvert.ToBase64String(encryptionString);}//////解析客户端数据包/// ///服务器接收的数据包///有效数据长度/// privatestaticstringAnalyticData(byte[]recBytes,intrecByteLength){if(recByteLength<2){returnstring.Empty;}boolfin=(recBytes[0]&0x80)==0x80;//1bit,1表示最后一帧if(!fin){returnstring.Empty;//超过一帧暂不处理}boolmask_flag=(recBytes[1]&0x80)==0x80;//是否包含掩码if(!mask_flag){returnstring.Empty;//不包含掩码的暂不处理}intpayload_len=recBytes[1]&0x7F;//数据长度byte[]masks=newbyte[4];byte[]payload_data;if(payload_len==126){Array.Copy(recBytes,4,masks,0,4);payload_len=(UInt16)(recBytes[2]<<8recBytes[3]);payload_data=newbyte[payload_len];Array.Copy(recBytes,8,payload_data,0,payload_len);}elseif(payload_len==127){Array.Copy(recBytes,10,masks,0,4);byte[]uInt64Bytes=newbyte[8];for(inti=0;i<8;i++){uInt64Bytes[i]=recBytes[9-i];}UInt64len=BitConverter.ToUInt64(uInt64Bytes,0);payload_data=newbyte[len];for(UInt64i=0;i///打包服务器数据//////数据///数据包 privatestaticbyte[]PackData(stringmessage){byte[]contentBytes=null;byte[]temp=Encoding.UTF8.GetBytes(message);if(temp.Length<126){contentBytes=newbyte[temp.Length+2];contentBytes[0]=0x81;contentBytes[1]=(byte)temp.Length;Array.Copy(temp,0,contentBytes,2,temp.Length);}elseif(temp.Length<0xFFFF){contentBytes=newbyte[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{//暂不处理超长内容}returncontentBytes;}}}运行效果:使用的浏览器:下载代码:WebSocket.zip疑问:如实例中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);WebSocket简单使用(五)-模拟微信连接键盘功能感谢黑桃K投递于12-08-28被阅读508次评论0条编辑关于WebSocket我打算分如下几篇博文来探探路:1WebSocket简单使用(一)-概念2WebSocket简单使用(二)-客户端3WebSocket简单使用(三)-服务器端4WebSocket简单使用(四)-完整实例5WebSocket简单使用(五)-模拟微信连接键盘功能背景:前几天老大突然发现微信有个链接键盘的功能,我仔细想了下或许是使用WebSocket来实现的,又加上最近HTML5的呼声愈来愈高,觉得有必要研究下HTML5了。什么是”连接键盘“功能”连接键盘“功能其实就是开通了网页版的微信,利用键盘可以快速录入文字聊天。连接方法很简单,用手机打开微信点击右上角魔术棒,会弹出三个选项,只用选择连接键盘就可以了,这时用浏览器打开wx.qq.com然后用手机扫描网页中的二维码即可打开网页版微信,而这时也就可以直接利用电脑键盘实现快速聊天了。下载Demo源码真不愧是把妹利器,创意很有点意思。本文就来模拟下,其实有点类似远程控制,下载DEMO代码,解压后目录如下:WebSocket-Server里项目含义如下:\uf0b7Mobile:手机模拟器,与手机通讯服务器进行UDP通讯,负责提示打开的页面地址,并输入GUID(相当于二维码)与页面进行绑定;\uf0b7MobileServer:手机通讯服务器,负责接收手机信息(比如微信的账户信息以及二维码信息),此处接收GUID。并转发至WebSocket通讯服务器;\uf0b7WebSocket:WebSocket通讯服务器,与手机通讯服务器和页面的WebSocket进行通讯;WebSocket-Client里项目含义如下:\uf0b7test.html:测试的web页面(类似微信的wx.qq.com);\uf0b7jquery-1.8.0.min.js:jquery框架;实现原理页面test.html生成GUID并存储在WebSocket,手机模拟器输入GUID并传至WebSocket服务器,在WebSocket服务器检索页面Socket信息并通讯。运行DEMO步骤6使用IIS本地部署WebSocket-Client站点,端口为8075;7启动手机通讯服务器、WebSocket通讯服务器;8启动手机模拟器,按提示使用GoogleChorme打开地址:http://localhost:8075/test.html;9打开上一步骤页面地址,页面地址会生成GUID,并通过WebSocket传至WebSocket通讯服务器存储;10在手机模拟器属于该GUID,手机模拟器依次传入手机通讯服务器、WebSocket通讯服务器,在WebSocket通讯服务器中根据GUID检索WebSocket信息并发送信息至页面;11页面接收到信息,此处只传了GUID,把GUID当做get参数传递,演示OVER;注意事项12如果自己测试请根据上述步骤先启动手机通讯服务器和WebSocket通讯服务器;13把所有是“10.9.146.31”的字符串更换为自己的IP;',)