使用Socket 進行實時通信,若是使用APM,只須要一個Socket類便可。若是使用EAP,則還須要一個SocketAsyncEventArgs類。本文以EAP的方式展開討論。編程
Socket類提供了不少屬性和操做方法,但Socket類並無提供多少自身的狀態維護,好比Connected 屬性,按文檔說法:」獲取一個值,該值指示是否 Socket 鏈接到遠程主機從上次以來 Send 或 Receive 操做「,也就說這個值只表示了上次I/O的狀態,而不是當前的,還有像Blocking 屬性,阻塞的模式纔有用。Socket類其實就是Windows socket api的一個函數及狀態描述的集合。Socket類只是給咱們提供了網絡通信的能力,並無鏈接管理功能,因此Socket編程就是個制定協議、建立會話管理、進行數據輸入輸出(封包、解包)的過程,可能還要加上業務邏輯。api
服務端的主要的幾個功能是:端口監聽、接受鏈接、會話管理、數據傳輸管理。服務器
這部分代碼比較公式化,無非啓動監聽後,將Accect動做交給SocketAsyncEventArgs類實例來完成,觸發Completed事件:網絡
public void Start(IPEndPoint localEndPoint) { // create the socket which listens for incoming connections listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); listenSocket.Bind(localEndPoint); // start the server with a listen backlog of 100 connections listenSocket.Listen(100); // post accepts on the listening socket StartAccept(null); //Console.WriteLine("{0} connected sockets with one outstanding receive posted to each....press any key", m_outstandingReadCount); Console.WriteLine("Press any key to terminate the server process...."); Console.ReadKey(); } // Begins an operation to accept a connection request from the client // // <param name="acceptEventArg">The context object to use when issuing // the accept operation on the server's listening socket</param> public void StartAccept(SocketAsyncEventArgs acceptEventArg) { if (acceptEventArg == null) { acceptEventArg = new SocketAsyncEventArgs(); acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptEventArg_Completed); } else { // socket must be cleared since the context object is being reused acceptEventArg.AcceptSocket = null; } m_maxNumberAcceptedClients.WaitOne(); bool willRaiseEvent = listenSocket.AcceptAsync(acceptEventArg); if (!willRaiseEvent) { ProcessAccept(acceptEventArg); } } // This method is the callback method associated with Socket.AcceptAsync // operations and is invoked when an accept operation is complete // void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e) { ProcessAccept(e); } private void ProcessAccept(SocketAsyncEventArgs e) { Interlocked.Increment(ref m_numConnectedSockets); Console.WriteLine("Client connection accepted. There are {0} clients connected to the server", m_numConnectedSockets); // Get the socket for the accepted client connection and put it into the //ReadEventArg object user token SocketAsyncEventArgs readEventArgs = m_readWritePool.Pop(); ((AsyncUserToken)readEventArgs.UserToken).Socket = e.AcceptSocket; // As soon as the client is connected, post a receive to the connection bool willRaiseEvent = e.AcceptSocket.ReceiveAsync(readEventArgs); if(!willRaiseEvent){ ProcessReceive(readEventArgs); } // Accept the next connection request StartAccept(e); }
對於長鏈接的話,你須要隨時知道客戶端狀態,好比長時間無數據傳輸的狀況多是網絡已經斷開了,須要將這個鏈接給釋放掉。根據Connected 屬性描述,說明Socket自身並不能感知鏈接狀況,只能經過讀寫才能肯定網絡是否斷開。若是阻塞的方式讀寫數據,那在阻塞或讀寫的時候,鏈接斷開後會發生SocketException或IOException,這容易肯定但網絡的狀況,但異步模式會有點差異,首先沒有讀寫的狀況下不會發生異常,在只有異步讀的狀況下,若是客戶沒有作Close動做直接斷開,服務端也不會報異常,也不會觸發Completed 事件,而後這個鏈接就一直掛在那邊,只到有讀寫動做。但從程序的可靠性上來說,咱們不能經過業務邏輯的讀寫來肯定鏈接狀態,而是經過一個獨立讀寫機制來實現Socket類未提供的鏈接管理功能。這在Mina.net框架裏是做爲一個KeepAlived的過慮器來實現的,在超過設定讀寫空閒時間以後往客戶端發送心跳包,以經過讀寫來確認鏈接的是否正常。當寫入數據的時候,若是這個鏈接已經斷開,則會觸發寫操做的SocketAsyncEventArgs.Completed ,事件中獲得的SocketAsyncEventArgs.BytesTransferred==0以肯定客戶端已經斷開鏈接。框架
示例代碼:異步
//用定時器定時寫數據
void KeepAlive(){ new Timer((a) => { byte[] ping = new byte[] { 2,0,3,0,0,0x41,3 }; Send(ping); } }, null, 0, 1000); }
//socket
private void Send(byte[] buffer) { var e1 = new SocketAsyncEventArgs(); e1.Completed += (c, d) => { if (d.BytesTransferred == 0) {
socket.Shutdown(SocketShutdown.Both);
socket.Close();函數
} else { if (e1.SocketError != SocketError.Success) { log.Error(e1.SocketError); } } }; e1.SetBuffer(buffer, 0, buffer.Length); if (!client.SendAsync(e1)) { if (e1.BytesTransferred == 0) { socket.Shutdown(SocketShutdown.Both);
socket.Close(); } else { if (e1.SocketError != SocketError.Success) { log.Error(e1.SocketError); } } } }
客戶端鏈接到服務器也分同步鏈接與異步鏈接,但客戶端的異步鏈接存在一個問題,那就是若是服務器不可到達,異步鏈接的方式是沒有任何反饋的。以下代碼,若是遠程主機沒法鏈接,則不會拋出異常,也不會進入到Connected方法:post
private void Connected(SocketAsyncEventArgs e) { if (e.BytesTransferred == 0) { TryConnectAsyn(); } else { if (e.SocketError == SocketError.Success) { ushort h = 32; ushort f = 1; byte[] len = BitConverter.GetBytes(h); byte[] fid = BitConverter.GetBytes(f); byte[] simb = GetIdBytes(Sim); byte[] login = new byte[] { 2,len[1],len[0],fid[1],fid[0], 0x75,simb[2],simb[3],simb[4],simb[5], simb[0],simb[1],simb[2],simb[3],simb[4],simb[5],116,236,123,222,0x17,0x30,0x30,0x30, 0x30, 0x30 ,0x30 ,0x30, 0x30, 0x30, 0x31,0x0C, 0x22, 0x38, 0x4E,3 }; Send(login); //StartReceive(); } else { TryConnectAsyn(); } } } private void ConnectAsyn() { client = new Socket(SocketType.Stream, ProtocolType.Tcp); try { var cs = new SocketAsyncEventArgs(); cs.RemoteEndPoint = new IPEndPoint(IPAddress.Any, 5550); cs.Completed += (a, b) => { Connected(cs); }; if (!client.ConnectAsync(cs)) { Connected(cs); } } catch (SocketException ex) { client.Close(); TryConnectAsyn(); } catch (Exception ex) { } }
客戶端的鏈接狀態管理與服務的實現相似,區別是服務端是管理多個鏈接,客戶端只要管理一個鏈接。ui
這裏主要一些粘包的處理,這個網上有不少,不贅述。