C# 2.0 套接字程式設計例項初探
首先從原理上解釋一下采用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 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
若要使用 UDP 而不是 TCP,需要更改協議型別,如下面的示例所示:
Socket temp = new Socket(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端
using System;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
namespace socketsample
{
class Class1
{
static void Main()
{
try
{
int port = 2000;
string host = "127.0.0.1";
IPAddress ip = IPAddress.Parse(host);
IPEndPoint ipe = new IPEndPoint(ip, port);//把ip和埠轉化為IPEndPoint例項
Socket c = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//建立一個Socket
Console.WriteLine("Conneting...");
c.Connect(ipe);//連線到伺服器
string sendStr = "hello!This is a socket test";
byte[] bs = Encoding.ASCII.GetBytes(sendStr);
Console.WriteLine("Send Message");
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("Client Get Message:{0}", recvStr);//顯示伺服器返回資訊
c.Close();
}
catch (ArgumentNullException e)
{
Console.WriteLine("ArgumentNullException: {0}", e);
}
catch (SocketException e)
{
Console.WriteLine("SocketException: {0}", e);
}
Console.WriteLine("Press Enter to Exit");
Console.ReadLine();
}
}
}
//server端
using System;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
namespace Project1
{
class Class2
{
static void Main()
{
try
{
int port = 2000;
string host = "127.0.0.1";
IPAddress ip = IPAddress.Parse(host);
IPEndPoint ipe = new IPEndPoint(ip, port);
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//建立一個Socket類
s.Bind(ipe);//繫結2000埠
s.Listen(0);//開始監聽
Console.WriteLine("Wait for connect");
Socket temp = s.Accept();//為新建連線建立新的Socket。
Console.WriteLine("Get a connect");
string recvStr = "";
byte[] recvBytes = new byte[1024];
int bytes;
bytes = temp.Receive(recvBytes, recvBytes.Length, 0);//從客戶端接受資訊
recvStr += Encoding.ASCII.GetString(recvBytes, 0, bytes);
Console.WriteLine("Server Get Message:{0}",recvStr);//把客戶端傳來的資訊顯示出來
string sendStr = "Ok!Client Send Message Sucessful!";
byte[] bs = Encoding.ASCII.GetBytes(sendStr);
temp.Send(bs, bs.Length, 0);//返回客戶端成功資訊
temp.Close();
s.Close();
}
catch (ArgumentNullException e)
{
Console.WriteLine("ArgumentNullException: {0}", e);
}
catch (SocketException e)
{
Console.WriteLine("SocketException: {0}", e);
}
Console.WriteLine("Press Enter to Exit");
Console.ReadLine();
}
}
}
上面的例子是用的Socket類,System.Net.Socket名稱空間還提供了兩個抽象高階類TCPClient和UDPClient和用於通訊流處理的NetWorkStream,讓我們看下例子
客戶端
TcpClient tcpClient=new TcpCLient(主機IP,埠號);
NetworkStream ns=tcp.Client.GetStream();
服務端
TcpListener tcpListener=new TcpListener(監聽埠);
tcpListener.Start();
TcpClient tcpClient=tcpListener.AcceptTcpClient();
NetworkStream ns=tcpClient.GetStream();
服務端用TcpListener監聽,然後把連線的物件例項化為一個TcpClient,呼叫TcpClient.GetStream()方法,返回網路流例項化為一個NetworlStream流,下面就是用流的方法進行Send,Receive
如果是UdpClient的話,就直接UdpClient例項化,然後呼叫UdpClient的Send和Receive方法,需要注意的事, UdpClient沒有返回網路流的方法,就是說沒有GetStream方法,所以無法流化,而且使用Udp通訊的時候,不要伺服器監聽。
現在我們大致瞭解了.Net Socket通訊的流程,下面我們來作一個稍微複雜點的程式,一個廣播式的C/S聊天程式。
客戶端設計需要一個1個ListBox,用於顯示聊天內容,一個TextBox輸入你要說的話,一個Button傳送留言,一個Button建立連線。
點選建立連線的Button後出來一個對話方塊,提示輸入連線伺服器的IP,埠,和你的暱稱,啟動一個接受執行緒,負責接受從伺服器傳來的資訊並顯示在ListBox上面。
伺服器端2個Button,一個啟動服務,一個T掉已建立連線的客戶端,一個ListBox顯示連線上的客戶端的Ip和埠。
比較重要的地方是字串編碼的問題,需要先把需要傳送的字串按照UTF8編碼,然後接受的時候再還原成為GB2312,不然中文顯示會是亂碼。
還有一個就是接收執行緒,我這裡簡單寫成一個While(ture)迴圈,不斷判斷是否有資訊流入,有就接收,並顯示在ListBox上,這裡有問題,在.Net2.0裡面,交錯執行緒修改窗體空間屬性的時候會引發一個異常,不可以直接修改,需要定義一個委託來修改。
當客戶端需要斷開連線的時候,比如點選窗體右上角的XX,就需要定義一個this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Closing);(.Net2.0是 FormClosing系統事件),在Closing()函式裡面,傳送Close字元給服務端,伺服器判斷迴圈判斷所有的連線上的客戶端傳來的資訊,如 果是以Close開頭,斷開與其的連線。看到這裡,讀者就會問了,如果我在聊天視窗輸入Close是不是也斷開連線呢?不是的,在聊天視窗輸入的資訊傳給 伺服器的時候開頭都要加上Ip資訊和暱稱,所以不會衝突。
在使用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 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
若要使用 UDP 而不是 TCP,需要更改協議型別,如下面的示例所示:
Socket temp = new Socket(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端
using System;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
namespace socketsample
{
class Class1
{
static void Main()
{
try
{
int port = 2000;
string host = "127.0.0.1";
IPAddress ip = IPAddress.Parse(host);
IPEndPoint ipe = new IPEndPoint(ip, port);//把ip和埠轉化為IPEndPoint例項
Socket c = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//建立一個Socket
Console.WriteLine("Conneting...");
c.Connect(ipe);//連線到伺服器
string sendStr = "hello!This is a socket test";
byte[] bs = Encoding.ASCII.GetBytes(sendStr);
Console.WriteLine("Send Message");
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("Client Get Message:{0}", recvStr);//顯示伺服器返回資訊
c.Close();
}
catch (ArgumentNullException e)
{
Console.WriteLine("ArgumentNullException: {0}", e);
}
catch (SocketException e)
{
Console.WriteLine("SocketException: {0}", e);
}
Console.WriteLine("Press Enter to Exit");
Console.ReadLine();
}
}
}
//server端
using System;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
namespace Project1
{
class Class2
{
static void Main()
{
try
{
int port = 2000;
string host = "127.0.0.1";
IPAddress ip = IPAddress.Parse(host);
IPEndPoint ipe = new IPEndPoint(ip, port);
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//建立一個Socket類
s.Bind(ipe);//繫結2000埠
s.Listen(0);//開始監聽
Console.WriteLine("Wait for connect");
Socket temp = s.Accept();//為新建連線建立新的Socket。
Console.WriteLine("Get a connect");
string recvStr = "";
byte[] recvBytes = new byte[1024];
int bytes;
bytes = temp.Receive(recvBytes, recvBytes.Length, 0);//從客戶端接受資訊
recvStr += Encoding.ASCII.GetString(recvBytes, 0, bytes);
Console.WriteLine("Server Get Message:{0}",recvStr);//把客戶端傳來的資訊顯示出來
string sendStr = "Ok!Client Send Message Sucessful!";
byte[] bs = Encoding.ASCII.GetBytes(sendStr);
temp.Send(bs, bs.Length, 0);//返回客戶端成功資訊
temp.Close();
s.Close();
}
catch (ArgumentNullException e)
{
Console.WriteLine("ArgumentNullException: {0}", e);
}
catch (SocketException e)
{
Console.WriteLine("SocketException: {0}", e);
}
Console.WriteLine("Press Enter to Exit");
Console.ReadLine();
}
}
}
上面的例子是用的Socket類,System.Net.Socket名稱空間還提供了兩個抽象高階類TCPClient和UDPClient和用於通訊流處理的NetWorkStream,讓我們看下例子
客戶端
TcpClient tcpClient=new TcpCLient(主機IP,埠號);
NetworkStream ns=tcp.Client.GetStream();
服務端
TcpListener tcpListener=new TcpListener(監聽埠);
tcpListener.Start();
TcpClient tcpClient=tcpListener.AcceptTcpClient();
NetworkStream ns=tcpClient.GetStream();
服務端用TcpListener監聽,然後把連線的物件例項化為一個TcpClient,呼叫TcpClient.GetStream()方法,返回網路流例項化為一個NetworlStream流,下面就是用流的方法進行Send,Receive
如果是UdpClient的話,就直接UdpClient例項化,然後呼叫UdpClient的Send和Receive方法,需要注意的事, UdpClient沒有返回網路流的方法,就是說沒有GetStream方法,所以無法流化,而且使用Udp通訊的時候,不要伺服器監聽。
現在我們大致瞭解了.Net Socket通訊的流程,下面我們來作一個稍微複雜點的程式,一個廣播式的C/S聊天程式。
客戶端設計需要一個1個ListBox,用於顯示聊天內容,一個TextBox輸入你要說的話,一個Button傳送留言,一個Button建立連線。
點選建立連線的Button後出來一個對話方塊,提示輸入連線伺服器的IP,埠,和你的暱稱,啟動一個接受執行緒,負責接受從伺服器傳來的資訊並顯示在ListBox上面。
伺服器端2個Button,一個啟動服務,一個T掉已建立連線的客戶端,一個ListBox顯示連線上的客戶端的Ip和埠。
比較重要的地方是字串編碼的問題,需要先把需要傳送的字串按照UTF8編碼,然後接受的時候再還原成為GB2312,不然中文顯示會是亂碼。
還有一個就是接收執行緒,我這裡簡單寫成一個While(ture)迴圈,不斷判斷是否有資訊流入,有就接收,並顯示在ListBox上,這裡有問題,在.Net2.0裡面,交錯執行緒修改窗體空間屬性的時候會引發一個異常,不可以直接修改,需要定義一個委託來修改。
當客戶端需要斷開連線的時候,比如點選窗體右上角的XX,就需要定義一個this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Closing);(.Net2.0是 FormClosing系統事件),在Closing()函式裡面,傳送Close字元給服務端,伺服器判斷迴圈判斷所有的連線上的客戶端傳來的資訊,如 果是以Close開頭,斷開與其的連線。看到這裡,讀者就會問了,如果我在聊天視窗輸入Close是不是也斷開連線呢?不是的,在聊天視窗輸入的資訊傳給 伺服器的時候開頭都要加上Ip資訊和暱稱,所以不會衝突。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-526842/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 《UNIX網路程式設計》筆記 - 套接字選項/UDP套接字程式設計筆記UDP
- Java套接字程式設計Java程式設計
- 基本TCP套接字程式設計APITCP程式設計API
- Python原始套接字程式設計Python程式設計
- 【網路程式設計】TCPIP-8-套接字的多種選項程式設計TCP
- 14.1 Socket 套接字程式設計入門程式設計
- UNIX網路程式設計(6)--套接字地址結構、通用套接字地址結構程式設計
- C#多執行緒程式設計例項C#執行緒程式設計
- c#網路程式設計初探C#程式設計
- Linux網路程式設計--原始套接字(轉)Linux程式設計
- Linux系統程式設計(37)—— socket程式設計之原始套接字Linux程式設計
- Unix 套接字程序通訊初探【Go 版本】Go
- Unix 套接字程序通訊初探【Java 版本】Java
- 遠端開發分散式C#程式設計例項分散式C#程式設計
- Jmeter beanshell程式設計例項JMeterBean程式設計
- 設計模式例項程式碼設計模式
- KafKa Java程式設計例項KafkaJava程式設計
- c# winform程式設計轉例C#ORM程式設計
- Linux網路程式設計--高階套接字函式(轉)Linux程式設計函式
- UNIX網路程式設計學習(17)--檢查套接字選項是否受支援並獲取預設值程式設計
- 【Socket程式設計】【第一節】【Socket基本原理和套接字】程式設計
- JavaScript設計模式初探--單例設計模式JavaScript設計模式單例
- Shell程式設計入門例項程式設計
- Qt 中Socket程式設計例項QT程式設計
- android socket程式設計例項Android程式設計
- The MySQL C API程式設計例項MySqlAPI程式設計
- XML程式設計例項(二) (轉)XML程式設計
- Java&CORBA程式設計例項JavaORB程式設計
- corba程式設計簡單例項ORB程式設計單例
- Java XML程式設計例項解析JavaXML程式設計
- [C++]C++程式設計例項C++程式設計
- shell程式設計例項--實現累加程式設計
- 【Akka】Akka入門程式設計例項程式設計
- Delphi趣味程式設計例項三則程式設計
- Linux網路程式設計之原始套接字-ping協議實現Linux程式設計協議
- Linux作業系統套接字程式設計的5個隱患(轉)Linux作業系統程式設計
- 文字框輸入數字倒計例項程式碼
- C# Winform程式介面優化例項C#ORM優化