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实例集合
- [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);
- }
- }
[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)
- 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();
- }
- }
- }
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/
- <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>
<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网络环境下,双方的通信是否能穿透?还待研究...
浙公网安备 33010602011771号