03、Windows Phone 套接字(Socket)实战之WP客户端设计

 

      因为 PC 端和 WP 端进行通信时,采用的自定义的协议,所以也需要定义 DataType 类来判断

通信数据的类型,并且把数据的描述信息(head) 和数据的实际内容(body)进行拼接和反转,所以

在 WP 端也添加一个 CommonHelper.cs 文件。因为 PC 端的 CommonHelper 类的内容和 WP 端

的类功能基本相似,只是有一点点差别,这里就不再介绍 WP 端的 CommonHelper 类了。

     工程文件(客户端和服务器端)下载

      注意事项:这个工程的 demo 是手机端通过 Wifi 或者 WP模拟器与 PC 端完成通信的,所以 WP手机或者

模拟器需要具有访问网络的权限时才能运行成功,如果 WP 端无法连接 PC 端,可能是 PC防火墙或者内部局域网

的防火墙禁用了此 TCP 的连接。

      另外,如果运行的 PC 是笔记本的话,因为目前的主流笔记本都具有分享 Wifi 热点的功能,所以如果笔记本

能够联网的话,也可以让 WP8 的手机也连接到笔记本分享的 Wifi,具体设置可以参考 百度经验

 

 

一、概述  

   WP 客户端使用一个 Pivot 页面,第一个 Pivot 项来显示 连接状态、聊天信息和异常信息等,

第二个 Pivot 项仅仅列出服务器端发送到 WP 端独立存储里面的文件,第三个 Pivot 项用来向 PC

端发送图片文件。

     相应的操作截图:

1)状态和消息窗口

 

2)扫描 WP 独立存储里面,服务器端发送来的文件

 

3)向 PC 端发送图片文件:

 

4)PC 端接收到图片时,直接把图片保存到 D:盘的根目录下面:

 

二、WP 端页面的布局

     这里直接贴出 MainPage 页面的 XAML :

    <Grid x:Name="LayoutRoot" Background="Transparent">
        <phone:Pivot Title="我的应用程序">
            <!--枢轴项一-->
            <phone:PivotItem Header="消息窗口">
                <!--ContentPanel - 在此处放置其他内容-->
                <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="90"/>
                        <RowDefinition Height="60"/>
                        <RowDefinition Height="270"/>
                        <RowDefinition Height="90"/>
                    </Grid.RowDefinitions>

                    <StackPanel Orientation="Horizontal">
                        <!-- PC 端的 IP 地址 -->
                        <TextBox HorizontalAlignment="Left" Text="" Height="72" Margin="5,5,0,0" 
TextWrapping
="Wrap" x:Name="txtRemoteIP" Width="289"/> <!--连接 PC服务器端--> <Button Margin="20,0,0,0" Content="连接" Width="98" Click="Button_Click"/> </StackPanel> <TextBlock x:Name="txtLocalIP" Grid.Row="1" Margin="10,5,0,0" TextWrapping="Wrap" Width="345"/> <!--"10.239.201.36"--> <!--显示连接状态、聊天消息等--> <ScrollViewer x:Name="scroll" Height="266" Margin="10,0,0,0" Grid.Row="2"> <TextBlock x:Name="labeMsg" TextWrapping="Wrap"/> </ScrollViewer> <StackPanel Grid.Row="3" Orientation="Horizontal"> <!--聊天需要输入的文字--> <TextBox x:Name="txtSendMsg" Height="72" Margin="5,5,0,0" TextWrapping="Wrap" Width="289"/> <!--发送聊天内容--> <Button x:Name="btnSendMsg" Content="发送" Margin="20,0,0,0" Width="98" Click="btnSendMsg_Click"/> </StackPanel> </Grid> </phone:PivotItem> <!--枢轴项二--> <phone:PivotItem Header="文件窗口"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="90"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <!--当服务器端有文件发送到 WP 端时,直接把文件存储到独立存储里面,点击按钮,查看这些文件--> <Button x:Name="btnScanFiles" Content="扫描 Folder 里面的文件" Click="btnScanFiles_Click"/> <!--显示独立存储中,服务器端发送来的文件--> <ListBox x:Name="listboxFiles" Grid.Row="1"> <ListBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Name}" TextWrapping="Wrap" Margin="5,5,5,30"/> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid> </phone:PivotItem> <!--枢轴项三--> <phone:PivotItem Header="文件窗口"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="90"/> <RowDefinition Height="90"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <!--选择手机图片库中的图片,并且显示在下面的 imgPhoto 控件上--> <Button x:Name="btnChoosePhoto" Content="选择图片" Click="btnChoosePhoto_Click"/> <!--向服务器端发送图片文件--> <Button x:Name="btnSendPhoto" Content="向服务器发送图片" Grid.Row="1" Click="btnSendPhoto_Click"/> <Image x:Name="imgPhoto" Grid.Row="2" Stretch="Uniform"/> </Grid> </phone:PivotItem> </phone:Pivot> </Grid>


 

二、WP 端的 SocketClient 类的设计

      参考 MSDN 文档:http://msdn.microsoft.com/zh-cn/library/windowsphone/develop/hh202858(v=vs.105).aspx

      该 MSDN 文章中,有一个简单的 Socket 操作的 Demo,这里把它进行重构了一下。

      因为在 WP 端,Socket 进行操作时,主要使用 SocketAsyncEventArgs 类最为参数,并且 SocketAsyncEventArgs 参数

设置的 Socket 异步操作操作完成后的回调,都只调用在   socketAsyncEventArgs.Completed += socketAsyncEventArgs_Completed;

上注册的方法,这些操作包括:

namespace System.Net.Sockets
{
    
    //  最近使用此对象执行的异步套接字操作类型。
    public enum SocketAsyncOperation
    {
         //  没有套接字操作。
        None = 0,
     
          // 一个套接字 Accept 操作。
        Accept = 1,
      
          //  一个套接字 Connect 操作。
        Connect = 2,
       
          //  一个套接字 Receive 操作。
        Receive = 4,
       
          //  一个套接字 ReceiveFrom 操作。
        ReceiveFrom = 5,
       
          //  一个套接字 Send 操作。
        Send = 7,
      
        //  一个套接字 SendTo 操作。
        SendTo = 9,
    }
}


所以,在 SocketAsyncEventArgs 对象的 Competed 事件触发时,在回调中通过 Switch 进行

判断操作:

  // 异步操作完成时调用
  void socketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e)
  {
      //  获取最近使用此对象执行的异步套接字操作的类型。
      switch (e.LastOperation)
      {
          case SocketAsyncOperation.Connect:
              ProcessConnect(e);
              break;
          case SocketAsyncOperation.Receive:
              ProcessReceive(e);
              break;
          case SocketAsyncOperation.ReceiveFrom:
              ProcessReceiveFrom(e);
              break;
          case SocketAsyncOperation.Send:
              ProcessSend(e);
              break;
          case SocketAsyncOperation.SendTo:
              ProcessSendTo(e);
              break;
          default:
              throw new Exception("未知操作");
      }
  }

 


完整的 SocketClient 的定义:

namespace PhoneSocketServerDemo
{
    /// <summary>
    /// 封装 Socket对象,负责与 PC 服务器端进行通信的自定义类
    /// </summary>
    public class SocketClient
    {
        // 控制异步套接字的方法所使用的缓冲区的最大尺寸
        const int Max_Buffer_Size = 1024 * 4;

        // 当操作完成后,触发消息通知
        public event EventHandler<string> Completed;

        // 负责与 PC端通信
        Socket socket;

        /// <summary>
        ///  建立与 PC 端通信的连接
        /// </summary>
        /// <param name="hostName">远程服务器的 IP地址</param>
        /// <param name="portNumber">端口号</param>
        public void Connect(string hostName, int portNumber)
        {
            //this.SocketShutDowm();

            // 将网络终结点表示为主机名或 IP 地址和端口号的字符串表示形式。
            DnsEndPoint dnsEndPoint = new DnsEndPoint(hostName, portNumber);

            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            // 表示异步套接字操作。
            SocketAsyncEventArgs socketAsyncEventArgs = new SocketAsyncEventArgs();

            // 远程 IP 或 DNS 终结点
            socketAsyncEventArgs.RemoteEndPoint = dnsEndPoint;

            socketAsyncEventArgs.Completed += socketAsyncEventArgs_Completed;

            socketAsyncEventArgs.UserToken = null;

            // 开始一个对远程主机连接的异步请求。
            socket.ConnectAsync(socketAsyncEventArgs);
        }

        /// <summary>
        /// 监听服务器端发来的数据
        /// </summary>
        /// <returns></returns>
        public Task Receive()
        {
            return Task.Factory.StartNew(() =>
                 {
                     if (socket != null)
                     {
                         // 表示异步套接字操作。
                         SocketAsyncEventArgs socketAsyncEventArgs = new SocketAsyncEventArgs();

                         socketAsyncEventArgs.Completed += socketAsyncEventArgs_Completed;

                         socketAsyncEventArgs.RemoteEndPoint = socket.RemoteEndPoint;

                         socketAsyncEventArgs.UserToken = null;

                         socketAsyncEventArgs.SetBuffer(new byte[Max_Buffer_Size], 0, Max_Buffer_Size);

                         // 开始一个异步请求以便从连接的 Socket 对象中接收数据。
                         bool result = socket.ReceiveAsync(socketAsyncEventArgs);
                     }
                     else
                     {
                         OnCompleted("还没有建立连接");
                     }
                 });
        }

        /// <summary>
        /// 向服务器端发送文件
        /// </summary>
        /// <param name="dataType">body 的数据类型</param>
        /// <param name="byteFile">文件的 byte[]内容</param>
        /// <returns></returns>
        public Task SendFile(DataType dataType, byte[] byteFile)
        {
            return Task.Factory.StartNew(() =>
            {
                if (socket != null)
                {
                    SocketAsyncEventArgs socketAsyncEventArgs = new SocketAsyncEventArgs();

                    socketAsyncEventArgs.Completed += socketAsyncEventArgs_Completed;

                    socketAsyncEventArgs.RemoteEndPoint = socket.RemoteEndPoint;

                    socketAsyncEventArgs.UserToken = null;

                    byte[] sendBytes = CommonHelper.ConvertFileToByte(dataType, byteFile);

                    //  设置要用于异步套接字方法的数据缓冲区。
                    socketAsyncEventArgs.SetBuffer(sendBytes, 0, sendBytes.Length);

                    // 将数据异步发送到连接的 Socket 对象
                    bool result = socket.SendAsync(socketAsyncEventArgs);
                }
                else
                {
                    OnCompleted("还没有建立连接");
                }
            });
        }

        /// <summary>
        /// 向服务器端发送 文件 或者 文字 内容
        /// </summary>
        /// <param name="dataType">文件类型</param>
        /// <param name="strPath">文件路径</param>
        /// <param name="strMsg">文字消息</param>
        /// <returns></returns>
        public Task Send(DataType dataType, string strPath, string strMsg)
        {
            return Task.Factory.StartNew(() =>
            {
                if (socket != null)
                {
                    SocketAsyncEventArgs socketAsyncEventArgs = new SocketAsyncEventArgs();

                    socketAsyncEventArgs.Completed += socketAsyncEventArgs_Completed;

                    socketAsyncEventArgs.RemoteEndPoint = socket.RemoteEndPoint;

                    socketAsyncEventArgs.UserToken = null;

                    byte[] sendBytes = CommonHelper.ConvertDataToByte(dataType, strPath, strMsg);

                    socketAsyncEventArgs.SetBuffer(sendBytes, 0, sendBytes.Length);

                    // 将数据异步发送到连接的 Socket 对象
                    bool result = socket.SendAsync(socketAsyncEventArgs);
                }
                else
                {
                    OnCompleted("还没有建立连接");
                }
            });
        }

        // 异步操作完成时调用
        void socketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e)
        {
            //  获取最近使用此对象执行的异步套接字操作的类型。
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Connect:
                    ProcessConnect(e);
                    break;
                case SocketAsyncOperation.Receive:
                    ProcessReceive(e);
                    break;
                case SocketAsyncOperation.ReceiveFrom:
                    ProcessReceiveFrom(e);
                    break;
                case SocketAsyncOperation.Send:
                    ProcessSend(e);
                    break;
                case SocketAsyncOperation.SendTo:
                    ProcessSendTo(e);
                    break;
                default:
                    throw new Exception("未知操作");
            }
        }

        // 处理 socket连接 操作的回调
        void ProcessConnect(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                OnCompleted("连接服务器成功");
                //Socket socket = e.UserToken as Socket;

                Receive();
            }
            else
            {
                OnCompleted("连接服务器失败 :" + e.SocketError.ToString());
            }
        }

        // 处理服务器端发送来的数据
        async void ProcessReceive(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                string strMsg = null;
                byte[] byteFile = null;
                DataType dataType = null;
                CommonHelper.ConvertByteToData(e.Buffer, out dataType, out  byteFile, out strMsg);

                if (dataType != null && dataType.IsFile == true)
                {
                    await CommonHelper.SaveFile(dataType.FileName + dataType.Exten, byteFile);

                    OnCompleted("已经保存服务器发送的文件:" + dataType.FileName + dataType.Exten);
                }
                else
                {
                    OnCompleted(">>服务器:" + strMsg);
                }

                // 处理完服务器发送的数据后,继续等待消息
                Receive();
            }
            else
            {
                OnCompleted(e.SocketError.ToString());
            }
        }

        void ProcessReceiveFrom(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {

            }
            else
            {
                OnCompleted(e.SocketError.ToString());
            }
        }

        // 处理向服务器端发送数据后的回调
        void ProcessSend(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                string str = Encoding.UTF8.GetString(e.Buffer, 0, e.Buffer.Length);

            }
            else
            {
                OnCompleted(e.SocketError.ToString());
            }
        }

        void ProcessSendTo(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {


            }
            else
            {
                OnCompleted(e.SocketError.ToString());
            }
        }

        // 关闭 Socket 
        public void SocketShutDowm()
        {
            if (socket != null)
            {
                socket.Close();
                socket.Dispose();
            }
        }

        // 向宿主页面显示消息
        void OnCompleted(string strMsg)
        {
            if (Completed != null)
            {
                Completed(null, strMsg);
            }
        }
    }
}

 

 

三、MainPage 页面的 CodeBehind 代码:

namespace PhoneSocketServerDemo
{
    public partial class MainPage : PhoneApplicationPage
    {
        // 构造函数
        public MainPage()
        {
            InitializeComponent();

            MyIPAddress finder = new MyIPAddress();
            finder.Find((address) =>
            {
                Dispatcher.BeginInvoke(() =>
                {
                    txtLocalIP.Text = "手机的 IP 地址:" + (address == null ? "Unknown" : address.ToString());
                });
            });

            //txtLocalIP.Text = MyIPAddress.Find().ToString();

            txtRemoteIP.Text = RemoteIP;

            socketClient = new SocketClient();
            socketClient.Completed += client_Completed;
        }

        void client_Completed(object sender, string e)
        {
            ShowMsg(e);
        }

        // PC 端服务器的地址
        string RemoteIP = "172.28.125.70";//"10.239.201.48";
        int RemotePort = 5000;

        #region   枢轴项一
        SocketClient socketClient;
       // 连接服务器
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            socketClient.Connect(RemoteIP , RemotePort);
            ShowMsg("正在连接服务器....");
            //socketClient.Receive();
            //client.Receive();
        }
     
        // 发送文字
        private void btnSendMsg_Click(object sender, RoutedEventArgs e)
        {
            if (!string.IsNullOrEmpty(txtSendMsg.Text))
            {
                socketClient.Send(new DataType { IsFile = false }, null, txtSendMsg.Text);
                ShowMsg(txtSendMsg.Text);
                txtSendMsg.Text = "";
            }
            else
            {
                ShowMsg("请先输入内容");
            }
        }

        #endregion

        #region   枢轴项二
        // 浏览独立存储中的文件,显示到 listbox 中
        private async void btnScanFiles_Click(object sender, RoutedEventArgs e)
        {
            IReadOnlyList<Windows.Storage.StorageFile> files = await CommonHelper.ScanFiles();
            if (files.Count > 0)
            {
                listboxFiles.ItemsSource = files;
            }
            else
            {
                MessageBox.Show("该文件夹中没有文件");
            }
        }
        #endregion

        #region   枢轴项三

        // 选择发送到服务器端的图片文件
        private void btnChoosePhoto_Click(object sender, RoutedEventArgs e)
        {
            Microsoft.Phone.Tasks.PhotoChooserTask choooser = new Microsoft.Phone.Tasks.PhotoChooserTask();

            choooser.Completed += choooser_Completed;

            choooser.Show();
        }

        void choooser_Completed(object sender, Microsoft.Phone.Tasks.PhotoResult e)
        {
            if (e.TaskResult == Microsoft.Phone.Tasks.TaskResult.OK)
            {
                BitmapImage bitimage = new BitmapImage();
                bitimage.SetSource(e.ChosenPhoto);
                imgPhoto.Source = bitimage;               
            }
            else
            {
                imgPhoto.Source = null;
            }
        }

        // 向服务器端发送图片
        private void btnSendPhoto_Click(object sender, RoutedEventArgs e)
        {
            if (imgPhoto.Source != null)
            {
                BitmapImage bitmap = imgPhoto.Source as BitmapImage;
                WriteableBitmap wb = new WriteableBitmap(bitmap);

                MemoryStream stream = new MemoryStream();

                Extensions.SaveJpeg(wb, stream, wb.PixelWidth, wb.PixelHeight, 0, 100);
                byte[] bytesPhoto = stream.GetBuffer();

                // 发送文件,文件统一命名
                socketClient.SendFile(new DataType { IsFile = true , Exten = ".jpg", FileName = "手机发送的图片"}, bytesPhoto);
            }
            else
            {
                MessageBox.Show("请先选择一张图片");
            }
        }
        #endregion

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            // 如果是重新导航到该页面,则重新建立连接
            if (e.NavigationMode == NavigationMode.Back)
            {
                // 如果是重新导航到该应用,则重新连接服务器端
                socketClient.Connect( RemoteIP, RemotePort);
                //socketClient.Receive();
            }
            base.OnNavigatedTo(e);
        }

        protected override void OnNavigatedFrom(NavigationEventArgs e)
        {
            //socketClient.SocketShutDowm();
            base.OnNavigatedFrom(e);
        }

        // 显示消息
        void ShowMsg(string strMsg)
        {
            this.Dispatcher.BeginInvoke(delegate
            {
                labeMsg.Text += strMsg + Environment.NewLine + Environment.NewLine;

                // 滚动到底部
                scroll.ScrollToVerticalOffset(scroll.VerticalOffset);
            });
        }

    }
}

 

posted @ 2013-06-25 19:24  博琼  阅读(842)  评论(1编辑  收藏  举报