Silverlight 和 WPF 中的网络通信,或者如何让它们相互通信






4.59/5 (9投票s)
如何在 Silverlight 应用程序中使用原始套接字,以及如何让 Silverlight 与 Windows Forms 和 WPF 通信。
引言
您可能知道如何在WinForms中使用原始套接字。在WPF中也差不多,但在Silverlight中却非常不同(且受限)。今天,我们将创建Silverlight、WPF和WinForm中的示例应用程序,通过TCP发送和接收更新,并通过UDP(单播和多播)广播更新。那么,让我们开始吧。

开始吧
首先,我们将创建一个WinForms服务器,它应该分发更新。它知道当前时间,并通过UDP广播时间消息。它还有一个TCP服务器,将更新分发给所有客户端。
首先是UDP。我们应该先创建工作套接字。它使用所有IP地址通过给定端口广播更改。为了使套接字支持多播,我们应该设置适当的套接字选项。让我们看看代码。
lock (this)
{
if (m_mainSocket == null)
{
m_mainSocket = new Socket(AddressFamily.InterNetwork,
SocketType.Dgram, ProtocolType.Udp);
IPEndPoint ipLocal = new IPEndPoint(IPAddress.Any,port);
m_mainSocket.SetSocketOption(SocketOptionLevel.Socket,
SocketOptionName.ReuseAddress, 1);
m_mainSocket.Bind(ipLocal);
}
IPAddress ip = IPAddress.Parse(castGroupIp);
EPCast = new IPEndPoint(ip, port);
m_mainSocket.SetSocketOption(SocketOptionLevel.IP,
SocketOptionName.AddMembership, new MulticastOption(ip, IPAddress.Any));
}
现在,TCP部分。它非常相似(对于服务器,我们不需要特定的IP地址进行绑定),但是,我们不应该设置多播选项,也不应该在连接建立后立即开始监听。
lock (this)
{
if (m_mainSocket == null)
{
m_mainSocket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
IPEndPoint ipLocal = new IPEndPoint(IPAddress.Any, port);
m_mainSocket.Bind(ipLocal);
m_mainSocket.Listen(Backlog);
m_mainSocket.BeginAccept(new AsyncCallback(acceptCallback), null);
}
}
为了通过UDP发送数据,我们所要做的就是将其写入当前套接字。
socket.SendTo(data, EPCast);
在TCP世界中,我们首先需要知道要发送给谁,因此我们必须枚举所有传入的客户端以保存对其套接字的引用。
Socket workerSocket = m_mainSocket.EndAccept(asyn);
m_workerSocketList.Add(workerSocket.GetHashCode(), workerSocket);
m_mainSocket.BeginAccept(new AsyncCallback(acceptCallback), null);
然后,当我们知道要发送给谁时,我们所要做的就是发送
socket.Send(data);
现在,当我们知道如何发送时,我们应该学习如何接收网络消息。在TCP或UDP中都是完全相同的。首先检查是否有数据要接收,然后接收它。
if (m_pfnCallBack == null)
m_pfnCallBack = new AsyncCallback(dataReceivedCallback);
SocketPacket theSocPkt = new SocketPacket(socket, bufferSize);
socket.BeginReceive(theSocPkt.dataBuffer, 0,
theSocPkt.dataBuffer.Length, m_pfnCallBack,
theSocPkt);
我们已经完成了WinForms和WPF网络部分。所以我们可以开始图形部分。由于WinForms中图形不多,我们将重点关注WPF部分。
我们将使用ContentControl
来呈现内容,并使用我们的消息的datatemplate
。我们将为时钟创建一个椭圆,为时钟指针创建三个矩形。一旦收到数据,我们应该更改每个矩形的RenderTransform
的RotateTransform
值(首先将TransformOrigin
设置为时钟的中心)。将它们绑定在一起。
<Ellipse Width="250" Height="250" StrokeThickness="2" Stroke="Black"/>
<Rectangle Height="100" Width="20" RadiusX="10" RadiusY="10"
Fill="Black" RenderTransformOrigin="0.5,1">
<Rectangle.RenderTransform>
<TransformGroup>
<RotateTransform/>
<TranslateTransform Y="25" X="115"/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Height="125" Width="10" RadiusX="5" RadiusY="5"
Fill="Black" RenderTransformOrigin="0.5,1">
<Rectangle.RenderTransform>
<TransformGroup>
<RotateTransform/>
<TranslateTransform Y="0" X="120"/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Height="125" Width="4" RadiusX="2" RadiusY="2"
Fill="Black" RenderTransformOrigin="0.5,1">
<Rectangle.RenderTransform>
<TransformGroup>
<RotateTransform/>
<TranslateTransform Y="0" X="123"/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
我们应该如何将接收到的值转换为转动时钟指针的角度?我们这里只能使用一个转换器,并根据以下公式绑定到消息:
HourAngle = (Hours * 30)+(12*Minutes/60);
MinuteAngle = Minutes * 6;
(it's 360/60)
SecondAngle = Seconds * 6;
我们来运行它。什么都没发生。为什么?原因在于,即使对象的每个属性都发生了变化,它也不会触发绑定,因为整个对象没有发生变化。为了解决这个问题,我们应该绑定到每个属性。在小时的情况下,同时绑定到Hours和Minutes属性。但是如何让我的转换器既是单值转换器又是多值转换器呢?很简单——这一切都与接口有关。因此,以下转换器将完成这项工作。
public class TimeToAngleConverter : IValueConverter, IMultiValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
return (double)value * 6;
}
public object Convert(object[] values, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
return ((double)values[0] * 30) + (12 * (double)values[1] / 60);
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{ throw new
NotImplementedException(); }
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, System.Globalization.CultureInfo culture)
{ throw new
NotImplementedException(); }
}
现在是绑定表达式
<Rectangle Height="100" Width="20" RadiusX="10" RadiusY="10" Fill="Black"
RenderTransformOrigin="0.5,1">
<Rectangle.RenderTransform>
<TransformGroup>
<RotateTransform> <RotateTransform.Angle>
<MultiBinding Converter="{StaticResource timeToAngle}">
<Binding Path="Hour"/>
<Binding Path="Minute"/>
</MultiBinding>
</RotateTransform.Angle>
</RotateTransform>
<TranslateTransform Y="25" X="115"/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Height="125" Width="10" RadiusX="5" RadiusY="5"
Fill="Black" RenderTransformOrigin="0.5,1">
<Rectangle.RenderTransform>
<TransformGroup>
<RotateTransform Angle="{Binding Path=Minute, Converter={StaticResource timeToAngle}}"/>
<TranslateTransform Y="0" X="120"/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Height="125" Width="4" RadiusX="2" RadiusY="2"
Fill="Black" RenderTransformOrigin="0.5,1">
<Rectangle.RenderTransform>
<TransformGroup>
<RotateTransform Angle="{Binding Path=Second, Converter={StaticResource timeToAngle}}"/>
<TranslateTransform Y="0" X="123"/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
WPF中的图形部分我们已经完成了。让我们在Silverlight中重新开始。我们无法完成WPF中至少一半的工作。Silverlight不支持MultiBinding,没有隐式模板,并且Transform
类不支持Binding。我们该怎么办?让我们回忆一下美好的旧世界。
设置不使用模板的控件,然后查找资源并保存引用,并明确设置值(在订阅OnPropertyChanged
事件之后,当然)。换句话说,亲手进行绑定。
void onLoaded(object s, RoutedEventArgs e)
{
mt = ((RotateTransform)FindName("mTransform"));
ht = ((RotateTransform)FindName("hTransform"));
st = ((RotateTransform)FindName("sTransform"));
((NetTimeProvider)Resources["timeProvider"]).PropertyChanged +=
new PropertyChangedEventHandler(Page_PropertyChanged);
}
RotateTransform mt, ht, st;
void Page_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//Binding to Transform not supported (yet??);
NetTimeProvider s = sender as NetTimeProvider;
Dispatcher.BeginInvoke((SendOrPostCallback) delegate (object o)
{
NetTimeProvider ntp = o as NetTimeProvider;
if (e.PropertyName == "Hour")
{
ht.Angle = (ntp.Hour * 30) + (12 * ntp.Minute / 60); ;
}
else if (e.PropertyName == "Minute")
{
mt.Angle = ntp.Minute * 6;
}
else if (e.PropertyName == "Second")
{
st.Angle = ntp.Second * 6;
}
}, s);
}
现在,当我们的Silverlight控件有了布局后,我们应该连接到分发网络服务器。重用用于Winforms和WPF的管理器吗?不行。Silverlight是.NET Framework的一个子集,并且不依赖于它,因此我们必须为Silverlight编写一个新的网络提供程序。Silverlight不支持UDP,因此我们将使用TCP网络。让我们看看我们有什么。WebRequest
/WebResponse HttpWebRequest
/HttpWebResponse
——要使用它——不行。我们的服务器既不是HTTP服务器也不是Web服务器。我们应该在Silverlight中使用原始套接字。Socket
类存在于Silverlight的System.Net DLL中,但它非常受限。让我们建立连接。首先,我们应该知道要连接的IP。
由于安全限制,我们无法在 Silverlight 中进行 DNS 查询。另一方面,我们不想将其限制为硬编码的名称或 IP 地址。在 Silverlight 的应用程序类中,我们有一个非常方便的属性,名为 DnsSafeHost
(Application.Current.Host.Source.DnsSafeHost
)。所以让我们使用它。
端口呢?我可以使用TCP套接字连接到任何我想要的端口吗?不行。这是另一个安全限制。Silverlight唯一可用的端口范围是4502-5432(只有30个端口)。因此,在这些限制下,我们将按如下方式创建连接:
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream,
ProtocolType.Tcp);
DnsEndPoint ep = new DnsEndPoint
(Application.Current.Host.Source.DnsSafeHost, 4502);
SocketAsyncEventArgs args = new SocketAsyncEventArgs()
{
RemoteEndPoint = ep
};
args.Completed += onConnected;
socket.ConnectAsync(args);
现在我们应该检查连接是否成功建立。我们唯一能做的就是在onConnected
处理程序中。在这里,我们还将重用SocketAsyncArgs
的完成事件来执行读取序列。在处理程序结束时,我们将尝试从内联套接字读取一些内容。
void onConnected(object sender, SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
e.Completed -= onConnected;
e.Completed += onRead;
Message = "Connected";
}
readMoreData(e);
}
如果您还记得,在常规框架中,我们可以等待套接字。在 Silverlight 中我们也可以做到。
void readMoreData(SocketAsyncEventArgs e)
{
e.SetBuffer(buffer, bytesRead, (buffer.Length - bytesRead));
if (!socket.ReceiveAsync(e))
{
onRead(socket, e);
}
else
{
Message = "Disconnected";
}
}
因此,如果一切正常并且套接字中有数据,让我们读取它。其中应该做一些防错处理。首先,我们应该检查是否收到了所有消息。我们知道消息大小是20字节(5个整数 - 我们检查前四个)。然后我们应该检查收到的消息是否是我们的消息。因此,在消息头中,我们将检查魔术数字。如果一切正常,我们将解析它并填充我们类的所有属性。
void onRead(object sender, SocketAsyncEventArgs e)
{
if (e.BytesTransferred > 0)
{
bytesRead += e.BytesTransferred;
if (bytesRead == 20 && BitConverter.ToUInt32(buffer, 0) == Magic)
{
Hour = BitConverter.ToInt32(buffer, 4);
OnPropertyChanged("Hour");
Minute = BitConverter.ToInt32(buffer, 8);
OnPropertyChanged("Minute");
Second = BitConverter.ToInt32(buffer, 12);
OnPropertyChanged("Second");
bytesRead = 0;
}
readMoreData(e);
}
}
如果一切顺利,我们将返回等待下一条消息的到来。
下一步?
接下来,我们可以尝试将 Silverlight 1.0 应用程序连接到此示例。您会问,怎么做?请等待下一篇文章或访问我的博客。
历史
- 2008年4月22日:初始发布