建立了Socket服务端,服务器数据有变动,想要推送给客户端,如果是C/S模式,使用Socket自然没问题;如果是B/S呢,没有Socket怎么办?
html5支持WebSocket,这是一个不错的改善。
想起没有顺丰、韵达、各种通等之类快递的日子,你以为远方的朋友会给你邮寄包裹——虽然不会有人这样做——然后你就翻山越岭天天去邮局问,“有没有我的包裹啊”,运气好的话,有,你领回去了,运气不好,对不起接着坚持来问吧。于是,跑啊跑啊,累个半死不活。
不过就你自己累,邮局无所谓,反正不用给你送货上门。
但架不住人多啊,更多的像你一样的人有你那样的想法了,跑邮局,人多邮局就出问题了,每天只回答有没有包裹这个问题都忙到嘴没工夫闭上的地步,无法进行日常的发信和拍电报业务了。
于是邮局崩溃了。
这时,快递产生了。
他们负责送货上门。住在城市中的人幸福了,不用跑邮局了。不过住在乡下的人还是郁闷,他们以为快递会平等对待给他们送货上门,也不跑邮局了,可等来等去,等到了一句话:乡下交通不便,恕不配送。。
kao kao kao
html5 WebSocket同样遇到了这样的尴尬。对IE,它只兼容10以及以上的版本(其它浏览器不考虑,IE是大户)。对于6789这些依旧没能淘汰掉的老古董,开发人员只能采用别的方式解决。
有人说Nodejs可以。
不错,确实可以。
不过我想说的是,Nodejs的socket.io应该是判断了浏览器的兼容性之后做出的选择吧?支持web socket的采用websocket,不支持的采用别的方式(跑邮局)?
工作也这么多年了,script一直是我的弱项。。。。
虽然照猫画虎使用nodejs及socket.io写出了同样的功能,但心里总是没底。。。打脸-ing
我说nodejs没别的意思,只是我不会。。。
***********************************************************分割线***********************************************************
之前有看园子里的文章,知道使用Flash的socket可以解决兼容性的问题,但由于flex已经好几年没碰了,就没心思去搞。
使用nodejs处理之后由于底气不足不敢在线上服务器用啊,于是转头copy别人的代码使用flash去解决socket问题,搞来搞去园友的文章不是抄别人的就是给出的flash是只适合自己用的,总也调试不通,干脆自己搞,也就是重拾一下as和flex而已。
不多说废话了(打脸-ing),上代码吧。
***********************************************************分割线***********************************************************
首先,创建Socket服务端,c#代码(我是使用Windows服务作Socket服务端,至于别的方式请自行脑补):
1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Data;
5 using System.Diagnostics;
6 using System.Linq;
7 using System.Net;
8 using System.Net.Sockets;
9 using System.Reflection;
10 using System.ServiceProcess;
11 using System.Text;
12 using log4net;
13
14 namespace YourNameSpace
15 {
16 partial class FlashSocket : ServiceBase
17 {
18 private static readonly ILog Logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
19 private static Socket server;
20
21 private static Socket handSocket;
22
23 private static string message = "testset";
24 /// <summary>
25 /// 需要维护的一个Socket Client集合
26 /// </summary>
27 private static List<Socket> Clients = new List<Socket>();
28 public FlashSocket()
29 {
30 InitializeComponent();
31 }
32
33 protected override void OnStart(string[] args)
34 {
35 // TODO: 在此处添加代码以启动服务。
36 CreateSocketServer();
37 System.Timers.Timer tmr = new System.Timers.Timer();
38 tmr.Interval = 1000 * 3;
39 tmr.Elapsed += (sender, e) =>
40 {
41 DoWork();
42 };
43 tmr.Start();
44 }
45
46
47 protected override void OnStop()
48 {
49 // TODO: 在此处添加代码以执行停止服务所需的关闭操作。
50 }
51
52 #region 创建Socket服务端
53
54 private static void CreateSocketServer()
55 {
56 AllowDomain ad = new AllowDomain();
57 server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
58 server.Bind(new IPEndPoint(IPAddress.Parse("192.168.5.134"), 2013));
59 server.Listen(int.MaxValue);
60 server.BeginAccept(InsertClientToCollection, server);
61 }
62 #endregion
63
64 #region 有新客户端访问的时候添加进来
65 /// <summary>
66 /// 有新客户端访问的时候添加到客户端集合中
67 /// </summary>
68 /// <param name="ar"></param>
69 private static void InsertClientToCollection(IAsyncResult ar)
70 {
71 try
72 {
73 var socket = ar.AsyncState as Socket;
74 if (socket != null)
75 {
76 Clients.Add(socket.EndAccept(ar));
77 server.BeginAccept(InsertClientToCollection, server);
78 Logger.InfoFormat("新用户连接{0}", socket.LocalEndPoint);
79 }
80 }
81 catch (Exception ex)
82 {
83 Logger.Error(ex);
84 }
85 }
86
87 #endregion
88 #region
89 private void DoWork()
90 {
91 message = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "\0";
92 try
93 {
94 if (Clients.Count > 0)
95 {
96 var count = Clients.Count;
97 for (int i = 0; i < count; )
98 {
99 if (i < Clients.Count)
100 {
101 try
102 {
103 Clients[i].Send(Encoding.UTF8.GetBytes(message));
104 }
105 catch (Exception)
106 {
107 Clients.RemoveAt(i);
108 i--;
109 }
110 }
111 i++;
112 }
113 }
114 if (Clients.Count > 0)
115 Logger.InfoFormat("当前客户端总数:{0}", Clients.Count);
116 }
117 catch (Exception ex)
118 {
119 Logger.Error(ex);
120 }
121
122 }
123 #endregion
124
125 }
126 }
其中CreateSocketServer方法内有一句“AllowDomain ad = new AllowDomain();”,这个是解决flash安全沙箱跨域问题的,如果没有这句,flash读取socket数据的时候会报安全沙箱冲突。其实就是暴露Socket Server所在服务器的843端口给flash,以供握手(个人理解)。
代码如下:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Net.Sockets;
5 using System.Text;
6
7 namespace YourNameSpace
8 {
9 public class AllowDomain
10 {
11 private TcpListener Server;
12 private AsyncCallback callback;
13 private bool islisten = false;
14
15 public AllowDomain()
16 {
17 this.Server = new TcpListener(843);
18 this.Server.Start();
19 this.callback = new AsyncCallback(this.OnConnectionEvent);
20 this.islisten = true;
21 this.Server.BeginAcceptSocket(this.callback, null);
22 }
23
24 public void Close()
25 {
26 this.islisten = false;
27 this.Server.Stop();
28 }
29
30 private void OnConnectionEvent(IAsyncResult syn)
31 {
32 if (this.islisten)
33 {
34 SocketError error;
35 Socket conn = this.Server.EndAcceptSocket(syn);
36 //conn.Send(PolicyFile.Policys);
37
38 byte[] buffer = new byte[1024];
39 int len = conn.Receive(buffer, 0, 1024, SocketFlags.None, out error);
40 if (error == SocketError.Success)
41 {
42 string s = Encoding.Default.GetString(buffer, 0, len);
43 if (s == "<policy-file-request/>\0")
44 {
45 buffer = Encoding.Default.GetBytes("<cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"2013\"/></cross-domain-policy>\0");
46 conn.Send(buffer);
47 }
48 }
49 conn.Close();
50 if (this.islisten)
51 this.Server.BeginAcceptSocket(this.OnConnectionEvent, null);
52 }
53 }
54 }
55 }
下面是Flash代码,AS3:
1 package
2 {
3 import flash.display.Sprite;
4 import flash.events.Event;
5 import flash.events.ProgressEvent;
6 import flash.external.ExternalInterface;
7 import flash.net.Socket;
8 import flash.system.Security;
9 import flash.utils.ByteArray;
10
11 public class PushMessageLess extends Sprite
12 {
13 private var socket:Socket;
14 private var msg:String="";
15 protected function CreateSocket():void
16 {
17 Security.allowDomain("*");
18 Security.loadPolicyFile("xmlsocket://192.168.5.134:843");
19
20 // TODO Auto-generated method stub
21 socket=new Socket();
22 socket.connect("192.168.5.134",2013);
23 socket.addEventListener(ProgressEvent.SOCKET_DATA,receivedMessage);
24 }
25
26 private function socketConnected(event:Event):void{
27 }
28 private function receivedMessage(e:ProgressEvent):void{
29 var message:String="";
30 while(socket.bytesAvailable){
31 message+=socket.readMultiByte(socket.bytesAvailable,"utf8");
32 }
33 msg=message;
34 if(message.length>0){
35 callJs(message);
36 }
37 }
38
39 private function callJs(m:String):void{
40 ExternalInterface.call("callFlexFunction",m);
41 }
42 public function PushMessageLess()
43 {
44 if(stage){
45 CreateSocket();
46 }else
47 {
48 addEventListener(Event.ADDED_TO_STAGE,CreateSocket);
49 }
50 }
51 }
52 }
使用Flash Builder编译成swf文件PushMessageLess.swf(其实Less没有别的意思,是我原来用的是Flex项目,编译后把sdk中mx的部分东西编译进去了,比较大,有280k+,这个只有2k,可以接受)。
这个文件名会在后面代码中用到,拿来主义者切记:用的时候改名字。
下面是html代码:
1 <html>
2 <head>
3 <title>测试Flash socket兼容IE6,7,8</title>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5 <script src="swfobject.js"></script>
6 </head>
7
8 <body>
9 <script type="text/javascript">
10 function callFlexFunction(msg) {
11 if (msg != null) {
12 document.getElementById("msgContainer").innerHTML = msg;
13 }
14 }
15 var flashvars = false;
16 var params = {};
17 params.allowScriptAccess = "always";
18 var attributes = {};
19 //ID,也就是swf的ID,这个ID很重要,通过它调用flex的方法
20 attributes.id = "PushMessageLess";
21
22 swfobject.embedSWF("PushMessageLess.swf?"+Math.random(), "PushMessageLess", "0", "0", "9.0.0","",flashvars,params,attributes);
23 </script>
24 <div id="PushMessageLess" style="display: none;"></div>
25 <div id="msgContainer"></div>
26 </body>
27 </html>
所需做的只是把上面生成的swf文件放到html同级目录下(当然,不放同级目录也可以,注意下html中的路径就可以了)。
还有一个就是html页面中引用的一个swfObject.js,地址在这里
就这么多了,希望各位少走弯路。