WCF WebSocketsService (HTML5 WebSocket)

在Web应用中,HTTP协议决定了客户端和服务端连接是“短连接”,即客户端Request,服务端Response,连接断开。要想实现客户端和服务端实时通信,只能通过客户端轮询来实现。“服务端推送数据”也并不是字面上意思上的“直接推”,其实还是客户端“自己取”。在HTML5标准中新的Websocket协议可以在客户端和服务器之间无限制的连接,WebSocket 不仅更快,也更廉价,更简单。利用WebSocket,可以取代之前的ajax客户端轮询,真正实现从服务端到客户端的推送。(IE9还不支持WebSocket,下面使用的是Chrome浏览器进行的测试:Chrome 14.0.835.186 m)

2012/2/14 更新:

Chrome 14 使用的是:Sec-WebSocket-Version: 7     (成功)
Chrome 17 使用的是:Sec-WebSocket-Version: 13   (失败)
说明目前的 WebSocketsService 所支持的 WebSocket 版本是 13 以下的。


.NET 方面也支持WebSocket,不过还没有提供正式的下载,但也可以通过 HTML5Lib 下载的demo里,获取到:Microsoft.ServiceModel.WebSockets.dll

1. 创建 WebSocket 服务:

这里继承了 WebSocketsService,并且Service Instance 使用了 PerSession,这样每个客户端连接对应着一个实例。(WebSocketsService 使用 Singleton 时会抛异常)
下面的代码主要实现:
(1) 客户端连接时,将Service实例保存到 List<WebSocketsService> 中
(2) 客户端断开连接时,将Service实例移除 List<WebSocketsService>
(3) 返回活动的Service实例集合

 

  1. [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession,  
  2.                      ConcurrencyMode = ConcurrencyMode.Multiple)]  
  3.     public class NotificationService : WebSocketsService  
  4.     {  
  5.         public static event Action<WebSocketsService, string> MessageReceieved;  
  6.         private static List<NotificationService> _serviceContainer = new List<NotificationService>();  
  7.         public static List<NotificationService> ActivityServices  
  8.         {  
  9.             get   
  10.             {  
  11.                 return _serviceContainer;  
  12.             }  
  13.         }  
  14.   
  15.         public override void OnMessage(string value)  
  16.         {  
  17.             base.OnMessage(value);  
  18.             if (MessageReceieved != null)  
  19.                 MessageReceieved(this, value);  
  20.             Console.WriteLine(value);  
  21.         }  
  22.   
  23.         public override void OnOpen()  
  24.         {  
  25.             base.OnOpen();  
  26.             base.SendMessage("Welcome");  
  27.             Console.WriteLine("Client Opened");  
  28.             _serviceContainer.Add(this);  
  29.         }  
  30.   
  31.         protected override void OnError(object sender, EventArgs e)  
  32.         {  
  33.             base.OnError(sender, e);  
  34.             Console.WriteLine("Client Error");  
  35.         }  
  36.   
  37.         protected override void OnClose(object sender, EventArgs e)  
  38.         {  
  39.             base.OnClose(sender, e);  
  40.             Console.WriteLine("Client Closed");  
  41.             _serviceContainer.Remove(this);  
  42.         }  
  43.     }   
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession,
                     ConcurrencyMode = ConcurrencyMode.Multiple)]
    public class NotificationService : WebSocketsService
    {
        public static event Action<WebSocketsService, string> MessageReceieved;
        private static List<NotificationService> _serviceContainer = new List<NotificationService>();
        public static List<NotificationService> ActivityServices
        {
            get 
            {
                return _serviceContainer;
            }
        }

        public override void OnMessage(string value)
        {
            base.OnMessage(value);
            if (MessageReceieved != null)
                MessageReceieved(this, value);
            Console.WriteLine(value);
        }

        public override void OnOpen()
        {
            base.OnOpen();
            base.SendMessage("Welcome");
            Console.WriteLine("Client Opened");
            _serviceContainer.Add(this);
        }

        protected override void OnError(object sender, EventArgs e)
        {
            base.OnError(sender, e);
            Console.WriteLine("Client Error");
        }

        protected override void OnClose(object sender, EventArgs e)
        {
            base.OnClose(sender, e);
            Console.WriteLine("Client Closed");
            _serviceContainer.Remove(this);
        }
    } 

再创建一个Host:

 

为了演示服务端推送的效果,加上了一个Task,每两秒发送一条消息给客户端。
与 wsHttpBinding 和 netTcpBinding 的 WCF ServiceHost 不同的是:使用了 WebSocketsHost,地址的Schema是“ws”或者是"wss"(基于SSL)

  1. class Program  
  2. {  
  3.     static void Main(string[] args)  
  4.     {  
  5.         var baseAddress = new Uri("ws://localhost:20001/NotificationService");  
  6.         using (var host = new WebSocketsHost<NotificationService>(baseAddress))  
  7.         {  
  8.             host.AddWebSocketsEndpoint();  
  9.             host.Open();  
  10.             Console.WriteLine(baseAddress.ToString() + " Opened ...");  
  11.   
  12.             var task = new Task(() => {  
  13.                 while (true)  
  14.                 {  
  15.                     System.Threading.Thread.Sleep(2000);  
  16.                     try  
  17.                     {  
  18.                         Console.WriteLine("Service Instance Count:" + NotificationService.ActivityServices.Count);  
  19.                         NotificationService.ActivityServices.  
  20.                             ForEach(s => s.SendMessage("Service Message: " + DateTime.Now.ToLongTimeString()));  
  21.                     }  
  22.                     catch(Exception ex)  
  23.                     {  
  24.                         Console.WriteLine("Error: " + ex.Message);  
  25.                     }  
  26.                 }  
  27.             });  
  28.             task.Start();  
  29.   
  30.             Console.Read();  
  31.         }  
  32.     }  
  33. }  
 class Program
 {
     static void Main(string[] args)
     {
         var baseAddress = new Uri("ws://localhost:20001/NotificationService");
         using (var host = new WebSocketsHost<NotificationService>(baseAddress))
         {
             host.AddWebSocketsEndpoint();
             host.Open();
             Console.WriteLine(baseAddress.ToString() + " Opened ...");

             var task = new Task(() => {
                 while (true)
                 {
                     System.Threading.Thread.Sleep(2000);
                     try
                     {
                         Console.WriteLine("Service Instance Count:" + NotificationService.ActivityServices.Count);
                         NotificationService.ActivityServices.
                             ForEach(s => s.SendMessage("Service Message: " + DateTime.Now.ToLongTimeString()));
                     }
                     catch(Exception ex)
                     {
                         Console.WriteLine("Error: " + ex.Message);
                     }
                 }
             });
             task.Start();

             Console.Read();
         }
     }
 }


2. HTML 客户端:
HTML5里直接内置了WebSocket对象,操作起来非常简单。详细的内容,参见:http://dev.w3.org/html5/websockets/

  1. <html>  
  2. <head>  
  3. <title>WebSockets Client</title>  
  4. <style>  
  5. body {    
  6.     font-family:Arial, Helvetica, sans-serif;    
  7. }    
  8. #container{    
  9.     border:5px solid grey;    
  10.     width:800px;  
  11.     margin:0 auto;  
  12.     padding:10px;  
  13. }    
  14. #chatLog{    
  15.     padding:5px;  
  16.     border:1px solid black;  
  17. }    
  18. #chatLog p {  
  19.     margin:0;  
  20. }    
  21. .event {    
  22.     color:#999;    
  23. }    
  24. .warning{    
  25.     font-weight:bold;    
  26.     color:#CCC;    
  27. }   
  28. </style>  
  29. <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>  
  30. <script>  
  31. $(document).ready(function() {    
  32.     
  33.   if(!("WebSocket" in window)){    
  34.       $('#chatLog, input, button, #examples').fadeOut("fast");    
  35.       $('<p>Oh no, you need a browser that supports WebSockets. How about <href="http://www.google.com/chrome">Google Chrome</a>?</p>').appendTo('#container');    
  36.   }else{    
  37.       //The user has WebSockets    
  38.       connect();    
  39.     
  40.       function connect(){    
  41.           var socket;    
  42.           var host = "ws://localhost:20001/NotificationService";    
  43.           //var host = "ws://localhost:10081/";    
  44.     
  45.           try{    
  46.               var socket = new WebSocket(host);  
  47.               message('<class="event">Socket Status: '+socket.readyState);    
  48.     
  49.               socket.onopen = function(){    
  50.                  message('<class="event">Socket Status: '+socket.readyState+' (open)');  
  51.               }    
  52.     
  53.               socket.onmessage = function(msg){    
  54.                  message('<class="message">Received: '+msg.data);    
  55.               }    
  56.     
  57.               socket.onclose = function(){    
  58.                 message('<class="event">Socket Status: '+socket.readyState+' (closed)');    
  59.               }             
  60.     
  61.           } catch(exception){    
  62.              message('<p>Error'+exception);  
  63.           }    
  64.     
  65.           function send(){    
  66.               var text = $('#text').val();  
  67.               if(text==""){    
  68.                   message('<class="warning">Please enter a message');    
  69.                   return;  
  70.               }  
  71.               try{   
  72.                   socket.send(text.toString());  
  73.                   message('<class="event">Sent: '+text);    
  74.               } catch(exception){    
  75.                  message('<class="warning">');    
  76.               }    
  77.               $('#text').val("");    
  78.           }    
  79.     
  80.           function message(msg){    
  81.             $('#chatLog').append(msg+'</p>');    
  82.           }    
  83.     
  84.           $('#text').keypress(function(event) {    
  85.               if (event.keyCode == '13') {    
  86.                 send();    
  87.               }  
  88.           });  
  89.     
  90.           $('#disconnect').click(function(){    
  91.              socket.close();    
  92.           });    
  93.       }//End connect    
  94.   }//End else    
  95. });    
  96. </script>  
  97. </head>  
  98. <body>  
  99. <div id="wrapper">  
  100.     <div id="container">  
  101.         <h1>WebSockets Test</h1>  
  102.         <div id="chatLog">  
  103.         </div><!-- #chatLog -->   
  104.         <input id="text" type="text" />  
  105.         <button id="disconnect">Disconnect</button>  
  106.     </div><!-- #container -->  
  107. </div>  
  108. </body>  
  109. </html>  
<html>
<head>
<title>WebSockets Client</title>
<style>
body {  
    font-family:Arial, Helvetica, sans-serif;  
}  
#container{  
    border:5px solid grey;  
    width:800px;
    margin:0 auto;
    padding:10px;
}  
#chatLog{  
    padding:5px;
    border:1px solid black;
}  
#chatLog p {
    margin:0;
}  
.event {  
    color:#999;  
}  
.warning{  
    font-weight:bold;  
    color:#CCC;  
} 
</style>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script>
$(document).ready(function() {  
  
  if(!("WebSocket" in window)){  
      $('#chatLog, input, button, #examples').fadeOut("fast");  
      $('<p>Oh no, you need a browser that supports WebSockets. How about <a href="http://www.google.com/chrome">Google Chrome</a>?</p>').appendTo('#container');  
  }else{  
      //The user has WebSockets  
      connect();  
  
      function connect(){  
          var socket;  
          var host = "ws://localhost:20001/NotificationService";  
          //var host = "ws://localhost:10081/";  
  
          try{  
              var socket = new WebSocket(host);
              message('<p class="event">Socket Status: '+socket.readyState);  
  
              socket.onopen = function(){  
                 message('<p class="event">Socket Status: '+socket.readyState+' (open)');
              }  
  
              socket.onmessage = function(msg){  
                 message('<p class="message">Received: '+msg.data);  
              }  
  
              socket.onclose = function(){  
                message('<p class="event">Socket Status: '+socket.readyState+' (closed)');  
              }           
  
          } catch(exception){  
             message('<p>Error'+exception);
          }  
  
          function send(){  
              var text = $('#text').val();
              if(text==""){  
                  message('<p class="warning">Please enter a message');  
                  return;
              }
              try{ 
                  socket.send(text.toString());
                  message('<p class="event">Sent: '+text);  
              } catch(exception){  
                 message('<p class="warning">');  
              }  
              $('#text').val("");  
          }  
  
          function message(msg){  
            $('#chatLog').append(msg+'</p>');  
          }  
  
          $('#text').keypress(function(event) {  
              if (event.keyCode == '13') {  
                send();  
              }
          });
  
          $('#disconnect').click(function(){  
             socket.close();  
          });  
      }//End connect  
  }//End else  
});  
</script>
</head>
<body>
<div id="wrapper">
    <div id="container">
        <h1>WebSockets Test</h1>
        <div id="chatLog">
        </div><!-- #chatLog --> 
        <input id="text" type="text" />
        <button id="disconnect">Disconnect</button>
    </div><!-- #container -->
</div>
</body>
</html>


3. 运行效果:
服务端:

客户端:


4. 注意点:
(1) WebSocket 如果在5分钟没有消息通信,则自动断开连接
(2) WebSocket 第一次通过HTTP进行HandShake,接下来通过Tcp协议直接通信
(3) 在NAT网络环境下,双方的通信是否能穿透?还待研究...

posted on 2015-01-04 23:48  荷城码畜  阅读(206)  评论(0)    收藏  举报

导航