許懷民
【摘 要】.NET框架(.NET Framework) 是由微軟開發(fā),一個致力于敏捷軟件開發(fā)(Agile software development)、快速應用開發(fā)(Rapid application development)、平臺無關性和網(wǎng)絡透明化的軟件開發(fā)平臺。.NET是微軟為下一個十年對服務器和桌面型軟件工程邁出的第一步。.NET包含許多有助于互聯(lián)網(wǎng)和內部網(wǎng)應用迅捷開發(fā)的技術。.NET框架是微軟公司繼Windows DNA之后的新開發(fā)平臺。.NET框架是以一種采用系統(tǒng)虛擬機運行的編程平臺,以通用語言運行庫(Common Language Runtime)為基礎,支持多種語言(C#、VB.NET、C++、Python等)的開發(fā)。
【關鍵詞】.NET;技術原理;開發(fā)
本文簡述了.NET架構中關于套接字、網(wǎng)絡編碼等底層網(wǎng)絡編程原理,詳細介紹.NET中socket中的異步套接字的方法。
一、基本原理
(一)套接字,套接字是網(wǎng)絡通信的基石,是支持TCP/IP協(xié)議的網(wǎng)絡通信的基本操作單元。可以將Socket看作不同主機間的進程進行雙向通信的端點,它構成了單個主機內及整個網(wǎng)絡間的編程界面。Socket存在于通信域(為了處理一般的線程通過Socket通信而引人的一種抽象概念)中。Socket通常和同一個域中的Socket交換數(shù)據(jù)。當然,數(shù)據(jù)交換也可以穿越域的界限,但需要執(zhí)行某種特定的解釋程序。各種進程在這個域中相互之間利用Internet協(xié)議簇來進行通信。Socket有兩種不同的類型:流Socket和數(shù)據(jù)報Socket。應用程序一般在同一類型Socket之間進行通信。當然,只要底層的通信協(xié)議允許,不同類型的Socket之間也可以通信。
(二)網(wǎng)絡編程, Socket不僅有阻塞與非阻塞之分,還允許同步或異步方式進行操作。所謂同步方式,就是發(fā)送方發(fā)送數(shù)據(jù)分組之后,無需等待接收方響應,就接著發(fā)送下一個數(shù)據(jù)分組。異步方式是指當發(fā)送方發(fā)送一個數(shù)據(jù)分組之后,一直等待接收方響應后,才接著發(fā)送下一個數(shù)據(jù)分組。
(三)網(wǎng)絡流,在網(wǎng)絡通信中,流是一個用于傳輸數(shù)據(jù)的對象。數(shù)據(jù)傳輸有兩個方向:如果數(shù)據(jù)從外部源傳輸?shù)綉贸绦蛑校瑒t為讀取流;如果數(shù)據(jù)從應用程序傳輸?shù)酵獠吭粗?,即為寫人流。其中,外部源可以是文件、網(wǎng)絡、管道或內存區(qū)域。流對外部數(shù)據(jù)源不做任何假設。通常,應盡可能地使用一個獨立的對象來傳輸數(shù)據(jù),這樣可以將數(shù)據(jù)傳輸過程和數(shù)據(jù)源分離,更容易切換數(shù)據(jù)源。
(四)IP地址與DNS解析,System.Net空間提供了一些與網(wǎng)絡基本操作息息相關的類。比較重要的是IPAddress , Dns , IPHostEntry , IPEndEntry ,IPAddress類是一個描述IP地址的類。它的靜態(tài)方法Parse及TryParse可以將一個IP地址字符串實例化為一個IPAddress對象。Dns類是一個提供有關域名解析操作的靜態(tài)類。它將從網(wǎng)絡域名系統(tǒng)中獲取IP地址、主機名以及域名的對應關系,并將這些信息保存在一個IPHostEntry對象中。IPHostEntrv的主要屬性成員有AddressList, Aliases和HostName分別保存主機的IP地址、別名和域名。IPEndPoint類表示一個連接端點,即IP地址加上端口號構成的一個綁定。
(五)網(wǎng)絡編碼與解碼,由于使用多種編程語言開發(fā)的應用程序及多種支撐平臺在網(wǎng)絡這個混合體上運行,并且網(wǎng)絡中有多種編碼方法:ASCII ,Unicode , UTF7 , UTFS , Big-Endian等,因此網(wǎng)絡編程中經(jīng)常要遇到編碼、解碼的操作。這種現(xiàn)狀需要一種將系統(tǒng)內碼轉換成網(wǎng)絡編碼的機制,以便應用程序能夠準確地讀寫數(shù)據(jù)。.NET架構的System.Text.Encoding類提供這種功能。
Encoding類提供了字符串、Unicode字符集和相應編碼的字節(jié)數(shù)組之間的轉換(轉換的數(shù)據(jù)必須連續(xù))。事實上,應用程序一般使用Encode:和Decode:來進行轉換。Encoding類包含常用編碼的實例。開發(fā)者還可以在Encode:和Decoder的基礎上編寫自定義的編碼和解碼類來處理大量的數(shù)據(jù)轉換工作。
二、socket中的異步套接字的研究
(一)socket的建立
1. 構造socket
不管是同步還是異步套接字,都必須首先建立起socket,這是后面連接的基礎。socket構造函數(shù)如下:
public Socket (
AddressFamily addressFamily,
SocketType socketType,
ProtocolType protocolType
)
可以看到,里面有三個構造函數(shù),每個參數(shù)其實都是一個枚舉值,第一個參數(shù)AddressFamily.InterNetwork說明我們采用IP 版本 4 的地址(現(xiàn)在都出版本6了);第二個參數(shù)SocketType.Stream是說建立的socket支持可靠、雙向、基于連接的字節(jié)流,而不重復數(shù)據(jù),也不保留邊界,此類型的 Socket 與單個對方主機進行通信,并且在通信開始之前需要遠程主機連接,Stream 使用傳輸控制協(xié)議 (Tcp) ProtocolType 和 InterNetworkAddressFamily。 第三個參數(shù)表名傳輸控制協(xié)議采用TCP。利用這三個參數(shù)就可以構造一個socket實例。我們常用的構造函數(shù)如下:
socket = new Socket(AddressFamily.InterNetwork, , ProtocolType.Tcp);
送信息至客戶端等等。
2.當服務器端與客戶端的連接建立之后,就可以在兩方交流數(shù)據(jù)。但客戶端和服務器端的程序都會等待socket的動作,也就說在在socket發(fā)送或者接收數(shù)據(jù)過程中,兩方程序都會中止,一直等到發(fā)送或者接收數(shù)據(jù)完畢才會繼續(xù)往下執(zhí)行。這種情況下,最明顯的表現(xiàn)就是:如果是winForm程序,窗口會表現(xiàn)出死機狀態(tài),該狀態(tài)到程序往下繼續(xù)執(zhí)行的時候才會恢復。
(二)異步套接字
在同步里面,當接收或者發(fā)送數(shù)據(jù)的時候,程序的進程是中止的,一直在等待,那么想要解決這個問題自然就會想到——接收或者發(fā)送數(shù)據(jù)的時候程序繼續(xù)往下執(zhí)行就行了。 但是這樣問題就來了:接受或者發(fā)送數(shù)據(jù)完畢之后本來是想執(zhí)行一些針對這些數(shù)據(jù)或者另外相關的操作,現(xiàn)在不理會數(shù)據(jù)的接受或者發(fā)送的話,那我的這些想要的操作怎么去執(zhí)行?這個問題是這樣解決的:在該操作發(fā)生前定義該操作為之后的特定操作,服務器端希望在接收完客戶端發(fā)送的數(shù)據(jù)后再將該數(shù)據(jù)發(fā)送回給客戶端。那么,在這里我們就可以將把接收的數(shù)據(jù)發(fā)送回給客戶端作為一個操作,程序里面自然就是一個函數(shù)了,將這個函數(shù)定義為服務器端接收完數(shù)據(jù)之后立即執(zhí)行的函數(shù)。
服務器端代碼:
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;
}
}
三、結束語
任何技術的核心并不難,難就難在當該技術發(fā)展成熟以后為了各種需要而添加的枝枝蔓蔓,這些后期添加的東西一方面使技術的應用更加方便和穩(wěn)定,另一方面,對于學習該技術的人來說,增加了不少的學習成本和障礙。學習技術的理念就是,將其枝枝蔓蔓的不斷裁剪,一直到露出問題的最最核心,然后掌握這個核心,之后從該核心出發(fā),順著問題的發(fā)展思路去學習,一切就是理所當然了。