Windows 8 Metro 關於StreamSocket與原異步Socket


前一篇 《Windows 8 Metro 關於 StreamSocket MSDN示例閱讀》我們基本懂得如何通過StreamSocket 做監聽、連接、發送接收數據。

同時前一篇留下的幾個疑問,我們在這里進行實驗和解答。

 

在“原有的異步Socket”連接方式與現在WIN8 Metro App 的StreamSocket 如何通信呢?

首先解釋下這里說的“原有的異步Socket” 是什么。

在await/async 關鍵字出現前,我們的Socket異步是依靠System.Net 以及 System.Net.Sockets 命名空間下的類與方法實現的。

請求連接BeginConnect/ConnectAsync 接受連接 BeginAccept/AcceptAsync 發送信息BeginSend/SendAsyn 收信息BeginReceive/ReceiveAsync

上面列舉的都是Socket類下的異步操作主要方法。這就是前面文章說的“原有的異步Socket”。

 

前一篇里還有兩個問題分別是

StreamSocket在原有的數據包格式如何讀取到數據?(沒有DataWriter.WriteUInt32(),DataReader.ReadUInt32();)

WIN8 Metro App 如何像之前的Silverlight一樣與服務器進行異步收發數據(問答數據)?

 

這幾個問題我想用一個相對簡單合適的示例來講解。

所以我們先來設定一下場景。

我現在面對的情況是,已經有使用原異步Socket的成熟服務器程序以及解決方案了。

這個解決方案可能會用於各種客戶端的連接,比如 FLASH客戶端 Silverlight客戶端 WINFORM客戶端 HTML5客戶端 等等。 

現在我們需要增加一個WIN8 metro APP 的客戶端。但是我們不希望重新開發服務器端的東西。

於是乎我們就有了WIN8 metro APP 與 原異步Socket 連接收發信息的需求。

 

首先是服務器方面的一些主要代碼:(如果您之前已經做了很多可以忽略這一部分)

首先建立一個WINFORM工程然后在默認的FORM1上添加一個LISTBOX 和一個 Button 控件

 

 

然后轉到后台代碼。

聲明一個Socket 以及一個存儲客戶端的LIST。 添加Button Click事件代碼:

button1_Click
 1         IList<Client> ClientList = new List<Client>();
 2         Socket ServerSoket;
 3         /// <summary>
 4         /// 開始監聽
 5         /// </summary>
 6         /// <param name="sender"></param>
 7         /// <param name="e"></param>
 8         private void button1_Click(object sender, EventArgs e)
 9         {
10             ServerSoket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
11             ServerSoket.Bind(new IPEndPoint(IPAddress.Any, 4518));//監聽端口為4518
12             ServerSoket.Listen(100);//100是相對穩定連接隊列數
13             ServerSoket.BeginAccept(new AsyncCallback(ClientConnectComplete), null);
14             OutPutMsg("服務器在端口4518開始監聽");
15         }

 

ClientConnectComplete 代碼:

ClientConnectComplete
 1         private void ClientConnectComplete(IAsyncResult async)
 2         {
 3             Client client = new Client();
 4 
 5             // 完成接受客戶端傳入的連接的這個異步操作,並返回客戶端連入的 socket
 6             try
 7             {
 8                 client.Socket = ServerSoket.EndAccept(async);
 9             }
10             catch (Exception)
11             {
12 
13                 return;
14             }
15 
16             //把連接進來的客戶端保存起來
17             ClientList.Add(client);
18 
19             OutPutMsg(client.Socket.RemoteEndPoint + " 連接成功!");
20 
21             try
22             {
23                 // 開始異步接收客戶端傳入的數據
24                 client.Socket.BeginReceive(client.Buffer, 0, client.Buffer.Length, SocketFlags.None, new AsyncCallback(ClientReceiveComplete), client);
25             }
26             catch (SocketException ex)
27             {
28                 // 處理異常
29             }
30 
31             ServerSoket.BeginAccept(new AsyncCallback(ClientConnectComplete), null);//繼續接受下一個連接
32         }

 

ClientReceiveComplete代碼:

ClientReceiveComplete
 1         private void ClientReceiveComplete(IAsyncResult async)
 2         {
 3             Client client = async.AsyncState as Client;
 4 
 5             int _receiveCount = 0;//用於統計本次接收到的字節數
 6 
 7             try
 8             {
 9                 // 完成接收數據的這個異步操作,並返回接收的字節數
10                 if (client.Socket.Connected)
11                 {
12                     _receiveCount = client.Socket.EndReceive(async);
13                 }
14                 else
15                 {
16                 }
17             }
18             catch (SocketException ex)
19             {
20                 // 處理異常
21             }
22             
23             char startChar = System.BitConverter.ToChar(client.Buffer, 0);//獲取到包頭
24             Int16 msgType = System.BitConverter.ToInt16(client.Buffer, 2);//獲取到消息類型
25             Int32 dataSize = System.BitConverter.ToInt32(client.Buffer, 4);//獲取到包長度
26             char endtChar = System.BitConverter.ToChar(client.Buffer, dataSize-2);//獲取到包尾
27             
28             //驗證數據包的正確性
29             if (startChar.Equals(Convert.ToChar(2)) && endtChar.Equals(Convert.ToChar(3)))
30             {
31                 //數據驗證成功輸出信息
32                 OutPutMsg(client.Socket.RemoteEndPoint + "數據驗證成功。");
33                 string receivedContent = UTF32Encoding.UTF8.GetString(client.Buffer,8, dataSize - 10);//獲取到數據內容
34                 OutPutMsg("接收到的數據內容是:" + receivedContent);
35 
36                 //服務器應答機制,制造回發給客戶端消息
37                 byte[] sendMsg = GetSendMsg(UTF32Encoding.UTF8.GetBytes("服務器發回數據:" + receivedContent),2);
38 
39                 try
40                 {
41                     //發送給所有客戶端
42                     foreach (var clientItem in ClientList)
43                     {
44                         clientItem.Socket.BeginSend(sendMsg, 0, sendMsg.Length, SocketFlags.None, new AsyncCallback(SendToClientComplete), clientItem);
45                     }
46                     
47                 }
48                 catch (Exception ex)
49                 {
50 
51                     OutPutMsg(ex.Message);
52                 }
53             }
54 
55             try
56             {
57                 //繼續開始接收客戶端傳入的數據
58                 if (client.Socket.Connected)
59                 {
60                     client.Socket.BeginReceive(client.Buffer, 0, client.Buffer.Length, 0, new AsyncCallback(ClientReceiveComplete), client);
61                 }
62             }
63             catch (SocketException ex)
64             {
65                 // 處理異常
66             }
67         }

 

這里數據接收以及驗證數據都是比較簡單粗略的,沒有考慮掉包拆包復雜的數據驗證等等。 

 

SendToClientComplete代碼:

SendToClientComplete
 1         private void SendToClientComplete(IAsyncResult async)
 2         {
 3             Client client = async.AsyncState as Client;
 4             try
 5             {
 6                 // 完成將信息發送到客戶端的這個異步操作
 7                 if (client.Socket.Connected)
 8                 {
 9                     client.Socket.EndSend(async);
10                 }
11             }
12             catch (SocketException ex)
13             {
14                 // 處理異常
15             }
16             OutPutMsg("服務器數據回發完畢。");
17         }

 

OutPutMsg代碼:

OutPutMsg
1        /// <summary>
2         /// 正常消息輸出
3         /// </summary>
4         /// <param name="msg"></param>
5         private void OutPutMsg(string msg)
6         {
7             this.BeginInvoke(new System.EventHandler(AddToList), msg);
8         }

 

AddToList代碼:

AddToList
 1  /// <summary>
 2         /// 添加到窗體列表顯示
 3         /// </summary>
 4         /// <param name="sender"></param>
 5         /// <param name="e"></param>
 6         private void AddToList(object sender, EventArgs e)
 7         {
 8             this.listBox1.Items.Add(sender.ToString());
 9             try
10             {
11                 this.listBox1.SelectedIndex = this.listBox1.Items.Count - 1;
12             }
13             catch (Exception)
14             {
15                 //throw;
16             }
17 
18             if (this.listBox1.Items.Count > 100)
19             {
20                 this.listBox1.Items.RemoveAt(0);
21             }
22         }

 

GetSendMsg代碼:

 1          /// <summary>
 2         /// 組裝消息包
 3         /// </summary>
 4         /// <param name="BArr">內容字節流</param>
 5         /// <param name="MsgType">消息類型</param>
 6         /// <returns></returns>
 7         private byte[] GetSendMsg(byte[] BArr, Int16 MsgType)
 8         {
 9             //數據包格式為 包頭 + 消息類型 + 包長度 + 消息內容 + 包尾
10             //開始字節流 包頭
11             byte[] bs = System.BitConverter.GetBytes(Convert.ToChar(2));
12             //結束字節流 包尾
13             byte[] be = System.BitConverter.GetBytes(Convert.ToChar(3));
14             //消息體類型
15             byte[] b1 = System.BitConverter.GetBytes(MsgType);
16 
17             //總長度
18             int size = BArr == null ? 10 : BArr.Length + 10;
19 
20             byte[] b2 = System.BitConverter.GetBytes(size);
21 
22 
23             byte[] b = new byte[size];
24 
25             Array.Copy(bs, 0, b, 0, bs.Length);
26             Array.Copy(b1, 0, b, 2, b1.Length);
27             Array.Copy(b2, 0, b, 4, b2.Length);
28             if (BArr != null)
29             {
30                 Array.Copy(BArr, 0, b, 8, BArr.Length);
31             }
32             Array.Copy(be, 0, b, size - 2, be.Length);
33 
34 
35             return b;
36 
37         }

 

這個方法需要講解下。前一篇博文我已經說到,做原異步socket通信時需要設計的數據包格式。

我的數據包格式 包頭 + 消息類型 + 包長度 + 消息內容 + 包尾

當然METRO APP 通信時也是需要設計數據包的,只是數據長度可以單獨出來。

 

class Client 代碼:

 

class Client
 1     public class Client
 2     {
 3         /// <summary>
 4         /// 客戶端 Socket
 5         /// </summary>
 6         public Socket Socket { get; set; }
 7 
 8         private byte[] _buffer;
 9         /// <summary>
10         /// 為該客戶端 Socket 開辟的緩沖區
11         /// </summary>
12         public byte[] Buffer
13         {
14             get
15             {
16                 if (_buffer == null)
17                     _buffer = new byte[102400];
18 
19                 return _buffer;
20             }
21         }
22     }

 

 

到這里簡單的原異步socket服務器就設計完畢了。

服務器做的事情:接收連接放入客戶端列表,接收客戶端發來的數據驗證處理並回發給所有客戶端。

 

現在我們看看WIN8 METRO APP 的 設計。

首先新建一個 Windows 應用商店 空應用程序。

使用Blend + SketchFlow Preview for Visual Studio 2012 打開工程。

 

在默認的MainPage.xaml 里添加 兩個 button,一個 TextBox, 一個ListBox 控件。

在第一個Button事件內我們寫入StreamSocket 的連接以及對已連接上的StreamSocket 實例進行消息監聽。

 

Button_Click_1代碼:

 

Button_Click_1
 1         StreamSocket sk = new StreamSocket();
 2         private async void Button_Click_1(object sender, Windows.UI.Xaml.RoutedEventArgs e)
 3         {
 4           
 5             await sk.ConnectAsync(new HostName("127.0.0.1"), "4518");
 6             await ShowMsg("發送連接完畢。");
 7 
 8             if (sk.Information!= null)
 9             {
10                 await BeginReceived();
11             }
12             
13         }

 

 

這段代碼我就不解釋了,看過前一篇的朋友應該都懂。

 

BeginReceived 代碼:

BeginReceived
       private async Task BeginReceived()
        {

            //綁定已連接的StreamSocket到DataReader
            DataReader reader = new DataReader(sk.InputStream);
            while (true)
            {
                try
                {
                    //嘗試從StreamSocket 讀取到數據 讀取2個字節的數據(包頭)
                    uint sizeFieldCount = await reader.LoadAsync(sizeof(UInt16));
                    if (sizeFieldCount != sizeof(UInt16))
                    {
                        return;
                    }
                }
                catch (Exception exception)
                {
                    return;
                }
                //臨時處理讀取器 的字節流數組
                byte[] tempByteArr;
                tempByteArr = new byte[2];

                //將剛才reader讀取到的數據填充到tempByteArr
                reader.ReadBytes(tempByteArr);

                char startChar = System.BitConverter.ToChar(tempByteArr, 0);//獲取到包頭
                //如果不是包頭
                if (!startChar.Equals(Convert.ToChar(2)))
                {
                    return;
                }

                //在讀取下2個字節
                await reader.LoadAsync(sizeof(UInt16));
                reader.ReadBytes(tempByteArr);
                Int16 msgType = System.BitConverter.ToInt16(tempByteArr,0);//獲取到消息類型
                await ShowMsg("獲取到的消息類型:" + msgType.ToString());

                tempByteArr = new byte[4];
                //在讀取下4個字節
                await reader.LoadAsync(sizeof(uint));
                reader.ReadBytes(tempByteArr);
                uint dataSize = System.BitConverter.ToUInt32(tempByteArr, 0);//獲取到包長度
                await ShowMsg("獲取到的數據包長度:" + dataSize.ToString());

                uint contentByteLenth = dataSize - 8;//內容字節流長度
                //根據長度讀取內容字節流
                uint actualStringLength = await reader.LoadAsync(contentByteLenth);
                if (contentByteLenth != actualStringLength)
                {
                   //內容數據流未發送完成
                    await ShowMsg("內容數據流未發送完成");
                    return;
                }

                //填充
                tempByteArr = new byte[contentByteLenth];
                reader.ReadBytes(tempByteArr);

                await ShowMsg(System.Text.UnicodeEncoding.UTF8.GetString(tempByteArr, 0, int.Parse(contentByteLenth.ToString())));
            }
        }

 

 

BeginReceived方法內我們用一個 DataReader 綁定已經連接上的StreamSocket 實例的InputStream

然后我們利用循環進行監聽,監聽時需要進行初步的數據讀取以便查看是否有數據到達。

 

我講解一下BeginReceived內的代碼。因為這是這篇博文比較重要的部分。

代碼:

                try
                {
                    //嘗試從StreamSocket 讀取到數據 讀取2個字節的數據(包頭)
                    uint sizeFieldCount = await reader.LoadAsync(sizeof(UInt16));
                    if (sizeFieldCount != sizeof(UInt16))
                    {
                        return;
                    }
                }
                catch (Exception exception)
                {
                    return;
                }

            

這里要說下為什么要讀取 2個字節的數據長度來做驗證呢?

前面的服務器設計里我們數據包的格式包頭就是2個字節。

DataReader.LoadAsync() 方法的機制是:

從StreamSocket的InputStream讀取一定長度的數據字節數到DataReader內,且讀取完畢后InputStream內被讀取到的數據將被刪除。

所以我這里用讀取2個字節的包頭來做是否有數據的判斷。

 

接下來的代碼:

 1                 //臨時處理讀取器 的字節流數組
 2                 byte[] tempByteArr;
 3                 tempByteArr = new byte[2];
 4 
 5                 //將剛才reader讀取到的數據填充到tempByteArr
 6                 reader.ReadBytes(tempByteArr);
 7 
 8                 char startChar = System.BitConverter.ToChar(tempByteArr, 0);//獲取到包頭
 9                 //如果不是包頭
10                 if (!startChar.Equals(Convert.ToChar(2)))
11                 {
12                     return;
13                 }

定義一個臨時的字節數組 tempByteArr 且數組大小與需要讀取到的數據長度一樣。

DataReader.ReadBytes()方法是根據傳入的字節數組長度讀取數據的。

所以接下來的代碼:

 1                 //再讀取下2個字節
 2                 await reader.LoadAsync(sizeof(UInt16));
 3                 reader.ReadBytes(tempByteArr);
 4                 Int16 msgType = System.BitConverter.ToInt16(tempByteArr,0);//獲取到消息類型
 5                 await ShowMsg("獲取到的消息類型:" + msgType.ToString());
 6 
 7                 tempByteArr = new byte[4];
 8                 //再讀取下4個字節
 9                 await reader.LoadAsync(sizeof(uint));
10                 reader.ReadBytes(tempByteArr);
11                 uint dataSize = System.BitConverter.ToUInt32(tempByteArr, 0);//獲取到包長度
12                 await ShowMsg("獲取到的數據包長度:" + dataSize.ToString());
13 
14                 uint contentByteLenth = dataSize - 8;//內容字節流長度
15                 //根據長度讀取內容字節流
16                 uint actualStringLength = await reader.LoadAsync(contentByteLenth);
17                 if (contentByteLenth != actualStringLength)
18                 {
19                    //內容數據流未發送完成
20                     await ShowMsg("內容數據流未發送完成");
21                     return;
22                 }
23 
24                 //填充
25                 tempByteArr = new byte[contentByteLenth];
26                 reader.ReadBytes(tempByteArr);
27 
28                 await ShowMsg(System.Text.UnicodeEncoding.UTF8.GetString(tempByteArr, 0, int.Parse(contentByteLenth.ToString())));

 

我們讀取到包長度后就能順利的得到我們的消息內容了。

 

 

接下來的Button2 發送信息 代碼:

Button_Click_2
 1         DataWriter writer;
 2         private async void Button_Click_2(object sender, Windows.UI.Xaml.RoutedEventArgs e)
 3         {
 4             if (this.SendText.Text != string.Empty)
 5             {
 6                 if (writer == null)
 7                 {
 8                     writer = new DataWriter(sk.OutputStream);
 9                 }
10 
11                 //需要發送的數據
12                 byte[] sendMsg = GetSendMsg(System.Text.UnicodeEncoding.UTF8.GetBytes(this.SendText.Text), 1);
13 
14                 //把數據寫入到發送流
15                 writer.WriteBytes(sendMsg);
16 
17                 //異步發送
18                 await writer.StoreAsync();
19 
20             }
21         }

 

ShowMsg GetSendMsg 代碼:

ShowMsg GetSendMsg
 1        protected async Task ShowMsg(string s)
 2         {
 3             MsgList.Items.Insert(0, s);
 4         }
 5 
 6         /// <summary>
 7         /// 組裝消息包
 8         /// </summary>
 9         /// <param name="BArr">內容字節流</param>
10         /// <param name="MsgType">消息類型</param>
11         /// <returns></returns>
12         private byte[] GetSendMsg(byte[] BArr, Int16 MsgType)
13         {
14             //數據包格式為 包頭 + 消息類型 + 包長度 + 消息內容 + 包尾
15             //開始字節流 包頭
16             byte[] bs = System.BitConverter.GetBytes(Convert.ToChar(2));
17             //結束字節流 包尾
18             byte[] be = System.BitConverter.GetBytes(Convert.ToChar(3));
19             //消息體類型
20             byte[] b1 = System.BitConverter.GetBytes(MsgType);
21 
22             //總長度
23             int size = BArr == null ? 10 : BArr.Length + 10;
24 
25             byte[] b2 = System.BitConverter.GetBytes(size);
26 
27 
28             byte[] b = new byte[size];
29 
30             Array.Copy(bs, 0, b, 0, bs.Length);
31             Array.Copy(b1, 0, b, 2, b1.Length);
32             Array.Copy(b2, 0, b, 4, b2.Length);
33             if (BArr != null)
34             {
35                 Array.Copy(BArr, 0, b, 8, BArr.Length);
36             }
37             Array.Copy(be, 0, b, size - 2, be.Length);
38 
39 
40             return b;
41 
42         }

 

到這里基本解答了

在“原有的異步Socket”連接方式與現在WIN8 Metro App 的StreamSocket 如何通信呢?

 

StreamSocket在原有的數據包格式如何讀取到數據?(沒有DataWriter.WriteUInt32(),DataReader.ReadUInt32();)

 

WIN8 Metro App 如何像之前的Silverlight一樣與服務器進行異步收發數據(問答數據)?

 

最后給上 示例的源碼:點擊這里下載

 

國際慣例如果您看得起鄙人,轉載請注明出處謝謝。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM