Socket通信的基本流程具體步驟如下所示
1.開啟一個鏈接之前,需要先完成Socket和Bind兩個步驟。Socket是新建一個套接字,Bind指定套接字的IP和端口(客戶端在調用Connect時會由系統分配端口,因此可以省去Bind)。
2.服務端通過Listen開啟監聽,等待客戶端接入。
3.客戶端通過Connect連接服務器, 服務端通過Accept接收客戶端連接。 在connect-accept過程中, 操作系統將會進行三次握手
4.客戶端和服務端通過write和read發送和接收數據, 操作系統將會完成TCP數據的確認、 重發等步驟。
5.通過close關閉連接, 操作系統會進行四次揮手。
異步原理
套接字編程原理:延續文件作用思想,打開-讀寫-關閉的模式。
C/S編程模式如下:
Ø 服務器端:
打開通信通道,告訴本地機器,願意在該通道上接受客戶請求——監聽,等待客戶請求——接受請求,創建專用鏈接進行讀寫——處理完畢,關閉專用鏈接——關閉通信通道(當然其中監聽到關閉專用鏈接可以重復循環)
Ø 客戶端:打開通信通道,連接服務器——數據交互——關閉信道。
Socket通信方式:
Ø 同步:客戶端在發送請求之后必須等到服務器回應之后才可以發送下一條請求。串行運行
Ø 異步:客戶端請求之后,不必等到服務器回應之后就可以發送下一條請求。並行運行
套接字模式:
Ø 阻塞:執行此套接字調用時,所有調用函數只有在得到返回結果之后才會返回。在調用結果返回之前,當前進程會被掛起。即此套接字一直被阻塞在網絡調用上。
Ø 非阻塞:執行此套接字調用時,調用函數即使得不到得到返回結果也會返回。
套接字工作步驟:
Ø 服務器監聽:監聽時服務器端套接字並不定位具體客戶端套接字,而是處於等待鏈接的狀態,實時監控網絡狀態
Ø 客戶端鏈接:客戶端發出鏈接請求,要連接的目標是服務器端的套接字。為此客戶端套接字必須描述服務器端套接字的服務器地址與端口號。
Ø 鏈接確認:是指服務器端套接字監聽到客戶端套接字的鏈接請求時,它響應客戶端鏈接請求,建立一個新的線程,把服務器端套接字的描述發送給客戶端,一旦客戶端確認此描述,則鏈接建立好。而服務器端的套接字繼續處於監聽狀態,繼續接受其他客戶端套接字請求。
異步通信例子(部分代碼)
static void Main(string[] args) { //創建多個鏈接池,表示創建maxConn最大客戶端 conns = new Conn[maxConn]; for (int i = 0; i < maxConn; i++) { conns[i] = new Conn(); } serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 11000); serverSocket.Bind(ipEndPoint);//綁定IP和端口號 serverSocket.Listen(maxConn);//開始監聽端口,0為監聽無限個客戶端 Console.WriteLine("[服務器]啟動成功"); //開始調用異步連接 serverSocket.BeginAccept(AcceptCb, null); //按下quit退出程序 while (true) { if (Console.ReadLine() == "quit") return; } }
/// <summary> /// Accept回調 /// </summary> /// <param name="ar"></param> static void AcceptCb(IAsyncResult ar) { try { Socket socket = serverSocket.EndAccept(ar);//嘗試進行異步連接 int index = NewIndex(); if (index < 0) { socket.Close(); Console.Write("[警告]鏈接已滿"); } else { Conn conn = conns[index]; conn.Init(socket); string adr = conn.GetAdress(); Console.WriteLine("客戶端連接 [" + adr + "] conn池ID:" + index); conn.socket.BeginReceive(conn.readBuff, conn.buffCount, conn.BuffRemain(), SocketFlags.None, ReceiveCb, conn); } serverSocket.BeginAccept(AcceptCb, null); } catch (Exception e) { Console.WriteLine("AcceptCb失敗:" + e.Message); } }
static void ReceiveCb(IAsyncResult ar) { Conn conn = (Conn)ar.AsyncState; try { int count = conn.socket.EndReceive(ar); //關閉信號 if (count <= 0) { Console.WriteLine("收到 [" + conn.GetAdress() + "] 斷開鏈接"); conn.Close(); return; } //數據處理 string str = Encoding.UTF8.GetString(conn.readBuff, 0, count); Console.WriteLine("收到 [" + conn.GetAdress() + "] 數據:" + str); str = conn.GetAdress() + "發送的:" + str; byte[] bytes = System.Text.Encoding.UTF8.GetBytes("接收到" + str); //廣播 /* for (int i = 0; i < conns.Length; i++) { if (conns[i] == null) continue; if (!conns[i].isUse) continue; Console.WriteLine("將消息轉播給 " + conns[i].GetAdress()); conns[i].socket.Send(bytes); }*/ //點播 for (int i = 0; i<=0;i++) { if (conns[i] == null) continue; if (!conns[i].isUse) continue; Console.WriteLine("將消息轉播給 " + conns[i].GetAdress()); conns[i].socket.Send(bytes); } //繼續接收 conn.socket.BeginReceive(conn.readBuff, conn.buffCount, conn.BuffRemain(),SocketFlags.None, ReceiveCb, conn); } catch (Exception e) { Console.WriteLine("收到 [" + conn.GetAdress() + "] 斷開鏈接"); conn.Close(); } }