Windows Phone开发(46):与Socket有个约会

简介: 原文: Windows Phone开发(46):与Socket有个约会 不知道大家有没有“谈Socket色变”的经历?就像我一位朋友所说的,Socket这家伙啊,不得已而用之。
原文: Windows Phone开发(46):与Socket有个约会

不知道大家有没有“谈Socket色变”的经历?就像我一位朋友所说的,Socket这家伙啊,不得已而用之。哈,Socket真的那么恐怖吗?

其实这话一点也不假,Socket有时候真的不太好操控,也不好维护,但不管怎么样,我们还是要面对它的,没准Socket是一位大美女哦。

关于Socket的前世今生就不用我详述了,关于她的历史,已经不少人仁志士为她立传写著了,像我们国内的百度百科、互动百科等;全球著名的如维基百科之属。而且,能加入WP开发的学习行列的,我想各位对.NET的其它技术肯定是有一定基础的。我也相信,各位同仁过去一定也写过与Socket打交道的程序。那么,WP中的Socket又将如何呢?

前提公布答案吧,在WP中使用Socket跟你在其它桌面应用项目如WinForm,WPF等中是一样的,而且说白了,WP中的Socket只不过是从Silverlight框架中继承过来的。

.NET的一大优势就是集成性和统一性都好,这不,你看,无论你是编写桌面应用程序,还是WP上的应用程序,你会发现,你的学习成本不高,很多东西都是一样的,而且是相通的。显然这也是Win8和WP8的应用程序可以整合的原因吧。

在WP中使用Socket要注意以下几点:

1、WP客户端应用程序一般不被视为服务器端,因为不能进行绑定本地终结点和监听连接。但是,收发数据是没问题D。

2、在WP中的Socket操作(连接、接收以及发送)都是异步进行的。如果希望UI线程和后前线程进行同步,不妨使用System.Threading.ManualResetEvent类,这个东西不好讲述,也不好理解。这样吧,我举一个例子。

有一天,NC和脑残因为一件小事闹冲突,闹来闹去还是不能解决,怎么办呢?于是,NC和脑残决定来一场比试。两人约定以跑步方式比试,谁跑得快谁就是胜利者。然而,NC这个人一向比较自负,他相信脑残绝对跑不过他。这样,NC就修改了比赛规则:

NC让脑残先跑5秒,然后他才开始。

假设NC是主线程,脑残是后台线程,现在的情况是:主线程先等待一会儿,让后台线程先执行;后台线程执行5秒后向主线程发出信号,主线程收到信号后再继续往下执行。按照故事里的情节:NC先让脑残跑5秒钟,他自己就在起跑线上等待,脑残跑了5秒后向NC发出信号,NC看到信号后就开始跑。

下面介绍一个类——SocketAsyncEventArgs。

这个类作为启动异步操作时传递的参数,它可以包含如接收数据的缓冲区、远程主机、用户自定义对象等内容,这个类并不复杂,打开“对象浏览器”看看就一目了然了。

要设置用于异步接收数据的缓冲区,应调用SetBuffer方法。

好,理论就扯到这儿,其实也没有什么新的知识点,我只是简单提一下罢了。

按照惯例,大家都会猜到,理论过后要干什么了,是的,付诸实践。

 

在很多情况下,关于Socket的例子,都会做一个聊天程序的,不过,聊天程序要求服务器端和客户都具有发送和接收数据的功能,这样会增加实例的难度和代码长度,不方便入门者阅读。所以,想了一下,今天咱们不玩聊天的,今天咱们玩遥控飞机,如何?

程序代码较长,也不便于逐一来讲解,这样吧,为了保持代码的可读性,我会把完整的代码都贴出来,在代码中我会适当地加上注释。

先说一下原理,利用Socket进行通讯这不用说了,那是肯定的。功能是通过WP手机客户端应用程序来控制PC端播放、暂停和停止动画,而动画嘛,也不弄那么复杂了,就弄个矩形从左边移到右边的动画吧。

 

第一部分  服务器端

既然要播放动画,少不了要用WPF了,而且,也方便贴界面布局的代码。

1、新建WPF应用程序项目。

2、打开MainWindow.xaml文件(默认新建项目后自动打开),输入以下XAML代码。

<Window x:Class="MYServer.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="服务器端" Height="350" Width="525">
    
    <Window.Resources>
        <Storyboard x:Key="std">
            <DoubleAnimation Duration="0:0:5"
                                 Storyboard.TargetName="rect"
                                 Storyboard.TargetProperty="(Rectangle.RenderTransform).(TranslateTransform.X)"
                                 To="400"/>
        </Storyboard>
    </Window.Resources>
    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Rectangle x:Name="rect" Grid.Row="0" Width="50" Height="50" Fill="Orange" HorizontalAlignment="Left" VerticalAlignment="Center">
            <Rectangle.RenderTransform>
                <TranslateTransform X="0" Y="0"/>
            </Rectangle.RenderTransform>
        </Rectangle>
        <TextBlock Name="txtDisplay" Grid.Row="1"/>
    </Grid>
</Window>


3、打开MainWindow.xaml.cs文件,完成后台代码逻辑。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
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.Windows.Media.Animation;
using System.IO;
using System.Net;
using System.Net.Sockets;


namespace MYServer
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        Storyboard std = null; //演示图板
        public MainWindow()
        {
            InitializeComponent();

            // 从资源中把Key为std的Storyboard读出来
            std = this.Resources["std"] as Storyboard;
            // 声明用于监听连接请求的Socket
            Socket Server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint local = new IPEndPoint(IPAddress.Any, 1377); //监听所有网络接口上的地址
            Server.Bind(local);// 绑定本地终结点
            Server.Listen(100);// 侦听连接请求
            // 开始异步接受传入的连接请求
            Server.BeginAccept(new AsyncCallback(this.AcceptSocketCallback), Server);
        }

        /// <summary>
        /// 接受传入的Socket的回调
        /// </summary>
        private void AcceptSocketCallback(IAsyncResult ia)
        {
            Socket _socket = ia.AsyncState as Socket;
            Socket accptSocket = _socket.EndAccept(ia);
            try
            {
                IPEndPoint remote = (IPEndPoint)accptSocket.RemoteEndPoint;
                // 显示客户端的IP
                Dispatcher.BeginInvoke(new Action<string>(this.SetIPForText), remote.Address.ToString());
                StateObject so = new StateObject();
                so.theSocket = accptSocket;
                // 开始异步接收消息
                accptSocket.BeginReceive(so.Buffer, 0, so.Buffer.Length, SocketFlags.None, new AsyncCallback(this.ReceiveCallback), so);
            }
            catch
            {

            }
            // 继续接受连接请求
            _socket.BeginAccept(new AsyncCallback(this.AcceptSocketCallback), _socket);
        }
        /// <summary>
        /// 接收消息的回调
        /// </summary>
        private void ReceiveCallback(IAsyncResult ia)
        {
            StateObject _so = ia.AsyncState as StateObject;
            Socket _socket = _so.theSocket;
            try
            {
                int n = _socket.EndReceive(ia);//n就是接收到的字节数
                string msg = Encoding.UTF8.GetString(_so.Buffer, 0, n);
                // 判断客户端发送了啥命令
                switch (msg)
                {
                    case "play":
                        Dispatcher.BeginInvoke(new Action(this.Play), null);
                        break;
                    case "pause":
                        Dispatcher.BeginInvoke(new Action(this.Pause), null);
                        break;
                    case "stop":
                        Dispatcher.BeginInvoke(new Action(this.Stop), null);
                        break;
                    default:
                        break;
                }
            }
            catch 
            {
            }
            _so = new StateObject();
            _so.theSocket = _socket;
            // 继续接收消息
            _socket.BeginReceive(_so.Buffer,
                                0,
                                _so.Buffer.Length,
                                SocketFlags.None,
                                new AsyncCallback(this.ReceiveCallback),
                                _so);
        }
        /// <summary>
        /// 显示客户端的IP
        /// </summary>
        private void SetIPForText(string ip)
        {
            this.txtDisplay.Text = "客户端IP:" + ip;
        }

        #region 控制动画的方法
        private void Play()
        {
            std.Begin();
        }
        private void Pause()
        {
            std.Pause();
        }
        private void Stop()
        {
            std.Stop();
        }
        #endregion
    }

    /// <summary>
    /// 用于异步Socket操作传递的状态对象
    /// </summary>
    public class StateObject
    {
        private const int BUFFER_SIZE = 512;

        public byte[] Buffer { get;  set; }

        public Socket theSocket { get; set; }

        /// <summary>
        /// 构造函数
        /// </summary>
        public StateObject()
        {
            this.Buffer = new byte[BUFFER_SIZE];
        }
    }
}


 

 

第二部分  WP客户端

1、新建Windows Phone应用程序项目。

2、打开MainPage.xaml文件,参考下面的XAML代码。

<phone:PhoneApplicationPage 
    x:Class="WPClient.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True">

    <!--LayoutRoot 是包含所有页面内容的根网格-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitlePanel 包含应用程序的名称和页标题-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="我的应用程序" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="页面名称" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

        <!--ContentPanel - 在此处放置其他内容-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <Grid.RowDefinitions>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <Grid Grid.Row="0">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition />
                    <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions>
                <TextBlock Grid.Column="0" VerticalAlignment="Center" Text="服务器IP:" />
                <TextBox Name="txtServerIP" Grid.Column="1"/>
                <Button Grid.Column="2" Content="连接" Click="onConnect"/>
            </Grid>
            <StackPanel Grid.Row="1">
                <Button Content="放播动画" Click="onPlay"/>
                <Button Content="暂停动画" Click="onPause"/>
                <Button Content="停止动画" Click="onStop"/>
                <TextBlock Name="txtbInfo" Margin="3,18,3,0"/>
            </StackPanel>
        </Grid>
    </Grid>
 
    <!--演示 ApplicationBar 用法的示例代码-->
    <!--<phone:PhoneApplicationPage.ApplicationBar>
        <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
            <shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" Text="按钮 1"/>
            <shell:ApplicationBarIconButton IconUri="/Images/appbar_button2.png" Text="按钮 2"/>
            <shell:ApplicationBar.MenuItems>
                <shell:ApplicationBarMenuItem Text="菜单项 1"/>
                <shell:ApplicationBarMenuItem Text="菜单项 2"/>
            </shell:ApplicationBar.MenuItems>
        </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>-->

</phone:PhoneApplicationPage>


3、打开MainPage.xaml.cs,输入以下代码。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;

using System.Net.Sockets;
using System.IO;
using System.Threading;

namespace WPClient
{
    public partial class MainPage : PhoneApplicationPage
    {
        Socket mySocket = null;
        ManualResetEvent MyEvent = null;
        // 构造函数
        public MainPage()
        {
            InitializeComponent();
        }

        protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);

            if (mySocket == null)
            {
                mySocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            }
            if (MyEvent == null)
            {
                MyEvent = new ManualResetEvent(false);
            }
        }

        protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
        {
            if (mySocket != null)
            {
                mySocket.Shutdown(SocketShutdown.Both);
                mySocket.Close();
            }
            base.OnNavigatedFrom(e);
        }

        private void onConnect(object sender, RoutedEventArgs e)
        {
            if (mySocket != null)
            {
                SocketAsyncEventArgs connArg = new SocketAsyncEventArgs();
                // 要连接的远程服务器
                connArg.RemoteEndPoint = new DnsEndPoint(this.txtServerIP.Text, 1377);
                // 操作完成后的回调
                connArg.Completed += (sendObj, arg) =>
                {
                    if (arg.SocketError == SocketError.Success) //连接成功
                    {
                        Dispatcher.BeginInvoke(() => txtbInfo.Text = "连接成功。");
                    }
                    else
                    {
                        Dispatcher.BeginInvoke(() =>
                        {
                            txtbInfo.Text = "连接失败,错误:" + arg.SocketError.ToString();
                        });
                    }
                    // 向调用线程报告操作结束
                    MyEvent.Set();
                };
                // 重置线程等待事件
                MyEvent.Reset();
                txtbInfo.Text = "正在连接,请等候……";
                // 开始异连接
                mySocket.ConnectAsync(connArg);
                // 等待连接完成
                MyEvent.WaitOne(6000);
            }
        }

        private void onPause(object sender, RoutedEventArgs e)
        {
            SendCommand("pause");
        }

        private void onStop(object sender, RoutedEventArgs e)
        {
            SendCommand("stop");
        }

        private void onPlay(object sender, RoutedEventArgs e)
        {
            SendCommand("play");
        }


        private void SendCommand(string txt)
        {
            if (mySocket != null && mySocket.Connected)
            {
                SocketAsyncEventArgs sendArg = new SocketAsyncEventArgs();
                byte[] buffer = System.Text.Encoding.UTF8.GetBytes(txt);
                sendArg.SetBuffer(buffer, 0, buffer.Length);
                // 发送完成后的回调
                sendArg.Completed += (objSender, mArg) =>
                    {
                        // 如果操作成功
                        if (mArg.SocketError == SocketError.Success)
                        {
                            Dispatcher.BeginInvoke(() => txtbInfo.Text = "发送成功。");
                        }
                        else
                        {
                            Dispatcher.BeginInvoke(() =>
                                {
                                    this.txtbInfo.Text = "发送失败,错误:" + mArg.SocketError.ToString();
                                });
                        }
                        // 报告异步操作结束
                        MyEvent.Set();
                    };
                // 重置信号
                MyEvent.Reset();
                txtbInfo.Text = "正在发送,请等候……";
                // 异步发送
                mySocket.SendAsync(sendArg);
                // 等待操作完成
                MyEvent.WaitOne(6000);
            }
        }
    }
}


 

先运行服务器端,再在WP模拟器或真实手机上运行客户端。

在手机客户端中,输入IP地址,点“连接”,连接成功后,就可以发送指令了。

 

 好的,就到这儿吧,示例的源码我会上专到“资源”中,有需要的话,大家可以按标题下载。

 

 

目录
相关文章
|
6天前
|
IDE 关系型数据库 开发工具
使用Visual Basic进行Windows窗体开发
【4月更文挑战第27天】本文介绍了使用Visual Basic进行Windows窗体(WinForms)开发的步骤,从搭建开发环境到创建、设计用户界面,再到编写事件驱动的代码和数据绑定。Visual Basic结合WinForms提供了一种易学易用的桌面应用开发方案。通过调试、优化、部署和维护,开发者可以构建专业应用程序。随着技术发展,掌握最新UI设计和开发工具对于保持竞争力至关重要。本文为初学者提供了基础指导,鼓励进一步探索和学习。
|
3天前
|
前端开发 Linux iOS开发
【Flutter前端技术开发专栏】Flutter在桌面应用(Windows/macOS/Linux)的开发实践
【4月更文挑战第30天】Flutter扩展至桌面应用开发,允许开发者用同一代码库构建Windows、macOS和Linux应用,提高效率并保持平台一致性。创建桌面应用需指定目标平台,如`flutter create -t windows my_desktop_app`。开发中注意UI适配、性能优化、系统交互及测试部署。UI适配利用布局组件和`MediaQuery`,性能优化借助`PerformanceLogging`、`Isolate`和`compute`。
【Flutter前端技术开发专栏】Flutter在桌面应用(Windows/macOS/Linux)的开发实践
|
5天前
|
编解码 Linux Windows
FFmpeg开发笔记(十三)Windows环境给FFmpeg集成libopus和libvpx
本文档介绍了在Windows环境下如何为FFmpeg集成libopus和libvpx库。首先,详细阐述了安装libopus的步骤,包括下载源码、配置、编译和安装,并更新环境变量。接着,同样详细说明了libvpx的安装过程,注意需启用--enable-pic选项以避免编译错误。最后,介绍了重新配置并编译FFmpeg以启用这两个库,通过`ffmpeg -version`检查是否成功集成。整个过程参照了《FFmpeg开发实战:从零基础到短视频上线》一书的相关章节。
20 0
FFmpeg开发笔记(十三)Windows环境给FFmpeg集成libopus和libvpx
|
6天前
|
编解码 Linux Windows
FFmpeg开发笔记(十一)Windows环境给FFmpeg集成vorbis和amr
在Windows环境下,为FFmpeg集成音频编解码库,包括libogg、libvorbis和opencore-amr,涉及下载源码、配置、编译和安装步骤。首先,安装libogg,通过配置、make和make install命令完成,并更新PKG_CONFIG_PATH。接着,安装libvorbis,同样配置、编译和安装,并修改pkgconfig文件。之后,安装opencore-amr。最后,重新配置并编译FFmpeg,启用ogg和amr支持,通过ffmpeg -version检查是否成功。整个过程需确保环境变量设置正确,并根据路径添加相应库。
23 1
FFmpeg开发笔记(十一)Windows环境给FFmpeg集成vorbis和amr
|
28天前
|
Linux 编译器 C语言
FFmpeg开发笔记(二)搭建Windows系统的开发环境
在Windows上学习FFmpeg通常较困难,但通过安装预编译的FFmpeg开发包可以简化流程。首先需要安装MSYS2来模拟Linux环境。下载并执行MSYS2安装包,然后修改msys2_shell.cmd以继承Windows的Path变量。使用pacman安装必要的编译工具。接着,下载预编译的FFmpeg Windows包,解压并配置系统Path。最后,在MSYS2环境中运行`ffmpeg -version`确认安装成功。欲深入学习FFmpeg开发,推荐阅读《FFmpeg开发实战:从零基础到短视频上线》。
32 4
FFmpeg开发笔记(二)搭建Windows系统的开发环境
|
2月前
|
数据可视化 数据库 C++
Qt 5.14.2揭秘高效开发:如何用VS2022快速部署Qt 5.14.2,打造无与伦比的Windows应用
Qt 5.14.2揭秘高效开发:如何用VS2022快速部署Qt 5.14.2,打造无与伦比的Windows应用
|
5月前
|
监控 API C++
8.4 Windows驱动开发:文件微过滤驱动入门
MiniFilter 微过滤驱动是相对于`SFilter`传统过滤驱动而言的,传统文件过滤驱动相对来说较为复杂,且接口不清晰并不符合快速开发的需求,为了解决复杂的开发问题,微过滤驱动就此诞生,微过滤驱动在编写时更简单,多数`IRP`操作都由过滤管理器`(FilterManager或Fltmgr)`所接管,因为有了兼容层,所以在开发中不需要考虑底层`IRP`如何派发,更无需要考虑兼容性问题,用户只需要编写对应的回调函数处理请求即可,这极大的提高了文件过滤驱动的开发效率。
43 0
|
26天前
|
监控 安全 API
7.3 Windows驱动开发:内核监视LoadImage映像回调
在笔者上一篇文章`《内核注册并监控对象回调》`介绍了如何运用`ObRegisterCallbacks`注册`进程与线程`回调,并通过该回调实现了`拦截`指定进行运行的效果,本章`LyShark`将带大家继续探索一个新的回调注册函数,`PsSetLoadImageNotifyRoutine`常用于注册`LoadImage`映像监视,当有模块被系统加载时则可以第一时间获取到加载模块信息,需要注意的是该回调函数内无法进行拦截,如需要拦截则需写入返回指令这部分内容将在下一章进行讲解,本章将主要实现对模块的监视功能。
38 0
7.3 Windows驱动开发:内核监视LoadImage映像回调
|
5月前
|
监控 安全 API
7.2 Windows驱动开发:内核注册并监控对象回调
在笔者上一篇文章`《内核枚举进程与线程ObCall回调》`简单介绍了如何枚举系统中已经存在的`进程与线程`回调,本章`LyShark`将通过对象回调实现对进程线程的`句柄`监控,在内核中提供了`ObRegisterCallbacks`回调,使用这个内核`回调`函数,可注册一个`对象`回调,不过目前该函数`只能`监控进程与线程句柄操作,通过监控进程或线程句柄,可实现保护指定进程线程不被终止的目的。
31 0
7.2 Windows驱动开发:内核注册并监控对象回调
|
5月前
|
监控 安全 API
7.6 Windows驱动开发:内核监控FileObject文件回调
本篇文章与上一篇文章`《内核注册并监控对象回调》`所使用的方式是一样的都是使用`ObRegisterCallbacks`注册回调事件,只不过上一篇博文中`LyShark`将回调结构体`OB_OPERATION_REGISTRATION`中的`ObjectType`填充为了`PsProcessType`和`PsThreadType`格式从而实现监控进程与线程,本章我们需要将该结构填充为`IoFileObjectType`以此来实现对文件的监控,文件过滤驱动不仅仅可以用来监控文件的打开,还可以用它实现对文件的保护,一旦驱动加载则文件是不可被删除和改动的。
30 1
7.6 Windows驱动开发:内核监控FileObject文件回调