一旦創建 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信息和昵稱,所以不會沖突。