天天看點

02、Windows Phone 套接字(Socket)實戰之伺服器端設計

 這裡主要寫 PC 伺服器端的邏輯,UI 使用的是 WPF,因為 WPF 比普通的 WinForm 的流式布局

更容易控制,而且比 WinForm 美觀一些,顯示截圖:

02、Windows Phone 套接字(Socket)實戰之伺服器端設計

一、頁面 UI

      MainWindow.xaml 檔案中布局的 XAML:

<Grid ShowGridLines="True">
        <Grid.Resources>
            <Style TargetType="Button">
                <Setter Property="Width" Value="110"/>
                <Setter Property="Height" Value="30"/>
                <Setter Property="Margin" Value="10,0,10,0"/>
            </Style>
        </Grid.Resources>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="80*"/>
            <ColumnDefinition Width="20*"/>
        </Grid.ColumnDefinitions>
        <Grid ShowGridLines="True">
            <Grid.RowDefinitions>
                <RowDefinition Height="1*"/>
                <RowDefinition Height="7*"/>
                <RowDefinition Height="2*"/>
            </Grid.RowDefinitions>
            <!--本地 IP-->
            <StackPanel Orientation="Horizontal">
                <StackPanel.Resources>
                    <Style TargetType="TextBlock">
                        <Setter Property="Width" Value="120"/>
                        <Setter Property="VerticalAlignment" Value="Center"/>
                        <Setter Property="HorizontalAlignment" Value="Center"/>
                    </Style>
                </StackPanel.Resources>
                <!--伺服器端的 IP 和端口号-->
                <TextBlock x:Name="txtIP" Margin="4,14,0,14" Width="144"/>
                <TextBlock x:Name="txtPort" Text="5000" Margin="10, 0, 0, 0"/>
                <Button x:Name="btnBeginListening" Content="開始偵聽" HorizontalAlignment="Right" Click="btnBeginListening_Click"/>
                <!--<Button x:Name="btnStop" Content="停止" HorizontalAlignment="Right" Click="btnStop_Click"/>-->
            </StackPanel>

            <!--消息視窗和消息發送視窗-->
            <Grid Grid.Row="1">
                <Grid.RowDefinitions>
                    <RowDefinition Height="8*"/>
                    <RowDefinition Height="2*"/>
                </Grid.RowDefinitions>
                <ScrollViewer x:Name="scroll">
                    <!--顯示連接配接狀态、聊天消息等-->
                    <TextBlock x:Name="txtResult"  TextWrapping="Wrap"/>
                </ScrollViewer>
                <!--聊天文字輸入框-->
                <TextBox x:Name="txtInput" Grid.Row="1" KeyDown="txtInput_KeyDown" TextWrapping="Wrap"/>
            </Grid>

            <!--操作按鈕-->
            <Grid Grid.Row="2">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="5*"/>
                    <ColumnDefinition Width="5*"/>
                </Grid.ColumnDefinitions>
                <StackPanel Orientation="Horizontal">
                    <!--使用者選擇的檔案的路徑-->
                    <TextBox x:Name="txtFilePath" Width="150" Height="40"/>
                    <Button Content="選擇檔案" x:Name="btnChooseFile" Click="btnChooseFile_Click"/>
                </StackPanel>

                <StackPanel HorizontalAlignment="Right" Orientation="Horizontal" Grid.Column="1">
                    <!--發送檔案或者發送文字消息-->
                    <Button Content="發送檔案" x:Name="btnSendFile" Click="btnSendFile_Click"/>
                    <Button Content="發送消息" x:Name="btnSendMsg" Click="btnSendMsg_Click"/>
                </StackPanel>
            </Grid>
        </Grid>

        <!--顯示請求連接配接到伺服器端的 WP 端的 IP 資訊-->
        <ListBox Grid.Column="1" x:Name="listbox">
        </ListBox>
    </Grid>
           

二、定義一個伺服器端的 SocketClient 類,用來封裝與 WP 端通訊所使用的 Socket

      雖然 WP 和 PC 端都是使用 C# 語言開發的,但是 WP 端和 PC 端的 Socket 類在使用方法上有一些差異,

并且 WP 端明顯是經過精簡過的,這裡就不貼出來了。直接貼出 SocketClient 類:

namespace DesktopSocketServerDemo
{
    /// <summary>
    /// 與用戶端的 連接配接通信類(包含了一個 與用戶端 通信的 套接字,和線程)
    /// </summary>
    public class SocketClient
    {
        Socket sokMsg;
        Thread threadMsg;

        // 通信操作完成時調用,用來觸發在操作完成時,在宿首頁面顯示消息提示
        public event EventHandler<string> Completed;

        public SocketClient(Socket sokMsg)
        {
            this.sokMsg = sokMsg;

            this.threadMsg = new Thread(ReceiveMsg);
            this.threadMsg.IsBackground = true;
            this.threadMsg.Start();
        }

        bool isRec = true;
        // 負責監聽用戶端發送來的消息
        void ReceiveMsg()
        {
            while (isRec)
            {
                try
                {
                    byte[] byteSrc = new byte[CommonHelper.FileLength];

                    // 從綁定的 Socket 套接字接收資料,将資料存入接收緩沖區。
                    int length = sokMsg.Receive(byteSrc);

                    DataType dataType;
                    byte[] bytesFile;
                    string strMsg;

                    // 轉換源 byte[] 資料内容,擷取其中的 head 和 body 的實際内容
                    CommonHelper.ConvertByteToData(byteSrc, out dataType, out bytesFile, out strMsg);

                    if (dataType.IsFile == true)
                    {
                        // 如果 body 的類型檔案,則直接将檔案存儲到 PC 端的 D: 盤根路徑下
                        using (FileStream file = new FileStream("d:\\" + dataType.FileName + dataType.Exten, FileMode.OpenOrCreate))
                        {
                            file.Write(bytesFile, 0, bytesFile.Length);
                        }

                        // 顯示回調消息
                        OnCompleted("用戶端發送的檔案:" + dataType.FileName + dataType.Exten);
                        OnCompleted("該檔案儲存在 D: 盤的根目錄下");
                    }
                    else
                    {
                        OnCompleted(">>用戶端:" + strMsg);
                    }

                }
                catch (Exception ex)
                {
                    // 如果抛異常,則釋放該 Socket 對象所占用的資源
                    CloseConnection();

                    OnCompleted("SocketClient.ReceiveMsg() :" + ex.Message);
                }
            }
        }

        /// <summary>   
        ///  向用戶端發送消息   
        /// </summary>
        /// <param name="strMsg"></param>
        public void Send(string strMsg)
        {
            byte[] arrMsgFinal = CommonHelper.ConvertDataToByte(new DataType { IsFile = false }, null, strMsg);

            sokMsg.Send(arrMsgFinal);
        }

        // 向用戶端發送檔案資料
        public void SendFile(string strPath)
        {
            try
            {
                byte[] arrFileFina = CommonHelper.ConvertDataToByte(new DataType { IsFile = true }, strPath, null);

                //發送檔案資料
                sokMsg.Send(arrFileFina);//, 0, length + 1, SocketFlags.None);
                OnCompleted("發送檔案完成");
            }
            catch (Exception ex)
            {
                OnCompleted(ex.Message);
            }
        }

        // 關閉與用戶端連接配接
        public void CloseConnection()
        {
            isRec = false;
            sokMsg.Close();
            sokMsg.Dispose();
        }


        void OnCompleted(string strMsg)
        {
            if (Completed != null)
            {
                Completed(null, strMsg);
            }
        }

    }
}
           

三、在 MainWindow.xaml 檔案中,定義偵聽用戶端連接配接的 Socket

      MainWindow.xaml 檔案相應的 Codebehind 代碼:

namespace DesktopSocketServerDemo
{
    /// <summary>
    /// PC 端伺服器,用來接收連接配接 PC 的 Socket 連接配接,并建立 SocketClient 類與用戶端通信
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            //MultiCast cast = new MultiCast();
            //cast.Startup();

            txtIP.Text += "本地 IP:" + strIP;
            txtPort.Text = " 端口:" + Port;
        }

        int Port = 5000;
        // 伺服器端的 IP 位址
        string strIP = CommonHelper.GetIPAddress();

        //負責監聽 用戶端 的連接配接請求
        Socket socketWatch = null;

        // 執行 socketWatch 對象監聽請求的線程
        Thread threadWatch = null; 

        // 開啟監聽
        private void btnBeginListening_Click(object sender, RoutedEventArgs e)
        {
            if (socketWatch == null)
            {
                //執行個體化 套接字 (ip4尋址協定,流式傳輸,TCP協定)
                socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                IPEndPoint endpoint = new IPEndPoint(IPAddress.Parse(strIP), Port);

                //将 監聽套接字  綁定到 對應的IP和端口
                socketWatch.Bind(endpoint);

                //設定 監聽隊列 長度為10(同時能夠處理 10個連接配接請求)
                socketWatch.Listen(10);
                threadWatch = new Thread(StartWatch);
                threadWatch.IsBackground = true;
                threadWatch.Start();
                ShowMsg("啟動伺服器成功......\r\n");
            }
            else
            {
                ShowMsg("伺服器已經啟動");
            }
        }

        // 循環偵聽用戶端的連接配接請求
        bool isWatch = true;

        // 負責與用戶端通信的對象
        SocketClient socketClient;

        /// <summary>
        /// 被線程調用 監聽連接配接端口
        /// </summary>
        void StartWatch()
        {
            while (isWatch)
            {
                try
                {
                    //監聽 用戶端 連接配接請求,但是,Accept會阻斷目前線程
                    //監聽到請求,立即建立負責與該用戶端套接字通信的套接字
                    Socket socket = socketWatch.Accept();

                    if (socketClient != null)
                        socketClient.CloseConnection();

                    socketClient = new SocketClient(socket);
                    socketClient.Completed += connection_Completed;

                    this.Dispatcher.BeginInvoke(new Action(delegate
                    {
                        if (socket != null && socket.RemoteEndPoint != null)
                            //将 通信套接字的 用戶端IP端口儲存在下拉框裡
                            listbox.Items.Add(new TextBlock { Text = socket.RemoteEndPoint.ToString() });

                        ShowMsg("接收一個用戶端連接配接......");
                    }), null);
                }
                catch (Exception ex)
                {
                    ShowMsg(ex.Message);
                    socketWatch.Close();
                    socketWatch.Dispose();
                    isWatch = false;
                }

            }
        }
                
        //發送消息到已經連接配接到 PC 的用戶端
        private void btnSendMsg_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrEmpty(txtInput.Text))
            {
                ShowMsg("輸入内容不能為空");
                return;
            }

            if (socketClient != null)
            {
                ShowMsg(txtInput.Text.Trim());

                // 發送文字内容
                socketClient.Send(txtInput.Text.Trim());

                txtInput.Text = "";
            }
            else
            {
                MessageBox.Show("還沒有建立連接配接");
            }
        }

        //選擇要發送的檔案
        private void btnChooseFile_Click(object sender, EventArgs e)
        {
            Microsoft.Win32.OpenFileDialog openFileDialog = new Microsoft.Win32.OpenFileDialog();
            if (openFileDialog.ShowDialog() == true)
            {
                txtFilePath.Text = openFileDialog.FileName;
            }
        }

        //發送檔案
        private void btnSendFile_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrWhiteSpace(txtFilePath.Text))
            {
                MessageBox.Show("請先選擇一個檔案");
                return;
            }

            //connection = new SocketClient();

            if (socketClient != null)
            {
                // 發送檔案内容
                socketClient.SendFile(txtFilePath.Text.Trim());
            }
            else
            {
                MessageBox.Show("還沒有建立連接配接");
            }
        }

        // 操作完成
        void connection_Completed(object sender, string e)
        {
            ShowMsg(e);
        }
        
        // 向視窗追加消息
        void ShowMsg(string strMsg)
        {
            this.Dispatcher.BeginInvoke(new Action(delegate
               {
                   txtResult.Text += Environment.NewLine + strMsg + Environment.NewLine;
                   scroll.ScrollToBottom();
               }), null);
        }

        // 當伺服器關閉時觸發
        protected override void OnClosed(EventArgs e)
        {
            if (socketWatch != null)
            {
                socketWatch.Close();
                socketWatch.Dispose();
            }
            base.OnClosed(e);
        }

        // 按“Enter鍵”發送消息
        private void txtInput_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                btnSendMsg_Click(null, null);
            }
        }

        // 釋放連接配接資源
        //private void btnStop_Click(object sender, RoutedEventArgs e)
        //{
        //    if (socketClient != null)
        //    {
        //        socketClient.CloseConnection();
        //    }

        //    if (sokWatch != null)
        //    {
        //        sokWatch.Close();
        //        sokWatch.Dispose();
        //    }
        //}
    }
}
           

繼續閱讀