許懷民
【摘 要】.NET框架(.NET Framework) 是由微軟開發(fā),一個致力于敏捷軟件開發(fā)(Agile software development)、快速應(yīng)用開發(fā)(Rapid application development)、平臺無關(guān)性和網(wǎng)絡(luò)透明化的軟件開發(fā)平臺。.NET是微軟為下一個十年對服務(wù)器和桌面型軟件工程邁出的第一步。.NET包含許多有助于互聯(lián)網(wǎng)和內(nèi)部網(wǎng)應(yīng)用迅捷開發(fā)的技術(shù)。.NET框架是微軟公司繼Windows DNA之后的新開發(fā)平臺。.NET框架是以一種采用系統(tǒng)虛擬機(jī)運(yùn)行的編程平臺,以通用語言運(yùn)行庫(Common Language Runtime)為基礎(chǔ),支持多種語言(C#、VB.NET、C++、Python等)的開發(fā)。
【關(guān)鍵詞】.NET;技術(shù)原理;開發(fā)
本文簡述了.NET架構(gòu)中關(guān)于套接字、網(wǎng)絡(luò)編碼等底層網(wǎng)絡(luò)編程原理,詳細(xì)介紹.NET中socket中的異步套接字的方法。
一、基本原理
(一)套接字,套接字是網(wǎng)絡(luò)通信的基石,是支持TCP/IP協(xié)議的網(wǎng)絡(luò)通信的基本操作單元??梢詫ocket看作不同主機(jī)間的進(jìn)程進(jìn)行雙向通信的端點(diǎn),它構(gòu)成了單個主機(jī)內(nèi)及整個網(wǎng)絡(luò)間的編程界面。Socket存在于通信域(為了處理一般的線程通過Socket通信而引人的一種抽象概念)中。Socket通常和同一個域中的Socket交換數(shù)據(jù)。當(dāng)然,數(shù)據(jù)交換也可以穿越域的界限,但需要執(zhí)行某種特定的解釋程序。各種進(jìn)程在這個域中相互之間利用Internet協(xié)議簇來進(jìn)行通信。Socket有兩種不同的類型:流Socket和數(shù)據(jù)報Socket。應(yīng)用程序一般在同一類型Socket之間進(jìn)行通信。當(dāng)然,只要底層的通信協(xié)議允許,不同類型的Socket之間也可以通信。
(二)網(wǎng)絡(luò)編程, Socket不僅有阻塞與非阻塞之分,還允許同步或異步方式進(jìn)行操作。所謂同步方式,就是發(fā)送方發(fā)送數(shù)據(jù)分組之后,無需等待接收方響應(yīng),就接著發(fā)送下一個數(shù)據(jù)分組。異步方式是指當(dāng)發(fā)送方發(fā)送一個數(shù)據(jù)分組之后,一直等待接收方響應(yīng)后,才接著發(fā)送下一個數(shù)據(jù)分組。
(三)網(wǎng)絡(luò)流,在網(wǎng)絡(luò)通信中,流是一個用于傳輸數(shù)據(jù)的對象。數(shù)據(jù)傳輸有兩個方向:如果數(shù)據(jù)從外部源傳輸?shù)綉?yīng)用程序中,則為讀取流;如果數(shù)據(jù)從應(yīng)用程序傳輸?shù)酵獠吭粗?,即為寫人流。其中,外部源可以是文件、網(wǎng)絡(luò)、管道或內(nèi)存區(qū)域。流對外部數(shù)據(jù)源不做任何假設(shè)。通常,應(yīng)盡可能地使用一個獨(dú)立的對象來傳輸數(shù)據(jù),這樣可以將數(shù)據(jù)傳輸過程和數(shù)據(jù)源分離,更容易切換數(shù)據(jù)源。
(四)IP地址與DNS解析,System.Net空間提供了一些與網(wǎng)絡(luò)基本操作息息相關(guān)的類。比較重要的是IPAddress , Dns , IPHostEntry , IPEndEntry ,IPAddress類是一個描述IP地址的類。它的靜態(tài)方法Parse及TryParse可以將一個IP地址字符串實(shí)例化為一個IPAddress對象。Dns類是一個提供有關(guān)域名解析操作的靜態(tài)類。它將從網(wǎng)絡(luò)域名系統(tǒng)中獲取IP地址、主機(jī)名以及域名的對應(yīng)關(guān)系,并將這些信息保存在一個IPHostEntry對象中。IPHostEntrv的主要屬性成員有AddressList, Aliases和HostName分別保存主機(jī)的IP地址、別名和域名。IPEndPoint類表示一個連接端點(diǎn),即IP地址加上端口號構(gòu)成的一個綁定。
(五)網(wǎng)絡(luò)編碼與解碼,由于使用多種編程語言開發(fā)的應(yīng)用程序及多種支撐平臺在網(wǎng)絡(luò)這個混合體上運(yùn)行,并且網(wǎng)絡(luò)中有多種編碼方法:ASCII ,Unicode , UTF7 , UTFS , Big-Endian等,因此網(wǎng)絡(luò)編程中經(jīng)常要遇到編碼、解碼的操作。這種現(xiàn)狀需要一種將系統(tǒng)內(nèi)碼轉(zhuǎn)換成網(wǎng)絡(luò)編碼的機(jī)制,以便應(yīng)用程序能夠準(zhǔn)確地讀寫數(shù)據(jù)。.NET架構(gòu)的System.Text.Encoding類提供這種功能。
Encoding類提供了字符串、Unicode字符集和相應(yīng)編碼的字節(jié)數(shù)組之間的轉(zhuǎn)換(轉(zhuǎn)換的數(shù)據(jù)必須連續(xù))。事實(shí)上,應(yīng)用程序一般使用Encode:和Decode:來進(jìn)行轉(zhuǎn)換。Encoding類包含常用編碼的實(shí)例。開發(fā)者還可以在Encode:和Decoder的基礎(chǔ)上編寫自定義的編碼和解碼類來處理大量的數(shù)據(jù)轉(zhuǎn)換工作。
二、socket中的異步套接字的研究
(一)socket的建立
1. 構(gòu)造socket
不管是同步還是異步套接字,都必須首先建立起socket,這是后面連接的基礎(chǔ)。socket構(gòu)造函數(shù)如下:
public Socket (
AddressFamily addressFamily,
SocketType socketType,
ProtocolType protocolType
)
可以看到,里面有三個構(gòu)造函數(shù),每個參數(shù)其實(shí)都是一個枚舉值,第一個參數(shù)AddressFamily.InterNetwork說明我們采用IP 版本 4 的地址(現(xiàn)在都出版本6了);第二個參數(shù)SocketType.Stream是說建立的socket支持可靠、雙向、基于連接的字節(jié)流,而不重復(fù)數(shù)據(jù),也不保留邊界,此類型的 Socket 與單個對方主機(jī)進(jìn)行通信,并且在通信開始之前需要遠(yuǎn)程主機(jī)連接,Stream 使用傳輸控制協(xié)議 (Tcp) ProtocolType 和 InterNetworkAddressFamily。 第三個參數(shù)表名傳輸控制協(xié)議采用TCP。利用這三個參數(shù)就可以構(gòu)造一個socket實(shí)例。我們常用的構(gòu)造函數(shù)如下:
socket = new Socket(AddressFamily.InterNetwork, , ProtocolType.Tcp);
送信息至客戶端等等。
2.當(dāng)服務(wù)器端與客戶端的連接建立之后,就可以在兩方交流數(shù)據(jù)。但客戶端和服務(wù)器端的程序都會等待socket的動作,也就說在在socket發(fā)送或者接收數(shù)據(jù)過程中,兩方程序都會中止,一直等到發(fā)送或者接收數(shù)據(jù)完畢才會繼續(xù)往下執(zhí)行。這種情況下,最明顯的表現(xiàn)就是:如果是winForm程序,窗口會表現(xiàn)出死機(jī)狀態(tài),該狀態(tài)到程序往下繼續(xù)執(zhí)行的時候才會恢復(fù)。
(二)異步套接字
在同步里面,當(dāng)接收或者發(fā)送數(shù)據(jù)的時候,程序的進(jìn)程是中止的,一直在等待,那么想要解決這個問題自然就會想到——接收或者發(fā)送數(shù)據(jù)的時候程序繼續(xù)往下執(zhí)行就行了。 但是這樣問題就來了:接受或者發(fā)送數(shù)據(jù)完畢之后本來是想執(zhí)行一些針對這些數(shù)據(jù)或者另外相關(guān)的操作,現(xiàn)在不理會數(shù)據(jù)的接受或者發(fā)送的話,那我的這些想要的操作怎么去執(zhí)行?這個問題是這樣解決的:在該操作發(fā)生前定義該操作為之后的特定操作,服務(wù)器端希望在接收完客戶端發(fā)送的數(shù)據(jù)后再將該數(shù)據(jù)發(fā)送回給客戶端。那么,在這里我們就可以將把接收的數(shù)據(jù)發(fā)送回給客戶端作為一個操作,程序里面自然就是一個函數(shù)了,將這個函數(shù)定義為服務(wù)器端接收完數(shù)據(jù)之后立即執(zhí)行的函數(shù)。
服務(wù)器端代碼:
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
public class StateObject {
public Socket workSocket = null;
public const int BufferSize = 1024;
public byte[] buffer = new byte[BufferSize];
public StringBuilder sb = new StringBuilder();
}
public class AsynchronousSocketListener {
public static ManualResetEvent allDone = new ManualResetEvent(false);
public AsynchronousSocketListener() {
}
public static void StartListening() {
byte[] bytes = new Byte[1024];
IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
IPAddress ipAddress = ipHostInfo.AddressList[0];
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 11000);
Socket listener = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp );
try {
listener.Bind(localEndPoint);
listener.Listen(100);
while (true) {
allDone.Reset();
Console.WriteLine("Waiting for a connection...");
listener.BeginAccept(
new AsyncCallback(AcceptCallback),
listener );
allDone.WaitOne();
}
} catch (Exception e) {
Console.WriteLine(e.ToString());
}
Console.WriteLine("\nPress ENTER to continue...");
Console.Read();
}
public static void AcceptCallback(IAsyncResult ar) {
allDone.Set();
Socket listener = (Socket) ar.AsyncState;
Socket handler = listener.EndAccept(ar);
StateObject state = new StateObject();
state.workSocket = handler;
handler.BeginReceive( state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReadCallback), state);
}
public static void ReadCallback(IAsyncResult ar) {
String content = String.Empty;
StateObject state = (StateObject) ar.AsyncState;
Socket handler = state.workSocket;
int bytesRead = handler.EndReceive(ar);
if (bytesRead > 0) {
state.sb.Append(Encoding.ASCII.GetString(
state.buffer,0,bytesRead));
content = state.sb.ToString();
if (content.IndexOf("
Console.WriteLine("Read {0} bytes from socket. \n Data : {1}",
content.Length, content );
Send(handler, content);
} else {
handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReadCallback), state);
}
}
}
private static void Send(Socket handler, String data) {
byte[] byteData = Encoding.ASCII.GetBytes(data);
handler.BeginSend(byteData, 0, byteData.Length, 0,
new AsyncCallback(SendCallback), handler);
}
private static void SendCallback(IAsyncResult ar) {
try {
Socket handler = (Socket) ar.AsyncState;
int bytesSent = handler.EndSend(ar);
Console.WriteLine("Sent {0} bytes to client.", bytesSent);
handler.Shutdown(SocketShutdown.Both);
handler.Close();
} catch (Exception e) {
Console.WriteLine(e.ToString());
}
}
public static int Main(String[] args) {
StartListening();
return 0;
}
}
客戶端代碼:
public class AsynchronousClient {
private const int port = 11000;
private static ManualResetEvent connectDone =
new ManualResetEvent(false);
private static ManualResetEvent sendDone =
new ManualResetEvent(false);
private static ManualResetEvent receiveDone =
new ManualResetEvent(false);
private static String response = String.Empty;
private static void StartClient() {
try {
IPHostEntry ipHostInfo = Dns.Resolve("host.contoso.com");
IPAddress ipAddress = ipHostInfo.AddressList[0];
IPEndPoint remoteEP = new IPEndPoint(ipAddress, port);
Socket client = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
client.BeginConnect( remoteEP,
new AsyncCallback(ConnectCallback), client);
connectDone.WaitOne();
Send(client,"This is a test
sendDone.WaitOne();
Receive(client);
receiveDone.WaitOne();
Console.WriteLine("Response received : {0}", response);
client.Shutdown(SocketShutdown.Both);
client.Close();
} catch (Exception e) {
Console.WriteLine(e.ToString());
}
}
private static void ConnectCallback(IAsyncResult ar) {
try {
Socket client = (Socket) ar.AsyncState;
client.EndConnect(ar);
Console.WriteLine("Socket connected to {0}",
client.RemoteEndPoint.ToString());
connectDone.Set();
} catch (Exception e) {
Console.WriteLine(e.ToString());
}
}
private static void Receive(Socket client) {
try {
StateObject state = new StateObject();
state.workSocket = client;
client.BeginReceive( state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReceiveCallback), state);
} catch (Exception e) {
Console.WriteLine(e.ToString());
}
}
private static void ReceiveCallback( IAsyncResult ar ) {
try {
StateObject state = (StateObject) ar.AsyncState;
Socket client = state.workSocket;
int bytesRead = client.EndReceive(ar);
if (bytesRead > 0) {
state.sb.Append(Encoding.ASCII.GetString(state.buffer,0,bytesRead));
client.BeginReceive(state.buffer,0,StateObject.BufferSize,0,
new AsyncCallback(ReceiveCallback), state);
} else {
if (state.sb.Length > 1) {
response = state.sb.ToString();
}
receiveDone.Set();
}
} catch (Exception e) {
Console.WriteLine(e.ToString());
}
}
private static void Send(Socket client, String data) {
byte[] byteData = Encoding.ASCII.GetBytes(data);
client.BeginSend(byteData, 0, byteData.Length, 0,
new AsyncCallback(SendCallback), client);
}
private static void SendCallback(IAsyncResult ar) {
try {
Socket client = (Socket) ar.AsyncState;
int bytesSent = client.EndSend(ar);
Console.WriteLine("Sent {0} bytes to server.", bytesSent);
sendDone.Set();
} catch (Exception e) {
Console.WriteLine(e.ToString());
}
}
public static int Main(String[] args) {
StartClient();
return 0;
}
}
三、結(jié)束語
任何技術(shù)的核心并不難,難就難在當(dāng)該技術(shù)發(fā)展成熟以后為了各種需要而添加的枝枝蔓蔓,這些后期添加的東西一方面使技術(shù)的應(yīng)用更加方便和穩(wěn)定,另一方面,對于學(xué)習(xí)該技術(shù)的人來說,增加了不少的學(xué)習(xí)成本和障礙。學(xué)習(xí)技術(shù)的理念就是,將其枝枝蔓蔓的不斷裁剪,一直到露出問題的最最核心,然后掌握這個核心,之后從該核心出發(fā),順著問題的發(fā)展思路去學(xué)習(xí),一切就是理所當(dāng)然了。