簡(jiǎn)介: C#網(wǎng)絡(luò)編程API包含在System.Net和System.Net.Sockets命名空間下,大部分網(wǎng)絡(luò)操作都可以在其中找到相應(yīng)的類來實(shí)現(xiàn);包括Socket的創(chuàng)建和連接,網(wǎng)絡(luò)流收發(fā)方法的封裝,而且還封裝了服務(wù)端類和客戶端類,提供創(chuàng)建服務(wù)端和客戶端的快速通道; (一)Socket類 Socket類在System.Net.Sockets命名空間下,是最基本的網(wǎng)絡(luò)操作類,其中封裝了網(wǎng)絡(luò)連接的創(chuàng)建和關(guān)閉,數(shù)據(jù)的收發(fā),以及網(wǎng)絡(luò)狀態(tài)監(jiān)控等一系列有用的功能; 示例(TCP): using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
class Test_Tcp
{
Server()是一個(gè)簡(jiǎn)易的基于TCP連接的服務(wù)端開啟方法,使用這個(gè)方法需要用到System.Net.Sockets和System.Net兩個(gè)命名空間; 服務(wù)端開啟分為4步:創(chuàng)建Socket、綁定IP和端口Bind()、開啟監(jiān)聽Listen()、接入客戶端Accept(); 其中還有兩個(gè)自定義方法,ReceiveAsync()和Write(),它們分別是異步數(shù)據(jù)接收、數(shù)據(jù)發(fā)送,它們的定義在后面可以看到。 另外,Socket的Accept()接入客戶端連接方法也有異步版本BeginAccept(),它的用法類似于后面的BeginReceive(),多客戶端系統(tǒng)一般都是用這種異步接入方式,在BeginAccept方法的回調(diào)方法中,維護(hù)一個(gè)客戶端連接容器; 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()方法是相應(yīng)的客戶端開啟方法,相對(duì)服務(wù)端來說代碼更簡(jiǎn)單明了,而且不需綁定IP,端口也是自動(dòng)分配,不過需注意Connect()方法中的IP和端口是服務(wù)端綁定的服務(wù)地址;ReceiveAysnc()、Write()和服務(wù)端中的兩個(gè)方法是一樣的; 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個(gè)方法是數(shù)據(jù)異步接收、數(shù)據(jù)接收回調(diào)、和聊天數(shù)據(jù)發(fā)送的方法,這里的字符串 - 字節(jié)序列轉(zhuǎn)換,需要用到System.Text命名空間; ReceiveAysnc()是開啟異步數(shù)據(jù)接收方法,其中Socket.BeginReceive方法是Socket類中的異步接收方法,它需要一個(gè)字節(jié)緩沖區(qū)和一個(gè)回調(diào)方法作為參數(shù); OnRecieve()數(shù)據(jù)接收回調(diào)方法,在這個(gè)方法中,主要工作是開啟新的異步接收方法RecieveAsync(),以及數(shù)據(jù)處理;這里的數(shù)據(jù)處理只是簡(jiǎn)單地轉(zhuǎn)換為字符串,并打印到控制臺(tái),而一般在實(shí)際應(yīng)用中,這里就收的數(shù)據(jù)data會(huì)用一個(gè)容器儲(chǔ)存起來(一般是隊(duì)列Queue),然后在其它地方從容器中取出數(shù)據(jù),并進(jìn)行復(fù)雜的處理; Write()方法:在網(wǎng)絡(luò)連接可用狀態(tài)下,不斷從控制臺(tái)等待讀取一行字符串,并將其轉(zhuǎn)換為字節(jié)序列發(fā)送到socket,服務(wù)器在異步接收線程中會(huì)接收到數(shù)據(jù),并觸發(fā)回調(diào)方法OnRecieve(),控制臺(tái)會(huì)看到打印的字符串;輸入exit終結(jié)循環(huán),Write()方法返回,接著關(guān)閉socket; Socket.Send發(fā)送數(shù)據(jù)方法是一個(gè)阻塞方法,它同樣也有異步版本BeginSend(),用法和BeginReceive()類似; 在實(shí)際項(xiàng)目應(yīng)用中,數(shù)據(jù)的發(fā)送和接收會(huì)分別維護(hù)一個(gè)發(fā)送隊(duì)列和接收隊(duì)列,這樣,應(yīng)用層在調(diào)用數(shù)據(jù)發(fā)送方法時(shí),只是把數(shù)據(jù)加入到發(fā)送隊(duì)列,而不用等待發(fā)送完成,特別是數(shù)據(jù)量大的時(shí)候,等待發(fā)送的時(shí)間會(huì)影響到應(yīng)用層性能;真正的數(shù)據(jù)發(fā)送用一個(gè)專門的線程不斷從發(fā)送隊(duì)列里面取數(shù)據(jù)并發(fā)送;數(shù)據(jù)接收也是類似維護(hù)一個(gè)接收隊(duì)列。 程序入口: 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網(wǎng)絡(luò)流 完全限定名System.Net.Sockets.NetworkStream網(wǎng)絡(luò)流類,繼承于Stream類,簡(jiǎn)單地理解就是對(duì)Socket讀寫網(wǎng)絡(luò)數(shù)據(jù)的封裝,用NetworkStream網(wǎng)絡(luò)流封裝Socket,可以簡(jiǎn)化數(shù)據(jù)的接收和發(fā)送; NetworkStream的構(gòu)造方法需要傳入一個(gè)可用的網(wǎng)絡(luò)Socket實(shí)例,然后就可以用流的方式替代Socket進(jìn)行數(shù)據(jù)的讀寫; var netStream = new NetworkStream(socket); 這里的socket必須網(wǎng)絡(luò)連接成功(服務(wù)端連入客戶端,客戶端連接遠(yuǎn)程成功),才能用NetworkStream進(jìn)行數(shù)據(jù)流的讀寫; (三)UDP和心跳包 相比于TCP的可靠傳輸,UDP是一種非連接、不可靠的數(shù)據(jù)傳輸協(xié)議,它不需要建立連接,也就是說,服務(wù)端不需要監(jiān)聽Listen、接受連入Accept,而且Socket.Connected也不能用;Udp發(fā)送數(shù)據(jù)不需確定對(duì)方是否存在,網(wǎng)路是否可通,當(dāng)然也無法確定對(duì)方是否收到(但可以手動(dòng)發(fā)送返回包來通知對(duì)方),但是Udp相對(duì)Tcp的消耗也??; Socket的構(gòu)造方法第二、三個(gè)參數(shù)分別要設(shè)置為SocketType.Dgram、ProtocolType.Udp; 相比于TCP連接,UDP的客戶端差別不大,但是在UDP服務(wù)端,由于沒有客戶端連接,數(shù)據(jù)的發(fā)送應(yīng)該使用SendTo,這個(gè)方法要求傳入一個(gè)客戶端地址結(jié)構(gòu)EndPoint表示目標(biāo)終端,那么,在接收數(shù)據(jù)時(shí),就應(yīng)該保存好數(shù)據(jù)的來源地址; 那么,接收數(shù)據(jù)也應(yīng)該用另一個(gè)版本ReceiveFrom,這個(gè)方法可以得到一個(gè)數(shù)據(jù)來源地址EndPoint,這時(shí)就可以保存要用到的EndPoint,這個(gè)EndPoint可以用一個(gè)容器來維護(hù); 這兩個(gè)方法在可靠連接Tcp中也可以用(但一般不這么用),另外它們都也有各自的異步版本,Begin開頭的便是; 心跳包,顧名思義是一種在通信雙方,定時(shí)發(fā)送一個(gè)特定的數(shù)據(jù)序列,一般3-10s,用來通知對(duì)方網(wǎng)絡(luò)通信是正常的,一般這個(gè)數(shù)據(jù)序列短小、固定的;心跳包在Udp非連接協(xié)議中非常地必要,因?yàn)閁dp沒辦法在沒有數(shù)據(jù)接收的情況下確定網(wǎng)絡(luò)狀態(tài);在Tcp中心跳包不是必要的,但是要求較高的項(xiàng)目中依然會(huì)應(yīng)用心跳包;心跳超時(shí)(一般大于2倍的間隔時(shí)間),就表示網(wǎng)絡(luò)通信失聯(lián); |
|