Silverlight 4 和多个双工客户端






4.67/5 (5投票s)
能够将信息推送到单个浏览器页面或所有浏览器页面
- 下载源代码 - 2.91 MB
- 下载 Movie.txt - 348.71 KB (重命名为 Movie.rar - zip 文件太大无法上传)
引言
这个项目将演示使用双工机制的客户端-服务器方法,其中服务器将跟踪每个(客户端)浏览器并更新客户端,可以单独更新,也可以一起更新。但诀窍是使用推送(长轮询)架构,而不是客户端定期轮询服务器。
外观和感觉
在下面的图片(图 1)中,您可以看到应用程序的运行情况。在这个项目中,我将项目基于发电站场景,即中央办公室(服务器)将更新客户端发电站。
在应用程序(上面的屏幕截图)中,服务器创建了两个客户端发电站(浏览器),“Greenfield”和“Finnharps”。从服务器页面,我可以单击相应的选项卡(站点)并更改其功率瓦数,或者,如果我喜欢,单击“所有浏览器”复选框并一次更改所有站点(例如,向每个站点发送即将关闭的弹出窗口)。
项目 Visual Studio 结构
项目模块
服务器 (Silverlight) 模块
服务器实际上由两个 XAML 页面组成,“MainPage
”和“TabContents
”。当用户创建一个新站点时,将创建一个新选项卡来维护该站点 - 因此,一个新的控件将被插入到 Microsoft 选项卡控件中。同时,会打开一个新的浏览器(在 URL 中传递站点的名称以便在客户端浏览器中显示),并且该名称将用作密钥,用于将浏览器详细信息存储在一个集合中。因此,当用户位于特定选项卡中时 - 所选选项卡名称是用于从集合中获取浏览器详细信息并将新的(类 "ClientBrowserInfo
")详细信息(以 json 格式)推送到客户端的密钥。
客户端 (Silverlight) 模块
当浏览器最初打开时(从用户单击服务器上的“添加站点”按钮),它将在客户端和服务器之间创建一个双工轮询,并创建一个回调事件来处理来自服务器的通信。将 json 反序列化为(“ClientBrowserInfo
”)类,客户端可以从中得知要更改什么以及新值是什么。
服务器托管模块
除了包含将托管相应 Silverlight 项目的 HTML 页面之外,双工和浏览器服务也将包含在此模块中。每个浏览器的状态都将通过 static
类\方法和 static
集合来维护。双工服务将提供将数据推送到每个(或所有)客户端的机制。
第三方控件
对于刻度盘,我使用了 Telerik 的免费 Gauge 控件 (http://www.telerik.com/products/free-silverlight-controls.aspx)。
代码片段
下面的代码将创建并打开一个新的客户端,并将“TabControl
”插入到选项卡中。
/// <summary>
/// Handles the Click event of the btnSubmitNewPowerStation control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/>
/// instance containing the event data.</param>
private void btnSubmitNewPowerStation_Click(object sender, RoutedEventArgs e)
{
tabStations.Visibility = System.Windows.Visibility.Visible;
TabContents newStationControl = new TabContents();
TabItem newStationTabItem = new TabItem();
newStationTabItem.Header = this.txtNewStation.Text;
newStationTabItem.Content = newStationControl;
this.tabStations.Items.Add(newStationTabItem);
newStationTabItem.IsSelected = true;
// open (power station) browser
HtmlPopupWindowOptions options = new HtmlPopupWindowOptions();
options.Menubar = false;
options.Resizeable = false;
options.Status = false;
options.Toolbar = false;
options.Directories = false;
options.Scrollbars = false;
options.Location = false;
options.Height = 500;
options.Width = 500;
HtmlPage.PopupWindow(new Uri
("https://:62115/PowerStationClient.html?name=" +
this.txtNewStation.Text, UriKind.RelativeOrAbsolute), "_blank", options);
}
“TabControl
”中的一个事件是捕获滑块值的更改,然后将其发布到相应的(或所有)客户端。
/// <summary>
/// Handles the ValueChanged event of the sliderWattage control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The
/// <see cref="System.Windows.RoutedPropertyChangedEventArgs<System.Double>"/>
/// instance containing the event data.</param>
private void sliderWattage_ValueChanged(object sender,
RoutedPropertyChangedEventArgs<double> e)
{
TabItem selectedTab = (TabItem)this.Parent;
string clientKey = selectedTab.Header.ToString();
lblSliderValueSelected.Content = Convert.ToInt32(e.NewValue * 100).ToString();
if (this.imgStatus.Tag == null && e.NewValue == 0)
this.bthStatus.IsEnabled = false;
else if (this.imgStatus.Tag == null && e.NewValue > 0)
this.bthStatus.IsEnabled = true;
else if (this.imgStatus.Tag.ToString() == "off" && e.NewValue == 0)
this.bthStatus.IsEnabled = false;
else this.bthStatus.IsEnabled = true;
DuplexService.DuplexControllerClient client2 =
new DuplexService.DuplexControllerClient();
ClientBrowserInfo browserData = new ClientBrowserInfo();
browserData.BrowserKey = clientKey;
browserData.GaugeValue = int.Parse(lblSliderValueSelected.Content.ToString());
browserData.NotificationMessage = "";
browserData.PopupType = PopupStatus.NoPopup;
browserData.Operation = OperationType.Gauge;
if (!(bool)this.chkAllbrowsers.IsChecked)
client2.SendTriggerAuditDataAsync(SerializeObject(browserData),
browserData.BrowserKey);
else client2.SendTriggerAuditDataAsync(SerializeObject(browserData), "");
}
以下代码片段将根据密钥获取相应的浏览器并执行推送(它还确定是否需要更新所有浏览器)。
/// <summary>
/// Sends the trigger audit data.
/// </summary>
/// <param name="data">The data.</param>
public void SendTriggerAuditData(string data, string sessionId)
{
try
{
// loop through channels (browsers) and make a call
// to their callback method
if (BrowserHelper.GetCallbackChannels().Count() > 0)
{
lock (syncRoot)
{
if (sessionId.Equals(string.Empty))
{
IEnumerable
<IDBNotificationCallbackContract>
allChannels = BrowserHelper.
GetCallbackChannels();
allChannels.ToList().ForEach(c =>
c.SendNotificationToClients(data));
}
else
{
// send to one browser
IDBNotificationCallbackContract
channels = BrowserHelper.
GetCallbackChannel(sessionId);
channels.SendNotificationToClients(data);
}
}
}
}
catch (Exception) { /*log and display error message*/}
}
以下代码(来自客户端)将通知服务器它对回调感兴趣,并且它正在使用双工轮询注册自身。
/// <summary>
/// Initialises the page functionality.
/// </summary>
void InitialisePageFunctionality()
{
this.lblStationName.Content = string.Empty;
this.lblStationName.Content = "Station Name: " +
HtmlPage.Document.QueryString["name"].ToString();
// reference the notifications service and create the
// callback hook for database changes
client = new DBNotificationClient(new PollingDuplexHttpBinding(),
new EndpointAddress
("https://:62115/BrowserController/DBNotificationService.svc"));
client.SendNotificationToClientsReceived += (sender, e) =>
{
if (e.data != null)
UpdatedAuthorsReceived(e.data); // something to process
};
this.Subscribe(); // make the browser (instance) known to server
}
注意事项
- 我没有对发电站名称进行任何验证(这被用作集合键)。
- 我没有在浏览器关闭后完全清理(调用“
Unsubscribe
”方法)- 因此在static
集合中留下一些static
数据 - 这可能会在以后将数据推送到客户端时引起问题。 - 通过使用后台线程(来自客户端)和使用 Windows 2008 服务器(未在此项目中使用)可以实现高可扩展性。
历史
- 2011 年 2 月 3 日:首次发布