65.9K
CodeProject 正在变化。 阅读更多。
Home

通过 WebSockets 流式传输数据

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (8投票s)

2013 年 10 月 29 日

CPOL

3分钟阅读

viewsIcon

46955

downloadIcon

1189

在 C# 中实现一个简单的消息传递系统。

引言

有一个应用程序服务器连接着许多 Web 浏览器,我们想从每个浏览器读取一条消息,然后将其发送给所有人,就像聊天一样。

背景

在客户端,我们有一些 Ajax 来处理 WebSocket,还有 jQuery 来处理用户界面。

在服务器端,我们使用一个非常简单的 Web 应用程序,它构建在 NetFluid 应用程序服务器框架之上。

Using the Code

代码非常简单,由三个文件组成

  • Hoster.cs: 初始化应用程序服务器并自托管 Web 应用程序
  • SocketManager.cs: 包含服务器端使用的所有 C# 代码
  • ./UI/index.html: 包含所有用户界面和 JavaScript

设置

Hoster.cs 是我们 Web 应用程序的入口点。它初始化应用程序服务器,设置基本配置,并加载当前程序集以自托管我们的 Web 应用程序。
并非必须以编程方式设置这些值,您也可以从配置中进行设置。

static void Main() 
{ // Setup the application server, hosting the current assembly 
Engine.Load(Assembly.GetCallingAssembly()); // Enable console log of received request 
Engine.DevMode = true; // Set all files inside "Public" 
                       // as public and downloadable by the client (styles, js..) 
Engine.AddPublicFolder("/","./Public",true); // Add an HTTP interface 
                                                                 // on our application server 
Engine.Interfaces.AddInterface("127.0.0.1", 8080); // Makes the web application runs 
Engine.Start(); //Prevent application from closing ! 
Console.ReadLine(); 
} 

服务器端

SocketManager.cs 中,我们得到了 Web 应用程序的核心:WebSocket 服务器。
为了简单起见,我们将使用基于行的通信协议,客户端发送一行,服务器读取一行,然后将此行发送给所有客户端。

// On "/channel" uri the application server will call this method
[Route("/channel")]
public void Channel()
{
	// Add the current client to the clients list
	clients.Add(Context);

	while (true)
	{
		try
		{
			// Read the message
			var msg = Context.Reader.ReadLine();

			// Send to all
			foreach (var client in clients)
			{
				client.Writer.WriteLine(msg);
				client.Writer.Flush();
			}
		}
		catch (Exception)
		{
			//Client goes timeout
			break;
		}
	}

	//The client has close the connection, so we remove it from the list
	var trash = Context;
	clients.TryTake(out trash);
}

注意SocketManager 继承了 NetFluid.FluidPage 类型,这是托管 NetFluid 可调用方法所必需的。

添加一些培根

在本文的第一个版本中,服务器重新发送 string,客户端将其附加到表中。
这是一种非常简单的方式来跟踪数据,但也是一种非常简单的方式让您的浏览器在高流量下崩溃,所以我决定在服务器上保留最后收到的六条消息,并将服务器的整个实际状态发送给客户端,它们将用新的状态替换当前的 HTML 状态,而不是附加它。

[Route("/channel")]
public void Channel()
{
	//Save the current client into the recipients list
	clients.Add(Context);

	while (true)
	{
		try
		{
			// Take the message from the client
			messages.Enqueue(Context.Reader.ReadLine());

			// Store just the last six messages of clients
			if (messages.Count>6)
			{
				string trashMessage;
				messages.TryDequeue(out trashMessage);
			}

			foreach (var client in clients)
			{
				//join message build the message table for clients
				client.Writer.WriteLine(string.Join("",messages));
				client.Writer.Flush();
			}
		}
		catch (Exception)
		{
			//Client goes timeout
			break;
		}
	}

	//The client has close the connection, so we remove it from the list
	var trash = Context;
	clients.TryTake(out trash);
}

客户端

如果服务器端代码很简单,那么客户端代码就更简单了。
它所要做的就是获取用户 string,打开一个 WebSocket,发送 string,如果有任何传入消息,则从服务器获取复合数据并显示它。

HTML

<div class="container">
	<div>
		<h1>WebSocket streaming data</h1>
		<div>
			<p>
				<!– To keep trace of who is writing what –>
				<span>Nickname</span>
				<input type="text" class="form-control" 
				id="nick" placeholder="Nickname">
			</p>
			<p>
				<!– The message to be sent –>
				<span>Text message</span>
				<input type="text" class="form-control" 
				id="msg" placeholder="Text message">
			</p>
			
			<!– Send the message and reset the input –>
			<button id="send" type="submit" 
			class="btn btn-default">Send</button>
			
			<!– Start a brute force message spamming –>
			<button id="spam" type="submit" 
			class="btn btn-default">Let's spam !</button>
		</div>
		<hr />
		<!– Incoming composite will be displayed here –>
		<div id="incoming" class="row"></div>

		<!– Websocket status debugging –>
		<h3>Socket status</h3>
		<div id="status"></div>
	</div>
</div>

JavaScript 绑定

要设置 WebSocket,您只需要实例化一个新的 WebSocket 类(如果您的浏览器支持它)。

// Address of our websocket server 
var wsUri = "ws://:8080/channel";

function init()
{
	var sock = new WebSocket(wsUri);
	sock.onopen = function (evt) { $("#status").html("CONNECTING"); };
	sock.onclose = function (evt) { $("#status").html("CLOSED"); };
	sock.onerror = function (evt) { $("#status").html("ERROR"); };
	sock.onmessage = function (evt)
	{
		// HERE IT TAKE THE INCOMING COMPOSITE AND DISPLAY IT
		$("#incoming").html(evt.data);
	};
	$("#status").html("CONNECTED");
	return sock;
}

var websocket = init();

如果您单击“发送消息”按钮或在聚焦的输入文本框上按 Enter 键,客户端将发送消息。

//Send the message clicking on the button
$("#send").click(function (event)
{
	//Prevent to send empty messages
	if ($("#msg").val() == "")
		return;

	//prevent submitting or other unwanted js handlers
	event.preventDefault();

	//send the message in input textbox
	sendMessage($("#msg").val());

	//reset the input textbox
	$("#msg").val("");
});

//Send the message pressing Enter
$("#msg").keypress(function( event )
{
	//Prevent to send empty messages
	if (event.which == 13 && $("#msg").val()!="")
	{
		//prevent submitting or other unwanted js handlers
		event.preventDefault();

		//send the message in input textbox
		sendMessage($("#msg").val());

		//reset the input textbox
		$("#msg").val("");
	}
});

有趣的事情:为了简单的测试和调试,客户端将每条消息封装到一个随机颜色的框中,其中包含发送者的昵称和时间戳,“让我们垃圾邮件”按钮将启动一个不可阻挡的重复事件来测试您的 Web 应用程序的容量,该事件将每 30 毫秒发送给定的消息。
彩色框的样式大致从 Twitter Boostrap 警报导入。

// Random color to distinguish incoming messages
// names used are from Twitter Boostrap Alerts css class
var colors = new Array();
colors[0] = "success";
colors[1] = "info";
colors[2] = "warning";
colors[3] = "danger";

function randomColor()
{
	return colors[Math.floor(Math.random()*3)];
}

//Formats the message inside a div and sent it to the server
function sendMessage(msg)
{
	var div = $("<div class=\"col-md-4 alert alert-" + 
	randomColor() + "\"></div>");
	div.append($("<strong>" + $("#nick").val()+ 
	"</strong>"));
	div.append($("<h3>" + msg + "</h3>"));
	div.append($("<small>" + (new Date().toUTCString()) + 
	"</small>"));

	var fake = $("<div></div>");
	fake.append(div);
	var outer = fake.html();

	if (websocket.readyState == WebSocket.CLOSING || 
			websocket.readyState == WebSocket.CLOSED)
		websocket = init();

	websocket.send(outer+"\r\n");
}

//Spam messages to the server every 30 milliseconds
$("#spam").click(function (event)
{
	event.preventDefault();
	var msg = prompt("Message", "");

	if (msg != null)
	{
		setInterval(function (){sendMessage(msg);}, 30);
	}
});

关注点

我不对垃圾邮件模式造成的任何癫痫发作负责 :D

历史

  • 第一个公开版本
© . All rights reserved.