Socket一直是一个痛苦的玩意,不过,还是要把它说一说,其实,我们完全可以用WCF实现网络通信功能。
今天先说说DatagramSocket类,别看这名字好像有些陌生,其实,说白了,这家伙只是换了个“马甲”罢了,本质上说就是UDP传输,最适合做就是传输一些简单的文本信息,所以,弄个聊天程序相当合适。
由于Windows“板砖”应用一般是一个应用窗口占满整个屏幕,有时候可能会挂到屏幕的一边,为了说明DatagramSocket就是UDP协议的socket,我们一端使用Windows Store应用程序,而另一端使用WPF来开发,看看这两者之间的通信就可以说明。
为了在本机测试操作更方便,在Windows Store应用端可以考虑用模拟器来运行,模拟器是根据本地机器当前的系统来模拟的,所以,IP地址可以使用127.0.0.1。
以下是应用运行的效果图。
为了不产生乱码,通信双方均用UTF-8编码,这样它们才有默契。
第一项,实现Windows Store端。
主页布局参考下面XAML,我不再解释。
<
Page
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;
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中,然后稍微改一下就行了。
<
Window x:Class=
"wpfUDPSocketApp.MainWindow"
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;
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