在開始介紹socket前先補充補充基礎知識,在此基礎上理解網絡通信才會順理成章,當然有基礎的可以跳過去了。都是廢話,進入正題。
TCP/IP:Transmission Control Protocol/Internet Protocol,傳輸控制協議/因特網互聯協議,又名網絡通訊協議。簡單來說:TCP控制傳輸數據,負責發現傳輸的問題,一旦有問題就發出信號,要求重新傳輸,直到所有數據安全正確地傳輸到目的地,而IP是負責給因特網中的每一台電腦定義一個地址,以便傳輸。從協議分層模型方面來講:TCP/IP由:網絡接口層(鏈路層)、網絡層、傳輸層、應用層。它和OSI的七層結構以及對於協議族不同,下圖簡單表示:



注:上圖左圖:TCP/IP的四層結構對應OSI七層結構。
中間的圖示:TCP/IP協議族在OSI七層中的位置及對應的功能。
上圖右圖:TCP/IP協議模塊關系圖。
現階段socket通信使用TCP、UDP協議,相對應UDP來說,TCP則是比較安全穩定的協議了。本文只涉及到TCP協議來說socket通信。首先講述TCP/IP的三次握手,在握手基礎上延伸socket通信的基本過程。
下面介紹對於應屆生畢業面試來說是非常熟悉的,同時也是最臭名昭著的三次握手:
1 客戶端發送syn報文到服務器端,並置發送序號為x。
2 服務器端接收到客戶端發送的請求報文,然后向客戶端發送syn報文,並且發送確認序號x+1,並置發送序號為y。
3 客戶端受到服務器發送確認報文后,發送確認信號y+1,並置發送序號為z。至此客戶端和服務器端建立連接。

在此基礎上,socket連接過程:
服務器監聽:服務器端socket並不定位具體的客戶端socket,而是處於等待監聽狀態,實時監控網絡狀態。
客戶端請求:客戶端clientSocket發送連接請求,目標是服務器的serverSocket。為此,clientSocket必須知道serverSocket的地址和端口號,進行掃描發出連接請求。
連接確認:當服務器socket監聽到或者是受到客戶端socket的連接請求時,服務器就響應客戶端的請求,建議一個新的socket,把服務器socket發送給客戶端,一旦客戶端確認連接,則連接建立。
注:在連接確認階段:服務器socket即使在和一個客戶端socket建立連接后,還在處於監聽狀態,仍然可以接收到其他客戶端的連接請求,這也是一對多產生的原因。
下圖簡單說明連接過程:

socket連接原理知道了,此處編寫最基本最簡單的socket通信:
服務器端:
int port = 6000;
string host = "127.0.0.1";
IPAddress ip = IPAddress.Parse(host);
IPEndPoint ipe = new IPEndPoint(ip, port);
Socket sSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
sSocket.Bind(ipe);
sSocket.Listen(0);
Console.WriteLine("監聽已經打開,請等待");
//receive message
Socket serverSocket = sSocket.Accept();
Console.WriteLine("連接已經建立");
string recStr = "";
byte[] recByte = new byte[4096];
int bytes = serverSocket.Receive(recByte, recByte.Length, 0);
recStr += Encoding.ASCII.GetString(recByte, 0, bytes);
//send message
Console.WriteLine("服務器端獲得信息:{0}", recStr);
string sendStr = "send to client :hello";
byte[] sendByte = Encoding.ASCII.GetBytes(sendStr);
serverSocket.Send(sendByte, sendByte.Length, 0);
serverSocket.Close();
sSocket.Close();
客戶端:
int port = 6000;
string host = "127.0.0.1";//服務器端ip地址
IPAddress ip = IPAddress.Parse(host);
IPEndPoint ipe = new IPEndPoint(ip, port);
Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
clientSocket.Connect(ipe);
//send message
string sendStr = "send to server : hello,ni hao";
byte[] sendBytes = Encoding.ASCII.GetBytes(sendStr);
clientSocket.Send(sendBytes);
//receive message
string recStr = "";
byte[] recBytes = new byte[4096];
int bytes = clientSocket.Receive(recBytes, recBytes.Length, 0);
recStr += Encoding.ASCII.GetString(recBytes, 0, bytes);
Console.WriteLine(recStr);
clientSocket.Close();
上述服務器端和客戶端建立通信,在互相發送一次信息后通信便結束,而在大家進行的項目中,這樣的通信肯定滿足不了需求。於是接着介紹異步通信,簡單來說就是服務器端和客戶端可以進行多次互發信息的通信而不用擔心通道會關閉。在介紹異步通信時,客戶端和服務器端的連接和上面介紹的同步通信建立連接的方式是一樣的,這里只寫出服務器端和客戶端發送信息的方法和接收信息的方法。(服務器端和客戶端的發送、接收的方法是一樣的)
首先寫出異步連接的方法吧:
public void Connect(IPAddress ip, int port)
{
this.clientSocket.BeginConnect(ip, port, new AsyncCallback(ConnectCallback), this.clientSocket);
}
private void ConnectCallback(IAsyncResult ar)
{
try
{
Socket handler = (Socket)ar.AsyncState;
handler.EndConnect(ar);
}
catch (SocketException ex)
{ }
}
發送信息方法:
public void Send(string data)
{
Send(System.Text.Encoding.UTF8.GetBytes(data));
}
private void Send(byte[] byteData)
{
try
{
int length = byteData.Length;
byte[] head = BitConverter.GetBytes(length);
byte[] data = new byte[head.Length + byteData.Length];
Array.Copy(head, data, head.Length);
Array.Copy(byteData, 0, data, head.Length, byteData.Length);
this.clientSocket.BeginSend(data, 0, data.Length, 0, new AsyncCallback(SendCallback), this.clientSocket);
}
catch (SocketException ex)
{ }
}
private void SendCallback(IAsyncResult ar)
{
try
{
Socket handler = (Socket)ar.AsyncState;
handler.EndSend(ar);
}
catch (SocketException ex)
{ }
}
接收信息的方法:
public void ReceiveData()
{
clientSocket.BeginReceive(MsgBuffer, 0, MsgBuffer.Length, 0, new AsyncCallback(ReceiveCallback), null);
}
private void ReceiveCallback(IAsyncResult ar)
{
try
{
int REnd = clientSocket.EndReceive(ar);
if (REnd > 0)
{
byte[] data = new byte[REnd];
Array.Copy(MsgBuffer, 0, data, 0, REnd);
//在此次可以對data進行按需處理
clientSocket.BeginReceive(MsgBuffer, 0, MsgBuffer.Length, 0, new AsyncCallback(ReceiveCallback), null);
}
else
{
dispose();
}
}
catch (SocketException ex)
{ }
}
private void dispose()
{
try
{
this.clientSocket.Shutdown(SocketShutdown.Both);
this.clientSocket.Close();
}
catch (Exception ex)
{ }
}
異步問題解決了,再寫一個自己在使用過程中經常出現的一個問題。接收的數據包處理問題:在網絡通信中,使用異步進行通信,那么客戶端在接收服務器發送來的數據包的處理上會有一些麻煩,比如粘包、斷包,這是一些小問題,此處簡單寫寫自己處理此問題的一個方法。
粘包處理:
public Hashtable DataTable = new Hashtable();//因為要接收到多個服務器(ip)發送的數據,此處按照ip地址分開存儲發送數據
public void DataArrial(byte[] Data , string ip)
{
try
{
if (Data.Length < 12)//按照需求進行判斷
{
lock (DataTable)
{
if (DataTable.Contains(ip))
{
DataTable[ip] = Data;
return;
}
}
}
if (Data[0] != 0x1F || Data[1] != 0xF1)//標志位(按照需求編寫)
{
if (DataTable.Contains(ip))
{
if (DataTable != null)
{
byte[] oldData = (byte[])DataTable[ip];//取出粘包數據
if (oldData[0] != 0x1F || oldData[1] != 0xF1)
{
return;
}
byte[] newData = new byte[Data.Length + oldData.Length];
Array.Copy(oldData, 0, newData, 0, oldData.Length);
Array.Copy(Data, 0, newData, oldData.Length, Data.Length);//組成新數據數組,先到的數據排在前面,后到的數據放在后面
lock (DataTable)
{
DataTable[ip] = null;
}
DataArrial(newData, ip);
return;
}
}
return;
}
int revDataLength = Data[2];//打算發送數據的長度
int revCount = Data.Length;//接收的數據長度
if (revCount > revDataLength)//如果接收的數據長度大於發送的數據長度,說明存在多幀數據,繼續處理
{
byte[] otherData = new byte[revCount - revDataLength];
Data.CopyTo(otherData, revCount - 1);
Array.Copy(Data, revDataLength, otherData, 0, otherData.Length);
Data = (byte[])Redim(Data, revDataLength);
DataArrial(otherData, ip);
}
if (revCount < revDataLength) //接收到的數據小於要發送的長度
{
if (DataTable.Contains(ip))
{
DataTable[ip] = Data;//更新當前粘包數據
return;
}
}
//此處可以按需進行數據處理
}
catch (Exception ex)
{ }
}
private Array Redim(Array origArray, Int32 desizedSize)
{
//確認每個元素的類型
Type t = origArray.GetType().GetElementType();
//創建一個含有期望元素個數的新數組
//新數組的類型必須匹配數組的類型
Array newArray = Array.CreateInstance(t, desizedSize);
//將原數組中的元素拷貝到新數組中。
Array.Copy(origArray, 0, newArray, 0, Math.Min(origArray.Length, desizedSize));
//返回新數組
return newArray;
}

