首先從原理上解釋一下採用Socket介面的網路通訊,這裡以最常用的C/S模式作為範例,首先,服務端有一個進程(或多個進程)在指定的連接埠等待客戶來串連,服務程式等待客戶的串連資訊,一旦串連上之後,就可以按設計的資料交換方法和格式進行資料轉送。用戶端在需要的時刻發出向服務端的串連請求。這裡為了便於理解,提到了一些調用及其大致的功能。使用socket調用後,僅產生了一個可以使用的socket描述符,這時還不能進行通訊,還要使用其他的調用,以使得socket所指的結構中使用的資訊被填寫完。
在使用TCP協議時,一般服務端進程先使用socket調用得到一個描述符,然後使用bind調用將一個名字與socket描述符串連起來,對於Internet域就是將Internet地址聯編到socket。之後,服務端使用listen調用指出等待服務要求隊列的長度。然後就可以使用accept調用等待用戶端發起串連,一般是阻塞等待串連,一旦有用戶端發出串連,accept返回客戶的地址資訊,並返回一個新的socket描述符,該描述符與原先的socket有相同的特性,這時服務端就可以使用這個新的socket進行讀寫操作了。一般服務端可能在accept返回後建立一個新的進程進行與客戶的通訊,父進程則再到accept調用處等待另一個串連。用戶端進程一般先使用socket調用得到一個socket描述符,然後使用connect向指定的伺服器上的指定連接埠發起串連,一旦串連成功返回,就說明已經建立了與伺服器的串連,這時就可以通過socket描述符進行讀寫操作了。
NetFrameWork為Socket通訊提供了System.Net.Socket命名空間,在這個命名空間有以下幾個常用重要類分別是:
- ·Socket類這個低層的類用於管理串連,WebRequest,TcpClient和UdpClient在內部使用這個類。
- ·NetworkStream類這個類是從Stream派生出來的,它表示來自網路的資料流
- ·TcpClient類允許建立和使用TCP串連
- ·TcpListener類允許監聽傳入的TCP串連請求
- ·UdpClient類用於UDP客戶建立串連(UDP是另外一種TCP協議,但沒有得到廣泛使用,主要用於本網)
下面我們來看一個基於Socket的雙機通訊代碼的C#版本
首先建立Socket對象的執行個體,這可以通過Socket類的構造方法來實現:
public Socket(AddressFamily addressFamily,SocketType socketType,ProtocolType protocolType);
其中,addressFamily參數指定Socket使用的定址方案,socketType參數指定Socket的類型,protocolType參數指定Socket使用的協議。下面樣本語句建立一個Socket,它可用於在基於TCP/IP的網路(如Internet)上通訊。
Socket temp=newSocket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
若要使用UDP而不是TCP,需要更改協議類型,如下面的樣本所示:
Socket temp=newSocket(AddressFamily.InterNetwork,SocketType.Dgram,ProtocolType.Udp);
一旦建立Socket,在用戶端,你將可以通過Connect方法串連到指定的伺服器(你可以在Connect方法前Bind連接埠,就是以指定的連接埠發起串連,如果不事先Bind連接埠號碼的話,系統會預設在1024到5000隨機綁定一個連接埠號碼),並通過Send方法向遠程伺服器發送資料,而後可以通過Receive從服務端接收資料;而在伺服器端,你需要使用Bind方法綁定所指定的介面使Socket與一個本地終結點相聯,並通過Listen方法偵聽該介面上的請求,當偵聽到使用者端的串連時,調用Accept完成串連的操作,建立新的Socket以處理傳入的串連請求。使用完Socket後,使用Close方法關閉Socket。
可以看出,以上許多方法包含EndPoint類型的參數,在Internet中,TCP/IP使用一個網路地址和一個服務連接埠號碼來唯一標識裝置。網路地址標識網路上的特定裝置;連接埠號碼標識要串連到的該裝置上的特定服務。網路地址和服務連接埠的組合稱為終結點,在.NET架構中正是由EndPoint類表示這個終結點,它提供表示網路資源或服務的抽象,用以標誌網路地址等資訊。.Net同時也為每個受支援的地址族定義了EndPoint的子代;對於IP地址族,該類為IPEndPoint。IPEndPoint 類包含應用程式串連到主機上的服務所需的主機和連接埠資訊,通過組合服務的主機IP地址和連接埠號碼,IPEndPoint類形成到服務的連接點。
用到IPEndPoint類時就不可避免地涉及到電腦IP地址,System.Net命名空間中有兩種類可以得到IP地址執行個體:
·IPAddress類:IPAddress類包含電腦在IP網路上的地址。其Parse方法可將IP地址字串轉換為IPAddress執行個體。下面的語句建立一個IPAddress執行個體:
IPAddress myIP=IPAddress.Parse(
"192.168.0.1"
);
需要知道的是:Socket類支援兩種基本模式:同步和非同步。其區別在於:在同步模式中,按塊傳輸,對執行網路操作的函數(如Send和 Receive)的調用一直等到所有內容傳送操作完成後才將控制返回給調用程式。在非同步模式中,是按位傳輸,需要指定發送的開始和結束。同步模式是最常用的模式,我們這裡的例子也是使用同步模式。
下面看一個完整的例子,client向server發送一段測試字串,server接收並顯示出來,給予client成功響應。
//client端
usingSystem;
usingSystem.Text;
usingSystem.IO;
usingSystem.Net;
usingSystem.Net.Sockets;
namespacesocketsample
{
classClass1
{
staticvoidMain()
{
try
{
int port=2000;
string host="127.0.0.1";
IPAddress ip=IPAddress.Parse(host);
IPEndPoint ipe=newIPEndPoint(ip,port);//把ip和連接埠轉化為IPEndPoint執行個體
Socket c=newSocket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
//建立一個Socket
Console.WriteLine("Conneting...");
c.Connect(ipe);//串連到伺服器
string sendStr="hello!Thisisasockettest";
byte[] bs=Encoding.ASCII.GetBytes(sendStr);
Console.WriteLine("SendMessage");
c.Send(bs,bs.Length,0);//發送測試資訊
string recvStr="";
byte[] recvBytes=new byte[1024];
int bytes;
bytes=c.Receive(recvBytes,recvBytes.Length,0);//從伺服器端接受返回資訊
recvStr+=Encoding.ASCII.GetString(recvBytes,0,bytes);
Console.WriteLine("ClientGetMessage:{0}",recvStr);//顯示伺服器返回資訊
c.Close();
}
catch(ArgumentNullExceptione)
{
Console.WriteLine("ArgumentNullException:{0}",e);
}
catch(SocketExceptione)
{
Console.WriteLine("SocketException:{0}",e);
}
Console.WriteLine("PressEntertoExit");
Console.ReadLine();
}
}
}
//server端
using System;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
namespaceProject1
{
classClass2
{
staticvoidMain()
{
try
{
int port=2000;
string host="127.0.0.1";
IPAddress ip=IPAddress.Parse(host);
IPEndPoint ipe=newIPEndPoint(ip,port);
Socket s=newSocket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
//建立一個Socket類
s.Bind(ipe);//綁定2000連接埠
s.Listen(0);//開始監聽
Console.WriteLine("Waitforconnect");
Socket temp=s.Accept();//為建立串連建立新的Socket。
Console.WriteLine("Getaconnect");
string recvStr="";
byte[] recvBytes=newbyte[1024];
int bytes;
bytes=temp.Receive(recvBytes,recvBytes.Length,0);//從用戶端接受資訊
recvStr+=Encoding.ASCII.GetString(recvBytes,0,bytes);
Console.WriteLine("ServerGetMessage:{0}",recvStr);//把用戶端傳來的資訊顯示出來
stringsendStr="Ok!ClientSendMessageSucessful!";
byte[]bs=Encoding.ASCII.GetBytes(sendStr);
temp.Send(bs,bs.Length,0);//返回用戶端成功資訊
temp.Close();
s.Close();
}
catch(ArgumentNullExceptione)
{
Console.WriteLine("ArgumentNullException:{0}",e);
}
catch(SocketExceptione)
{
Console.WriteLine("SocketException:{0}",e);
}
Console.WriteLine("PressEntertoExit");
Console.ReadLine();
}
}
}