许怀民
【摘 要】.NET框架(.NET Framework) 是由微软开发,一个致力于敏捷软件开发(Agile software development)、快速应用开发(Rapid application development)、平台无关性和网络透明化的软件开发平台。.NET是微软为下一个十年对服务器和桌面型软件工程迈出的第一步。.NET包含许多有助于互联网和内部网应用迅捷开发的技术。.NET框架是微软公司继Windows DNA之后的新开发平台。.NET框架是以一种采用系统虚拟机运行的编程平台,以通用语言运行库(Common Language Runtime)为基础,支持多种语言(C#、VB.NET、C++、Python等)的开发。
【关键词】.NET;技术原理;开发
本文简述了.NET架构中关于套接字、网络编码等底层网络编程原理,详细介绍.NET中socket中的异步套接字的方法。
一、基本原理
(一)套接字,套接字是网络通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。可以将Socket看作不同主机间的进程进行双向通信的端点,它构成了单个主机内及整个网络间的编程界面。Socket存在于通信域(为了处理一般的线程通过Socket通信而引人的一种抽象概念)中。Socket通常和同一个域中的Socket交换数据。当然,数据交换也可以穿越域的界限,但需要执行某种特定的解释程序。各种进程在这个域中相互之间利用Internet协议簇来进行通信。Socket有两种不同的类型:流Socket和数据报Socket。应用程序一般在同一类型Socket之间进行通信。当然,只要底层的通信协议允许,不同类型的Socket之间也可以通信。
(二)网络编程, Socket不仅有阻塞与非阻塞之分,还允许同步或异步方式进行操作。所谓同步方式,就是发送方发送数据分组之后,无需等待接收方响应,就接着发送下一个数据分组。异步方式是指当发送方发送一个数据分组之后,一直等待接收方响应后,才接着发送下一个数据分组。
(三)网络流,在网络通信中,流是一个用于传输数据的对象。数据传输有两个方向:如果数据从外部源传输到应用程序中,则为读取流;如果数据从应用程序传输到外部源中,即为写人流。其中,外部源可以是文件、网络、管道或内存区域。流对外部数据源不做任何假设。通常,应尽可能地使用一个独立的对象来传输数据,这样可以将数据传输过程和数据源分离,更容易切换数据源。
(四)IP地址与DNS解析,System.Net空间提供了一些与网络基本操作息息相关的类。比较重要的是IPAddress , Dns , IPHostEntry , IPEndEntry ,IPAddress类是一个描述IP地址的类。它的静态方法Parse及TryParse可以将一个IP地址字符串实例化为一个IPAddress对象。Dns类是一个提供有关域名解析操作的静态类。它将从网络域名系统中获取IP地址、主机名以及域名的对应关系,并将这些信息保存在一个IPHostEntry对象中。IPHostEntrv的主要属性成员有AddressList, Aliases和HostName分别保存主机的IP地址、别名和域名。IPEndPoint类表示一个连接端点,即IP地址加上端口号构成的一个绑定。
(五)网络编码与解码,由于使用多种编程语言开发的应用程序及多种支撑平台在网络这个混合体上运行,并且网络中有多种编码方法:ASCII ,Unicode , UTF7 , UTFS , Big-Endian等,因此网络编程中经常要遇到编码、解码的操作。这种现状需要一种将系统内码转换成网络编码的机制,以便应用程序能够准确地读写数据。.NET架构的System.Text.Encoding类提供这种功能。
Encoding类提供了字符串、Unicode字符集和相应编码的字节数组之间的转换(转换的数据必须连续)。事实上,应用程序一般使用Encode:和Decode:来进行转换。Encoding类包含常用编码的实例。开发者还可以在Encode:和Decoder的基础上编写自定义的编码和解码类来处理大量的数据转换工作。
二、socket中的异步套接字的研究
(一)socket的建立
1. 构造socket
不管是同步还是异步套接字,都必须首先建立起socket,这是后面连接的基础。socket构造函数如下:
public Socket (
AddressFamily addressFamily,
SocketType socketType,
ProtocolType protocolType
)
可以看到,里面有三个构造函数,每个参数其实都是一个枚举值,第一个参数AddressFamily.InterNetwork说明我们采用IP 版本 4 的地址(现在都出版本6了);第二个参数SocketType.Stream是说建立的socket支持可靠、双向、基于连接的字节流,而不重复数据,也不保留边界,此类型的 Socket 与单个对方主机进行通信,并且在通信开始之前需要远程主机连接,Stream 使用传输控制协议 (Tcp) ProtocolType 和 InterNetworkAddressFamily。 第三个参数表名传输控制协议采用TCP。利用这三个参数就可以构造一个socket实例。我们常用的构造函数如下:
socket = new Socket(AddressFamily.InterNetwork, , ProtocolType.Tcp);
送信息至客户端等等。
2.当服务器端与客户端的连接建立之后,就可以在两方交流数据。但客户端和服务器端的程序都会等待socket的动作,也就说在在socket发送或者接收数据过程中,两方程序都会中止,一直等到发送或者接收数据完毕才会继续往下执行。这种情况下,最明显的表现就是:如果是winForm程序,窗口会表现出死机状态,该状态到程序往下继续执行的时候才会恢复。
(二)異步套接字
在同步里面,当接收或者发送数据的时候,程序的进程是中止的,一直在等待,那么想要解决这个问题自然就会想到——接收或者发送数据的时候程序继续往下执行就行了。 但是这样问题就来了:接受或者发送数据完毕之后本来是想执行一些针对这些数据或者另外相关的操作,现在不理会数据的接受或者发送的话,那我的这些想要的操作怎么去执行?这个问题是这样解决的:在该操作发生前定义该操作为之后的特定操作,服务器端希望在接收完客户端发送的数据后再将该数据发送回给客户端。那么,在这里我们就可以将把接收的数据发送回给客户端作为一个操作,程序里面自然就是一个函数了,将这个函数定义为服务器端接收完数据之后立即执行的函数。
服务器端代码:
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;
}
}
三、結束语
任何技术的核心并不难,难就难在当该技术发展成熟以后为了各种需要而添加的枝枝蔓蔓,这些后期添加的东西一方面使技术的应用更加方便和稳定,另一方面,对于学习该技术的人来说,增加了不少的学习成本和障碍。学习技术的理念就是,将其枝枝蔓蔓的不断裁剪,一直到露出问题的最最核心,然后掌握这个核心,之后从该核心出发,顺着问题的发展思路去学习,一切就是理所当然了。