今天先说说DatagramSocket类,别看这名字好像有些陌生,其实,说白了,这家伙只是换了个“马甲”罢了,本质上说就是UDP传输,最适合做就是传输一些简单的文本信息,所以,弄个聊天程序相当合适。
由于Windows“板砖”应用一般是一个应用窗口占满整个屏幕,有时候可能会挂到屏幕的一边,为了说明DatagramSocket就是UDP协议的socket,我们一端使用Windows Store应用程序,而另一端使用WPF来开发,看看这两者之间的通信就可以说明。
为了在本机测试操作更方便,在Windows Store应用端可以考虑用模拟器来运行,模拟器是根据本地机器当前的系统来模拟的,所以,IP地址可以使用127.0.0.1。
以下是应用运行的效果图。
为了不产生乱码,通信双方均用UTF-8编码,这样它们才有默契。
第一项,实现Windows Store端。
主页布局参考下面XAML,我不再解释。
x:Class= "WStoreSocketApp.MainPage"
xmlns= "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x= "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local= "using:WStoreSocketApp"
xmlns:d= "http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc= "http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable= "d">
< Page.Resources>
< Style x:Key= "tbfield" TargetType= "TextBlock">
< Setter Property= "FontSize" Value= "20" />
< Setter Property= "VerticalAlignment" Value= "Center" />
< Setter Property= "Margin" Value= "2,0,6,0" />
< /Style>
< /Page.Resources>
< Grid Background= "{StaticResource ApplicationPageBackgroundThemeBrush}">
< StackPanel>
<!-- 用于显示接收到的消息 -->
< ListBox x:Name= "lbRecMessages" Height= "300" />
< Grid Margin= "0,18,0,15">
< Grid.ColumnDefinitions>
< ColumnDefinition Width= "auto" />
< ColumnDefinition/>
< ColumnDefinition Width= "auto" />
< ColumnDefinition/>
< /Grid.ColumnDefinitions>
< TextBlock Grid.Column= "0" Style= "{StaticResource tbfield}" Text= "远程主机:" />
< TextBox x:Name= "txtRemote" Grid.Column= "1" Width= "260" HorizontalAlignment= "Left" Text= "127.0.0.1" />
< TextBlock Grid.Column= "2" Style= "{StaticResource tbfield}" Text= "远程端口:" />
< TextBox x:Name= "txtPort" Grid.Column= "3" Width= "90" HorizontalAlignment= "Left" />
< /Grid>
<!-- 用于输入要发送的消息 -->
< TextBox x:Name= "txtMessageInput" Margin= "2,13,2,16" Height= "180" />
< StackPanel Margin= "3,10,0,15" Orientation= "Horizontal">
< Button Content= "发送消息" Margin= "0,1,12,2" Click= "onSend" />
< TextBlock Margin= "8,2,0,2" x:Name= "tbMessage" FontSize= "22" />
< /StackPanel>
< /StackPanel>
< /Grid>
< /Page>
隐藏代码如下:【C#】
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows.Storage.Streams;
using Windows.Networking;
using Windows.Networking.Sockets;
namespace WStoreSocketApp
{
/// <summary>
/// 可用于自身或导航至 Frame 内部的空白页。
/// </summary>
public sealed partial class MainPage : Page
{
DatagramSocket mySocket = null;
const string LOCAL_PORT = "9700"; //本地端口
public MainPage()
{
this.InitializeComponent();
}
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
if (mySocket == null)
{
mySocket = new DatagramSocket();
mySocket.MessageReceived += mySocket_MessageReceived;
await mySocket.BindServiceNameAsync(LOCAL_PORT);
}
}
async void mySocket_MessageReceived(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args)
{
var reader = args.GetDataReader();
reader.UnicodeEncoding = UnicodeEncoding.Utf8;
string msg = reader.ReadString(reader.UnconsumedBufferLength);
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
this.lbRecMessages.Items.Add(msg);
});
}
private async void onSend( object sender, RoutedEventArgs e)
{
if (mySocket == null) return;
if ( this.txtRemote.Text == "" || this.txtPort.Text == "")
{
return;
}
HostName host = new HostName( this.txtRemote.Text);
var outStream = await mySocket.GetOutputStreamAsync(host, this.txtPort.Text);
DataWriter writer = new DataWriter(outStream);
// 往流里面写数据
writer.WriteString( this.txtMessageInput.Text);
await writer.StoreAsync();
writer.DetachStream();
tbMessage.Text = "消息已发送。";
txtMessageInput.Text = "";
}
}
}
注意以下几点:
1、调用BindServiceNameAsync或BindEndpointAsync方法绑定本地终结点之前,先注册MessageReceived事件处理。
2、在发送消息后,不要把输出流关了,不然下次再发消息时就会发生异常,关闭流就几乎相当于把socket也关了。
writer.WriteString(this.txtMessageInput.Text);
await writer.StoreAsync();
writer.DetachStream();
WriteString完了之后,数据还没有发送,StoreAsync调用后,数据才被提交到流中。用完之后要调用DetachStream,将DataWriter与流进行分离,但不要关闭流。
第二项,WPF端。
界面布局很简单,XAML具备极强的可移植性,所以,直接从刚才上面的应用中复制XAML到WPF项目的MainWindow.xaml中,然后稍微改一下就行了。
xmlns= "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x= "http://schemas.microsoft.com/winfx/2006/xaml"
Title= "客户端" Height= "500" Width= "800">
< Window.Resources>
< Style x:Key= "tbfield" TargetType= "{x:Type TextBlock}">
< Setter Property= "FontSize" Value= "20" />
< Setter Property= "VerticalAlignment" Value= "Center" />
< Setter Property= "Margin" Value= "2,0,6,0" />
< /Style>
< /Window.Resources>
< Grid>
< StackPanel>
<!-- 用于显示接收到的消息 -->
< ListBox x:Name= "lbRecMessages" Height= "200" />
< Grid Margin= "0,18,0,15">
< Grid.ColumnDefinitions>
< ColumnDefinition Width= "auto" />
< ColumnDefinition Width= "*" />
< ColumnDefinition Width= "auto" />
< ColumnDefinition Width= "*" />
< /Grid.ColumnDefinitions>
< TextBlock Grid.Column= "0" Style= "{DynamicResource tbfield}" Text= "远程主机:" />
< TextBox x:Name= "txtRemote" Grid.Column= "1" Width= "260" HorizontalAlignment= "Left" Text= "127.0.0.1" />
< TextBlock Grid.Column= "2" Style= "{StaticResource tbfield}" Text= "远程端口:" />
< TextBox x:Name= "txtPort" Grid.Column= "3" Width= "90" HorizontalAlignment= "Left" />
< /Grid>
<!-- 用于输入要发送的消息 -->
< TextBox x:Name= "txtMessageInput" Margin= "2,13,2,16" Height= "95" />
< StackPanel Margin= "3,10,0,15" Orientation= "Horizontal">
< Button Content= "发送消息" Margin= "0,1,12,2" Click= "onSend" />
< TextBlock Margin= "8,2,0,2" x:Name= "tbMessage" FontSize= "22" />
< /StackPanel>
< /StackPanel>
< /Grid>
< /Window>
而对于后面的代码,就跟以前的.NET开发一样了,用UdpClient类就能完成了。
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Net;
using System.IO;
using System.Net.Sockets;
namespace wpfUDPSocketApp
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
UdpClient mClient = null;
const int LOCAL_PORT = 9800; //本地端口
public MainWindow()
{
InitializeComponent();
this.mClient = new UdpClient(LOCAL_PORT);
this.Loaded += (a, b) =>
{
Task.Run( new Action( this.ReceiveMessage));
};
}
private void onSend( object sender, RoutedEventArgs e)
{
int rmPort= int.MinValue;
if (mClient != null && this.txtMessageInput.Text != "" && this.txtRemote.Text != "" && int.TryParse(txtPort.Text, out rmPort))
{
byte[] buffer = Encoding.UTF8.GetBytes( this.txtMessageInput.Text);
mClient.Send(buffer, buffer.Length, this.txtRemote.Text, rmPort);
tbMessage.Text = "消息已发送。";
txtMessageInput.Clear();
}
}
private void ReceiveMessage()
{
IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);
while (mClient != null)
{
byte[] buffer = mClient.Receive( ref ep);
string msg = Encoding.UTF8.GetString(buffer);
Dispatcher.BeginInvoke( new Action(() =>
{
this.lbRecMessages.Items.Add(msg);
}), null);
}
}
}
}
同时启动两个应用就可以测试了,注意远程端口要填对,对方在哪个端口上侦听你就填那个端口号就行了。
代码随后上传到资源中。
本文来自tcjiaan的博客,原文地址:http://blog.csdn.net/tcjiaan/article/details/8255955