簡介:
C#網絡編程API包含在System.Net和System.Net.Sockets命名空間下,大部分網絡操作都可以在其中找到相應的類來實現;包括Socket的創建和連接,網絡流收發方法的封裝,而且還封裝了服務端類和客戶端類,提供創建服務端和客戶端的快速通道;
(一)Socket類
Socket類在System.Net.Sockets命名空間下,是最基本的網絡操作類,其中封裝了網絡連接的創建和關閉,數據的收發,以及網絡狀態監控等一系列有用的功能;
示例(TCP):
using System; using System.Net; using System.Net.Sockets; using System.Text; class Test_Tcp {
private Socket socket; private void Server() { var server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // 綁定服務端IP和端口,客戶端通過這個地址連入
server.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9356)); // 開啟服務監聽,參數為最大掛起隊列的連接數(並發),已連接的不計
server.Listen(1); // 這是一個阻塞方法,接收客戶端接入,返回客戶端連接Socket
socket = server.Accept(); Console.WriteLine("Local : {0}\nRemote : {1}", socket.LocalEndPoint.ToString(), socket.RemoteEndPoint.ToString()); // 數據接收:這是一個異步過程
ReceiveAsync(); // 數據發送:這是一個阻塞方法
Write(); // 關閉客戶端連接Socket和服務Socket
socket.Close(); server.Close(); } }
Server()是一個簡易的基於TCP連接的服務端開啟方法,使用這個方法需要用到System.Net.Sockets和System.Net兩個命名空間;
服務端開啟分為4步:創建Socket、綁定IP和端口Bind()、開啟監聽Listen()、接入客戶端Accept();
其中還有兩個自定義方法,ReceiveAsync()和Write(),它們分別是異步數據接收、數據發送,它們的定義在后面可以看到。
另外,Socket的Accept()接入客戶端連接方法也有異步版本BeginAccept(),它的用法類似於后面的BeginReceive(),多客戶端系統一般都是用這種異步接入方式,在BeginAccept方法的回調方法中,維護一個客戶端連接容器;
class Test_Tcp { private void Client() { socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Connect("127.0.0.1", 9356); Console.WriteLine("{0}\n{1}", socket.LocalEndPoint.ToString(), socket.RemoteEndPoint.ToString()); ReceiveAsync(); Write(); socket.Close(); } }
Client()方法是相應的客戶端開啟方法,相對服務端來說代碼更簡單明了,而且不需綁定IP,端口也是自動分配,不過需注意Connect()方法中的IP和端口是服務端綁定的服務地址;ReceiveAysnc()、Write()和服務端中的兩個方法是一樣的;
using System.Text; class Test_Tcp { private const int BuffSize = 1024; private void ReceiveAsync() { if (socket == null || !socket.Connected) return; var Buff = new byte[BuffSize]; socket.BeginReceive(Buff, 0, BuffSize, SocketFlags.None, OnReceived, Buff); } private void OnReceived(IAsyncResult result) { if (socket == null || !socket.Connected) return; byte[] data = (byte[])result.AsyncState; int recLength = socket.EndReceive(result); Console.WriteLine("Length = {0}", recLength); if (recLength <= 0) return; ReceiveAsync(); string msg = Encoding.Default.GetString(data, 0, recLength); Console.WriteLine("Receive : {0}\n", msg); } private void Write() { while (socket != null && socket.Connected) { string msg = Console.ReadLine(); if (msg == "exit") break; byte[] buff = Encoding.Default.GetBytes(msg); socket.Send(buff); } } }
這3個方法是數據異步接收、數據接收回調、和聊天數據發送的方法,這里的字符串 - 字節序列轉換,需要用到System.Text命名空間;
ReceiveAysnc()是開啟異步數據接收方法,其中Socket.BeginReceive方法是Socket類中的異步接收方法,它需要一個字節緩沖區和一個回調方法作為參數;
OnRecieve()數據接收回調方法,在這個方法中,主要工作是開啟新的異步接收方法RecieveAsync(),以及數據處理;這里的數據處理只是簡單地轉換為字符串,並打印到控制台,而一般在實際應用中,這里就收的數據data會用一個容器儲存起來(一般是隊列Queue),然后在其它地方從容器中取出數據,並進行復雜的處理;
Write()方法:在網絡連接可用狀態下,不斷從控制台等待讀取一行字符串,並將其轉換為字節序列發送到socket,服務器在異步接收線程中會接收到數據,並觸發回調方法OnRecieve(),控制台會看到打印的字符串;輸入exit終結循環,Write()方法返回,接着關閉socket;
Socket.Send發送數據方法是一個阻塞方法,它同樣也有異步版本BeginSend(),用法和BeginReceive()類似;
在實際項目應用中,數據的發送和接收會分別維護一個發送隊列和接收隊列,這樣,應用層在調用數據發送方法時,只是把數據加入到發送隊列,而不用等待發送完成,特別是數據量大的時候,等待發送的時間會影響到應用層性能;真正的數據發送用一個專門的線程不斷從發送隊列里面取數據並發送;數據接收也是類似維護一個接收隊列。
程序入口:
class Test_Tcp { public void Run() { Console.WriteLine("Input 'c' or 's' :"); var key = Console.ReadKey(); Console.WriteLine(); if (key.Key == ConsoleKey.C) { Client(); } else if (key.Key == ConsoleKey.S) { Server(); } } } class Program { static int Main(string[] args) { new Test_Tcp().Run(); return 0; } }
(二)NetworkStream網絡流
完全限定名System.Net.Sockets.NetworkStream網絡流類,繼承於Stream類,簡單地理解就是對Socket讀寫網絡數據的封裝,用NetworkStream網絡流封裝Socket,可以簡化數據的接收和發送;
NetworkStream的構造方法需要傳入一個可用的網絡Socket實例,然后就可以用流的方式替代Socket進行數據的讀寫;
var netStream = new NetworkStream(socket);
這里的socket必須網絡連接成功(服務端連入客戶端,客戶端連接遠程成功),才能用NetworkStream進行數據流的讀寫;
(三)UDP和心跳包
相比於TCP的可靠傳輸,UDP是一種非連接、不可靠的數據傳輸協議,它不需要建立連接,也就是說,服務端不需要監聽Listen、接受連入Accept,而且Socket.Connected也不能用;Udp發送數據不需確定對方是否存在,網路是否可通,當然也無法確定對方是否收到(但可以手動發送返回包來通知對方),但是Udp相對Tcp的消耗也小;
Socket的構造方法第二、三個參數分別要設置為SocketType.Dgram、ProtocolType.Udp;
相比於TCP連接,UDP的客戶端差別不大,但是在UDP服務端,由於沒有客戶端連接,數據的發送應該使用SendTo,這個方法要求傳入一個客戶端地址結構EndPoint表示目標終端,那么,在接收數據時,就應該保存好數據的來源地址;
那么,接收數據也應該用另一個版本ReceiveFrom,這個方法可以得到一個數據來源地址EndPoint,這時就可以保存要用到的EndPoint,這個EndPoint可以用一個容器來維護;
這兩個方法在可靠連接Tcp中也可以用(但一般不這么用),另外它們都也有各自的異步版本,Begin開頭的便是;
心跳包,顧名思義是一種在通信雙方,定時發送一個特定的數據序列,一般3-10s,用來通知對方網絡通信是正常的,一般這個數據序列短小、固定的;心跳包在Udp非連接協議中非常地必要,因為Udp沒辦法在沒有數據接收的情況下確定網絡狀態;在Tcp中心跳包不是必要的,但是要求較高的項目中依然會應用心跳包;心跳超時(一般大於2倍的間隔時間),就表示網絡通信失聯;