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

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.59/5 (9投票s)

2008年4月22日

CPOL

5分钟阅读

viewsIcon

58346

downloadIcon

917

如何在 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。我们将为时钟创建一个椭圆,为时钟指针创建三个矩形。一旦收到数据,我们应该更改每个矩形的RenderTransformRotateTransform值(首先将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日:初始发布
© . All rights reserved.