通过 WebSockets 流式传输数据
在 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
历史
- 第一个公开版本