基於TCP協議的通信,估計大伙兒都不陌生的,以前玩.net或玩C++的時候應該玩得很多吧。現在老周簡單介紹一下在RT中如何用。
TCP是基於連接的,所以,肯定有一方是監聽者,通常稱服務端或服務器,它負責接受連接請求,但不負責通信;接受連接后得到一個專用於通信的套接字。
1、new一個StreamSocketListener,它用於監聽TCP連接。
2、處理StreamSocketListener實例的ConnectionReceived事件,當有新連接傳入,會發生該事件,並可以獲得用於通信的socket。
3、綁定本地結點。BindEndpointAsync綁定特定本機地址和端口(或服務,如果是藍牙通信,就是服務名,大多數情況下是端口號)。BindServiceNameAsync方法綁定本地端號或服務,該方法不指定地址,即綁定本機所有地址,如果有需要,你可以指定綁定到哪張網卡。如果所指定的端口是空白字符串("",不能為null),就會自動選擇一個隨機端口進行綁定。要是綁定的是本地的隨機端口,你可以從StreamSocketListener.Information的LocalPort屬性中獲取已綁定的端口。
4、在StreamSocketListener.ConnectionReceived事件的處理中,訪問事件參數的Socket屬性得到一個StreamSocket實例,然后你就可以用它來進行通信了。
5、當不需要時調用Dispose方法即可釋放。
下面來練習一下。老周發現一個現象,UWP兩個應用程序在同一台機器上不能連接,要用兩台機器來測試,但在同一個應用中就可以本地測試。
不過,后來想想,其實也無妨,畢竟UWP是通用應用,如果服務器一個應用,客戶一個應用,這樣反而不合理了,因為這樣用戶就要安裝兩個應用,在通用平台而言不太好,把服務器和客戶端都放在同一個應用中較好,讓用戶自行選擇是作為服務器端還是客戶端來運行。如果用戶選擇當前應用作為服務器,就開啟監聽;如果用戶選擇作為客戶端運行,就允許其輸入遠程設備的IP和端口進行連接。
下面代碼開啟連接監聽並綁定機地端口。
if (listener != null) { listener.ConnectionReceived -= OnConnReceived; listener.Dispose(); listener = null; } listener = new StreamSocketListener(); listener.ConnectionReceived += OnConnReceived; await listener.BindServiceNameAsync("");
調用BindServiceNameAsync時傳遞的是空字符串的參數,表示讓應用程序自動選擇一個隨機端口來監聽。為了讓客戶端知道該連接哪個端口,可以把本地監聽端口顯示在界面上。
runPort.Text = listener.Information.LocalPort;
處理ConnectionReceived事件,如果接收到連接請求,就向客戶端發送一條文本消息:“你好,我是你外公,我叫服務器。”。
private async void OnConnReceived(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args) { // 獲取用於通信的socket StreamSocket socket = args.Socket; // 向客戶端發送字符串: // 你好,我是你外公,我叫服務器。 using (DataWriter writer = new DataWriter(socket.OutputStream)) { string content = "你好,我是你外公,我叫服務器。"; writer.UnicodeEncoding = UnicodeEncoding.Utf8; //注意 // 計算長度 uint len = writer.MeasureString(content); // 寫入長度 writer.WriteUInt32(len); // 再寫內容 writer.WriteString(content); // 提交數據 await writer.StoreAsync(); } // 這個socket不要了,扔掉 socket.Dispose(); }
前一文章中,老周給大伙介紹過DataWriter的作用,這時我們用得上,用來把字符串寫入網絡流。注意,應該設置UnicodeEncoding屬性為Utf-8編碼,這個編碼比較通用,就不會出現亂碼。
由於字符串的長度是可變的,客戶端並不知道我們要發送的內容有多大,為了讓接收者能夠准確接收數據,應該先向流中寫入數據長度,然后再寫內容。接收方在讀的時候,可以先讀出長度,再讀內容,因為表示長度的值是uint,它的值大小是固定的4個字節。
下面代碼為客戶端發起連接。
StreamSocket socket = new StreamSocket(); try { HostName svname = new HostName(txtIp.Text); // 連接 await socket.ConnectAsync(svname, txtPort.Text); // 接收數據 DataReader reader = new DataReader(socket.InputStream); reader.UnicodeEncoding = UnicodeEncoding.Utf8; //注意 // 長度 await reader.LoadAsync(sizeof(uint)); uint len = reader.ReadUInt32(); // 讀內容 await reader.LoadAsync(len); string msg = reader.ReadString(reader.UnconsumedBufferLength); runRecMsg.Text = msg; // 釋放 reader.Dispose();
在讀取接收到的數據時,用的是DataReader類,而且記住要統一編碼utf-8,然后先加載4個字節,讀出內容長度,再加載剩余的字節,最后讀出字符串。
好,最后一步就是配置清單文件,打開清單文件,默認用設計器打開,切換到[功能]選項卡,勾選“Internet(客戶端與服務器)”與“專用網絡(客戶端與服務器)”,而“Internet(客戶端)”可以取消。
XML代碼如下。
<Capabilities> <Capability Name="internetClientServer" /> <Capability Name="privateNetworkClientServer" /> </Capabilities>
運行結果請看下面的艷圖。
啊,今天的話題就扯到這里吧,改天再扯其他話題。