原文地址:http://freshflower.iteye.com/blog/2285272、http://freshflower.iteye.com/blog/2285286
一)服務器端
說到Socket通信, 必須要有個服務端, 打開一個端口進行監聽(廢話!) 可能大家都會把socket.Accept方法放在一個while(true)的循環里, 當然也沒有錯, 但個人認為這個不科學, 極大可能地占用服務資源. 贊成的請舉手. 所以我想從另外一個方面解決這個問題. 之后是在MSDN找到SocketAsyncEventArgs的一個實例, 然后拿來改改, 有需要的同學可以看看MSDN的官方實例.https://msdn.microsoft.com/en-us/library/system.net.sockets.socketasynceventargs(v=vs.110).aspx
需要了解客戶端寫法的, 請參考: 客戶端實現http://freshflower.iteye.com/blog/2285286
不多說, 接下來貼代碼, 這個實例中需要用到幾個類:
1. BufferManager類, 管理傳輸流的大小 原封不動地拷貝過來,
using System; using System.Collections.Generic; using System.Net.Sockets; namespace IOCTestServer { class BufferManager { int m_numBytes; // the total number of bytes controlled by the buffer pool byte[] m_buffer; // the underlying byte array maintained by the Buffer Manager Stack<int> m_freeIndexPool; // int m_currentIndex; int m_bufferSize; public BufferManager(int totalBytes, int bufferSize) { m_numBytes = totalBytes; m_currentIndex = 0; m_bufferSize = bufferSize; m_freeIndexPool = new Stack<int>(); } // Allocates buffer space used by the buffer pool public void InitBuffer() { // create one big large buffer and divide that // out to each SocketAsyncEventArg object m_buffer = new byte[m_numBytes]; } // Assigns a buffer from the buffer pool to the // specified SocketAsyncEventArgs object // // <returns>true if the buffer was successfully set, else false</returns> public bool SetBuffer(SocketAsyncEventArgs args) { if (m_freeIndexPool.Count > 0) { args.SetBuffer(m_buffer, m_freeIndexPool.Pop(), m_bufferSize); } else { if ((m_numBytes - m_bufferSize) < m_currentIndex) { return false; } args.SetBuffer(m_buffer, m_currentIndex, m_bufferSize); m_currentIndex += m_bufferSize; } return true; } // Removes the buffer from a SocketAsyncEventArg object. // This frees the buffer back to the buffer pool public void FreeBuffer(SocketAsyncEventArgs args) { m_freeIndexPool.Push(args.Offset); args.SetBuffer(null, 0, 0); } } }
2. SocketEventPool類: 管理SocketAsyncEventArgs的一個應用池. 有效地重復使用.
using System; using System.Collections.Generic; using System.Net.Sockets; namespace IOCTestServer { class SocketEventPool { Stack<SocketAsyncEventArgs> m_pool; public SocketEventPool(int capacity) { m_pool = new Stack<SocketAsyncEventArgs>(capacity); } public void Push(SocketAsyncEventArgs item) { if (item == null) { throw new ArgumentNullException("Items added to a SocketAsyncEventArgsPool cannot be null"); } lock (m_pool) { m_pool.Push(item); } } // Removes a SocketAsyncEventArgs instance from the pool // and returns the object removed from the pool public SocketAsyncEventArgs Pop() { lock (m_pool) { return m_pool.Pop(); } } // The number of SocketAsyncEventArgs instances in the pool public int Count { get { return m_pool.Count; } } public void Clear() { m_pool.Clear(); } } }
3. AsyncUserToken類: 這個可以根據自己的實際情況來定義.主要作用就是存儲客戶端的信息.
using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; namespace IOCTestServer { class AsyncUserToken { /// <summary> /// 客戶端IP地址 /// </summary> public IPAddress IPAddress { get; set; } /// <summary> /// 遠程地址 /// </summary> public EndPoint Remote { get; set; } /// <summary> /// 通信SOKET /// </summary> public Socket Socket { get; set; } /// <summary> /// 連接時間 /// </summary> public DateTime ConnectTime { get; set; } /// <summary> /// 所屬用戶信息 /// </summary> public String UserInfo { get; set; } /// <summary> /// 數據緩存區 /// </summary> public List<byte> Buffer { get; set; } public AsyncUserToken() { this.Buffer = new List<byte>(); } } }
4. SocketManager類: 核心,實現Socket監聽,收發信息等操作.
using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Threading; namespace IOCTestServer { class SocketManager { private int m_maxConnectNum; //最大連接數 private int m_revBufferSize; //最大接收字節數 BufferManager m_bufferManager; const int opsToAlloc = 2; Socket listenSocket; //監聽Socket SocketEventPool m_pool; int m_clientCount; //連接的客戶端數量 Semaphore m_maxNumberAcceptedClients; List<AsyncUserToken> m_clients; //客戶端列表 #region 定義委托 /// <summary> /// 客戶端連接數量變化時觸發 /// </summary> /// <param name="num">當前增加客戶的個數(用戶退出時為負數,增加時為正數,一般為1)</param> /// <param name="token">增加用戶的信息</param> public delegate void OnClientNumberChange(int num, AsyncUserToken token); /// <summary> /// 接收到客戶端的數據 /// </summary> /// <param name="token">客戶端</param> /// <param name="buff">客戶端數據</param> public delegate void OnReceiveData(AsyncUserToken token, byte[] buff); #endregion #region 定義事件 /// <summary> /// 客戶端連接數量變化事件 /// </summary> public event OnClientNumberChange ClientNumberChange; /// <summary> /// 接收到客戶端的數據事件 /// </summary> public event OnReceiveData ReceiveClientData; #endregion #region 定義屬性 /// <summary> /// 獲取客戶端列表 /// </summary> public List<AsyncUserToken> ClientList { get { return m_clients; } } #endregion /// <summary> /// 構造函數 /// </summary> /// <param name="numConnections">最大連接數</param> /// <param name="receiveBufferSize">緩存區大小</param> public SocketManager(int numConnections, int receiveBufferSize) { m_clientCount = 0; m_maxConnectNum = numConnections; m_revBufferSize = receiveBufferSize; // allocate buffers such that the maximum number of sockets can have one outstanding read and //write posted to the socket simultaneously m_bufferManager = new BufferManager(receiveBufferSize * numConnections * opsToAlloc, receiveBufferSize); m_pool = new SocketEventPool(numConnections); m_maxNumberAcceptedClients = new Semaphore(numConnections, numConnections); } /// <summary> /// 初始化 /// </summary> public void Init() { // Allocates one large byte buffer which all I/O operations use a piece of. This gaurds // against memory fragmentation m_bufferManager.InitBuffer(); m_clients = new List<AsyncUserToken>(); // preallocate pool of SocketAsyncEventArgs objects SocketAsyncEventArgs readWriteEventArg; for (int i = 0; i < m_maxConnectNum; i++) { readWriteEventArg = new SocketAsyncEventArgs(); readWriteEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed); readWriteEventArg.UserToken = new AsyncUserToken(); // assign a byte buffer from the buffer pool to the SocketAsyncEventArg object m_bufferManager.SetBuffer(readWriteEventArg); // add SocketAsyncEventArg to the pool m_pool.Push(readWriteEventArg); } } /// <summary> /// 啟動服務 /// </summary> /// <param name="localEndPoint"></param> public bool Start(IPEndPoint localEndPoint) { try { m_clients.Clear(); listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); listenSocket.Bind(localEndPoint); // start the server with a listen backlog of 100 connections listenSocket.Listen(m_maxConnectNum); // post accepts on the listening socket StartAccept(null); return true; } catch (Exception) { return false; } } /// <summary> /// 停止服務 /// </summary> public void Stop() { foreach (AsyncUserToken token in m_clients) { try { token.Socket.Shutdown(SocketShutdown.Both); } catch (Exception) { } } try { listenSocket.Shutdown(SocketShutdown.Both); } catch (Exception) { } listenSocket.Close(); int c_count = m_clients.Count; lock (m_clients) { m_clients.Clear(); } if (ClientNumberChange != null) ClientNumberChange(-c_count, null); } public void CloseClient(AsyncUserToken token) { try { token.Socket.Shutdown(SocketShutdown.Both); } catch (Exception) { } } // 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(); if (!listenSocket.AcceptAsync(acceptEventArg)) { 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) { try { Interlocked.Increment(ref m_clientCount); // Get the socket for the accepted client connection and put it into the //ReadEventArg object user token SocketAsyncEventArgs readEventArgs = m_pool.Pop(); AsyncUserToken userToken = (AsyncUserToken)readEventArgs.UserToken; userToken.Socket = e.AcceptSocket; userToken.ConnectTime = DateTime.Now; userToken.Remote = e.AcceptSocket.RemoteEndPoint; userToken.IPAddress = ((IPEndPoint)(e.AcceptSocket.RemoteEndPoint)).Address; lock (m_clients) { m_clients.Add(userToken); } if (ClientNumberChange != null) ClientNumberChange(1, userToken); if (!e.AcceptSocket.ReceiveAsync(readEventArgs)) { ProcessReceive(readEventArgs); } } catch (Exception me) { //RuncomLib.Log.LogUtils.Info(me.Message + "\r\n" + me.StackTrace); } // Accept the next connection request if (e.SocketError == SocketError.OperationAborted) return; StartAccept(e); } void IO_Completed(object sender, SocketAsyncEventArgs e) { // determine which type of operation just completed and call the associated handler switch (e.LastOperation) { case SocketAsyncOperation.Receive: ProcessReceive(e); break; case SocketAsyncOperation.Send: ProcessSend(e); break; default: throw new ArgumentException("The last operation completed on the socket was not a receive or send"); } } // This method is invoked when an asynchronous receive operation completes. // If the remote host closed the connection, then the socket is closed. // If data was received then the data is echoed back to the client. // private void ProcessReceive(SocketAsyncEventArgs e) { try { // check if the remote host closed the connection AsyncUserToken token = (AsyncUserToken)e.UserToken; if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success) { //讀取數據 byte[] data = new byte[e.BytesTransferred]; Array.Copy(e.Buffer, e.Offset, data, 0, e.BytesTransferred); lock (token.Buffer) { token.Buffer.AddRange(data); } //注意:你一定會問,這里為什么要用do-while循環? //如果當客戶發送大數據流的時候,e.BytesTransferred的大小就會比客戶端發送過來的要小, //需要分多次接收.所以收到包的時候,先判斷包頭的大小.夠一個完整的包再處理. //如果客戶短時間內發送多個小數據包時, 服務器可能會一次性把他們全收了. //這樣如果沒有一個循環來控制,那么只會處理第一個包, //剩下的包全部留在token.Buffer中了,只有等下一個數據包過來后,才會放出一個來. do { //判斷包的長度 byte[] lenBytes = token.Buffer.GetRange(0, 4).ToArray(); int packageLen = BitConverter.ToInt32(lenBytes, 0); if (packageLen > token.Buffer.Count - 4) { //長度不夠時,退出循環,讓程序繼續接收 break; } //包夠長時,則提取出來,交給后面的程序去處理 byte[] rev = token.Buffer.GetRange(4, packageLen).ToArray(); //從數據池中移除這組數據 lock (token.Buffer) { token.Buffer.RemoveRange(0, packageLen + 4); } //將數據包交給后台處理,這里你也可以新開個線程來處理.加快速度. if (ReceiveClientData != null) ReceiveClientData(token, rev); //這里API處理完后,並沒有返回結果,當然結果是要返回的,卻不是在這里, 這里的代碼只管接收. //若要返回結果,可在API處理中調用此類對象的SendMessage方法,統一打包發送.不要被微軟的示例給迷惑了. } while (token.Buffer.Count > 4); //繼續接收. 為什么要這么寫,請看Socket.ReceiveAsync方法的說明 if (!token.Socket.ReceiveAsync(e)) this.ProcessReceive(e); } else { CloseClientSocket(e); } } catch (Exception xe) { //RuncomLib.Log.LogUtils.Info(xe.Message + "\r\n" + xe.StackTrace); } } // This method is invoked when an asynchronous send operation completes. // The method issues another receive on the socket to read any additional // data sent from the client // // <param name="e"></param> private void ProcessSend(SocketAsyncEventArgs e) { if (e.SocketError == SocketError.Success) { // done echoing data back to the client AsyncUserToken token = (AsyncUserToken)e.UserToken; // read the next block of data send from the client bool willRaiseEvent = token.Socket.ReceiveAsync(e); if (!willRaiseEvent) { ProcessReceive(e); } } else { CloseClientSocket(e); } } //關閉客戶端 private void CloseClientSocket(SocketAsyncEventArgs e) { AsyncUserToken token = e.UserToken as AsyncUserToken; lock (m_clients) { m_clients.Remove(token); } //如果有事件,則調用事件,發送客戶端數量變化通知 if (ClientNumberChange != null) ClientNumberChange(-1, token); // close the socket associated with the client try { token.Socket.Shutdown(SocketShutdown.Send); } catch (Exception) { } token.Socket.Close(); // decrement the counter keeping track of the total number of clients connected to the server Interlocked.Decrement(ref m_clientCount); m_maxNumberAcceptedClients.Release(); // Free the SocketAsyncEventArg so they can be reused by another client e.UserToken = new AsyncUserToken(); m_pool.Push(e); } /// <summary> /// 對數據進行打包,然后再發送 /// </summary> /// <param name="token"></param> /// <param name="message"></param> /// <returns></returns> public void SendMessage(AsyncUserToken token, byte[] message) { if (token == null || token.Socket == null || !token.Socket.Connected) return; try { //對要發送的消息,制定簡單協議,頭4字節指定包的大小,方便客戶端接收(協議可以自己定) byte[] buff = new byte[message.Length + 4]; byte[] len = BitConverter.GetBytes(message.Length); Array.Copy(len, buff, 4); Array.Copy(message, 0, buff, 4, message.Length); //token.Socket.Send(buff); //這句也可以發送, 可根據自己的需要來選擇 //新建異步發送對象, 發送消息 SocketAsyncEventArgs sendArg = new SocketAsyncEventArgs(); sendArg.UserToken = token; sendArg.SetBuffer(buff, 0, buff.Length); //將數據放置進去. token.Socket.SendAsync(sendArg); } catch (Exception e) { //RuncomLib.Log.LogUtils.Info("SendMessage - Error:" + e.Message); } } } }
5. 測試代碼
SocketManager m_socket = new SocketManager(200, 1024);
m_socket.Init();
m_socket.Start(new IPEndPoint(IPAddress.Any, 13909));
二)客戶端
與服務器不同的是客戶端的實現需要多個SocketAsyncEventArgs共同協作,至少需要兩個:接收的只需要一個,發送的需要一個,也可以多個,這在多線程中尤為重要,接下來說明。
客戶端一般需要數據的時候,就要發起請求,在多線程環境中,請求服務器一般不希望列隊等候,這樣會大大拖慢程序的處理。如果發送數據包的SocketAsyncEventArgs只有一個,且當他正在工作的時候, 下一個請求也來訪問,這時會拋出異常, 提示當前的套接字正在工作, 所以這不是我們願意看到, 唯有增加SocketAsyncEventArgs對象來解決。 那么接下來的問題就是我怎么知道當前的SocketAsyncEventArgs對象是否正在工作呢. 很簡單,我們新建一個MySocketEventArgs類來繼承它。
1. MySocketEventArgs類
using System; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; using System.Text; namespace Plates.Client.Net { class MySocketEventArgs : SocketAsyncEventArgs { /// <summary> /// 標識,只是一個編號而已 /// </summary> public int ArgsTag { get; set; } /// <summary> /// 設置/獲取使用狀態 /// </summary> public bool IsUsing { get; set; } } }
2. BufferManager類
直接使用服務器端的BufferManager類即可。
3. SocketManager類
using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Plates.Client.Net { class SocketManager: IDisposable { private const Int32 BuffSize = 1024; // The socket used to send/receive messages. private Socket clientSocket; // Flag for connected socket. private Boolean connected = false; // Listener endpoint. private IPEndPoint hostEndPoint; // Signals a connection. private static AutoResetEvent autoConnectEvent = new AutoResetEvent(false); BufferManager m_bufferManager; //定義接收數據的對象 List<byte> m_buffer; //發送與接收的MySocketEventArgs變量定義. private List<MySocketEventArgs> listArgs = new List<MySocketEventArgs>(); private MySocketEventArgs receiveEventArgs = new MySocketEventArgs(); int tagCount = 0; /// <summary> /// 當前連接狀態 /// </summary> public bool Connected { get { return clientSocket != null && clientSocket.Connected; } } //服務器主動發出數據受理委托及事件 public delegate void OnServerDataReceived(byte[] receiveBuff); public event OnServerDataReceived ServerDataHandler; //服務器主動關閉連接委托及事件 public delegate void OnServerStop(); public event OnServerStop ServerStopEvent; // Create an uninitialized client instance. // To start the send/receive processing call the // Connect method followed by SendReceive method. internal SocketManager(String ip, Int32 port) { // Instantiates the endpoint and socket. hostEndPoint = new IPEndPoint(IPAddress.Parse(ip), port); clientSocket = new Socket(hostEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); m_bufferManager = new BufferManager(BuffSize * 2, BuffSize); m_buffer = new List<byte>(); } /// <summary> /// 連接到主機 /// </summary> /// <returns>0.連接成功, 其他值失敗,參考SocketError的值列表</returns> internal SocketError Connect() { SocketAsyncEventArgs connectArgs = new SocketAsyncEventArgs(); connectArgs.UserToken = clientSocket; connectArgs.RemoteEndPoint = hostEndPoint; connectArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnConnect); clientSocket.ConnectAsync(connectArgs); autoConnectEvent.WaitOne(); //阻塞. 讓程序在這里等待,直到連接響應后再返回連接結果 return connectArgs.SocketError; } /// Disconnect from the host. internal void Disconnect() { clientSocket.Disconnect(false); } // Calback for connect operation private void OnConnect(object sender, SocketAsyncEventArgs e) { // Signals the end of connection. autoConnectEvent.Set(); //釋放阻塞. // Set the flag for socket connected. connected = (e.SocketError == SocketError.Success); //如果連接成功,則初始化socketAsyncEventArgs if (connected) initArgs(e); } #region args /// <summary> /// 初始化收發參數 /// </summary> /// <param name="e"></param> private void initArgs(SocketAsyncEventArgs e) { m_bufferManager.InitBuffer(); //發送參數 initSendArgs(); //接收參數 receiveEventArgs.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed); receiveEventArgs.UserToken = e.UserToken; receiveEventArgs.ArgsTag = 0; m_bufferManager.SetBuffer(receiveEventArgs); //啟動接收,不管有沒有,一定得啟動.否則有數據來了也不知道. if (!e.ConnectSocket.ReceiveAsync(receiveEventArgs)) ProcessReceive(receiveEventArgs); } /// <summary> /// 初始化發送參數MySocketEventArgs /// </summary> /// <returns></returns> MySocketEventArgs initSendArgs() { MySocketEventArgs sendArg = new MySocketEventArgs(); sendArg.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed); sendArg.UserToken = clientSocket; sendArg.RemoteEndPoint = hostEndPoint; sendArg.IsUsing = false; Interlocked.Increment(ref tagCount); sendArg.ArgsTag = tagCount; lock (listArgs) { listArgs.Add(sendArg); } return sendArg; } void IO_Completed(object sender, SocketAsyncEventArgs e) { MySocketEventArgs mys = (MySocketEventArgs)e; // determine which type of operation just completed and call the associated handler switch (e.LastOperation) { case SocketAsyncOperation.Receive: ProcessReceive(e); break; case SocketAsyncOperation.Send: mys.IsUsing = false; //數據發送已完成.狀態設為False ProcessSend(e); break; default: throw new ArgumentException("The last operation completed on the socket was not a receive or send"); } } // This method is invoked when an asynchronous receive operation completes. // If the remote host closed the connection, then the socket is closed. // If data was received then the data is echoed back to the client. // private void ProcessReceive(SocketAsyncEventArgs e) { try { // check if the remote host closed the connection Socket token = (Socket)e.UserToken; if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success) { //讀取數據 byte[] data = new byte[e.BytesTransferred]; Array.Copy(e.Buffer, e.Offset, data, 0, e.BytesTransferred); lock (m_buffer) { m_buffer.AddRange(data); } do { //注意: 這里是需要和服務器有協議的,我做了個簡單的協議,就是一個完整的包是包長(4字節)+包數據,便於處理,當然你可以定義自己需要的; //判斷包的長度,前面4個字節. byte[] lenBytes = m_buffer.GetRange(0, 4).ToArray(); int packageLen = BitConverter.ToInt32(lenBytes, 0); if (packageLen <= m_buffer.Count - 4) { //包夠長時,則提取出來,交給后面的程序去處理 byte[] rev = m_buffer.GetRange(4, packageLen).ToArray(); //從數據池中移除這組數據,為什么要lock,你懂的 lock (m_buffer) { m_buffer.RemoveRange(0, packageLen + 4); } //將數據包交給前台去處理 DoReceiveEvent(rev); } else { //長度不夠,還得繼續接收,需要跳出循環 break; } } while (m_buffer.Count > 4); //注意:你一定會問,這里為什么要用do-while循環? //如果當服務端發送大數據流的時候,e.BytesTransferred的大小就會比服務端發送過來的完整包要小, //需要分多次接收.所以收到包的時候,先判斷包頭的大小.夠一個完整的包再處理. //如果服務器短時間內發送多個小數據包時, 這里可能會一次性把他們全收了. //這樣如果沒有一個循環來控制,那么只會處理第一個包, //剩下的包全部留在m_buffer中了,只有等下一個數據包過來后,才會放出一個來. //繼續接收 if (!token.ReceiveAsync(e)) this.ProcessReceive(e); } else { ProcessError(e); } } catch (Exception xe) { Console.WriteLine(xe.Message); } } // This method is invoked when an asynchronous send operation completes. // The method issues another receive on the socket to read any additional // data sent from the client // // <param name="e"></param> private void ProcessSend(SocketAsyncEventArgs e) { if (e.SocketError != SocketError.Success) { ProcessError(e); } } #endregion #region read write // Close socket in case of failure and throws // a SockeException according to the SocketError. private void ProcessError(SocketAsyncEventArgs e) { Socket s = (Socket)e.UserToken; if (s.Connected) { // close the socket associated with the client try { s.Shutdown(SocketShutdown.Both); } catch (Exception) { // throws if client process has already closed } finally { if (s.Connected) { s.Close(); } connected = false; } } //這里一定要記得把事件移走,如果不移走,當斷開服務器后再次連接上,會造成多次事件觸發. foreach (MySocketEventArgs arg in listArgs) arg.Completed -= IO_Completed; receiveEventArgs.Completed -= IO_Completed; if (ServerStopEvent != null) ServerStopEvent(); } // Exchange a message with the host. internal void Send(byte[] sendBuffer) { if (connected) { //先對數據進行包裝,就是把包的大小作為頭加入,這必須與服務器端的協議保持一致,否則造成服務器無法處理數據. byte[] buff = new byte[sendBuffer.Length + 4]; Array.Copy(BitConverter.GetBytes(sendBuffer.Length), buff, 4); Array.Copy(sendBuffer, 0, buff, 4, sendBuffer.Length); //查找有沒有空閑的發送MySocketEventArgs,有就直接拿來用,沒有就創建新的.So easy! MySocketEventArgs sendArgs = listArgs.Find(a => a.IsUsing == false); if (sendArgs == null) { sendArgs = initSendArgs(); } lock (sendArgs) //要鎖定,不鎖定讓別的線程搶走了就不妙了. { sendArgs.IsUsing = true; sendArgs.SetBuffer(buff, 0, buff.Length); } clientSocket.SendAsync(sendArgs); } else { throw new SocketException((Int32)SocketError.NotConnected); } } /// <summary> /// 使用新進程通知事件回調 /// </summary> /// <param name="buff"></param> private void DoReceiveEvent(byte[] buff) { if (ServerDataHandler == null) return; //ServerDataHandler(buff); //可直接調用. //但我更喜歡用新的線程,這樣不拖延接收新數據. Thread thread = new Thread(new ParameterizedThreadStart((obj) => { ServerDataHandler((byte[])obj); })); thread.IsBackground = true; thread.Start(buff); } #endregion #region IDisposable Members // Disposes the instance of SocketClient. public void Dispose() { autoConnectEvent.Close(); if (clientSocket.Connected) { clientSocket.Close(); } } #endregion } }
4.使用類 Request
這是原作者給出了靜態類
using Newtonsoft.Json; using Plates.Common; using Plates.Common.Base; using Plates.Common.Beans; using RuncomLib.File; using RuncomLib.Log; using RuncomLib.Text; using System; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Timers; namespace Plates.Client.Net { class Request { //定義,最好定義成靜態的, 因為我們只需要一個就好 static SocketManager smanager = null; static UserInfoModel userInfo = null; //定義事件與委托 public delegate void ReceiveData(object message); public delegate void ServerClosed(); public static event ReceiveData OnReceiveData; public static event ServerClosed OnServerClosed; /// <summary> /// 心跳定時器 /// </summary> static System.Timers.Timer heartTimer = null; /// <summary> /// 心跳包 /// </summary> static ApiResponse heartRes = null; /// <summary> /// 判斷是否已連接 /// </summary> public static bool Connected { get { return smanager != null && smanager.Connected; } } /// <summary> /// 已登錄的用戶信息 /// </summary> public static UserInfoModel UserInfo { get { return userInfo; } } #region 基本方法 /// <summary> /// 連接到服務器 /// </summary> /// <returns></returns> public static SocketError Connect() { if (Connected) return SocketError.Success; //我這里是讀取配置, string ip = Config.ReadConfigString("socket", "server", ""); int port = Config.ReadConfigInt("socket", "port", 13909); if (string.IsNullOrWhiteSpace(ip) || port <= 1000) return SocketError.Fault; //創建連接對象, 連接到服務器 smanager = new SocketManager(ip, port); SocketError error = smanager.Connect(); if (error == SocketError.Success){ //連接成功后,就注冊事件. 最好在成功后再注冊. smanager.ServerDataHandler += OnReceivedServerData; smanager.ServerStopEvent += OnServerStopEvent; } return error; } /// <summary> /// 斷開連接 /// </summary> public static void Disconnect() { try { smanager.Disconnect(); } catch (Exception) { } } /// <summary> /// 發送請求 /// </summary> /// <param name="request"></param> /// <returns></returns> public static bool Send(ApiResponse request) { return Send(JsonConvert.SerializeObject(request)); } /// <summary> /// 發送消息 /// </summary> /// <param name="message">消息實體</param> /// <returns>True.已發送; False.未發送</returns> public static bool Send(string message) { if (!Connected) return false; byte[] buff = Encoding.UTF8.GetBytes(message); //加密,根據自己的需要可以考慮把消息加密 //buff = AESEncrypt.Encrypt(buff, m_aesKey); smanager.Send(buff); return true; } /// <summary> /// 發送字節流 /// </summary> /// <param name="buff"></param> /// <returns></returns> static bool Send(byte[] buff) { if (!Connected) return false; smanager.Send(buff); return true; } /// <summary> /// 接收消息 /// </summary> /// <param name="buff"></param> private static void OnReceivedServerData(byte[] buff) { //To do something //你要處理的代碼,可以實現把buff轉化成你具體的對象, 再傳給前台 if (OnReceiveData != null) OnReceiveData(buff); } /// <summary> /// 服務器已斷開 /// </summary> private static void OnServerStopEvent() { if (OnServerClosed != null) OnServerClosed(); } #endregion #region 心跳包 //心跳包也是很重要的,看自己的需要了, 我只定義出來, 你自己找個地方去調用吧 /// <summary> /// 開啟心跳 /// </summary> private static void StartHeartbeat() { if (heartTimer == null) { heartTimer = new System.Timers.Timer(); heartTimer.Elapsed += TimeElapsed; } heartTimer.AutoReset = true; //循環執行 heartTimer.Interval = 30 * 1000; //每30秒執行一次 heartTimer.Enabled = true; heartTimer.Start(); //初始化心跳包 heartRes = new ApiResponse((int)ApiCode.心跳); heartRes.data = new Dictionary<string, object>(); heartRes.data.Add("beat", Function.Base64Encode(userInfo.nickname + userInfo.userid + DateTime.Now.ToString("HH:mm:ss"))); } /// <summary> /// 定時執行 /// </summary> /// <param name="source"></param> /// <param name="e"></param> static void TimeElapsed(object source, ElapsedEventArgs e) { Request.Send(heartRes); } #endregion } }
5.其它臨時代碼
因文章中某些類不可用,故應急添加一些類和相應的修改,供測試通過。
ApiResponse類:
using System.Collections.Generic; namespace IOCTestClient { internal class ApiResponse { private int 心跳; public ApiResponse(int 心跳) { this.心跳 = 心跳; } public Dictionary<string, object> data { get; set; } } }
客戶端和服務器中的UserInfoModel 類型臨時改為 String。
6.測試代碼
Request.Connect();
Request.Send("XXXXXXXX");
System.Console.ReadKey();
對照資源:http://blog.csdn.net/sqldebug_fan/article/details/17556353,包括客戶端和服務器,更細致一些。
在實際測試中,對照的兩份代碼都是用VS2015測試通過,但因種種原因,效率方面未測試。