现在的位置: 首页 > 综合 > 正文

新时尚Windows8开发(38):聊天程序

2012年12月04日 ⁄ 综合 ⁄ 共 6734字 ⁄ 字号 评论关闭

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);
            }
        }
    }
}

同时启动两个应用就可以测试了,注意远程端口要填对,对方在哪个端口上侦听你就填那个端口号就行了。

代码随后上传到资源中。

抱歉!评论已关闭.