与服务器不同的是客户端的实现需要多个SocketAsyncEventArgs共同协作,至少需要两个:接收的只需要一个,发送的需要一个,也可以多个,这在多线程中尤为重要,接下来说明。
客户端一般需要数据的时候,就要发起请求,在多线程环境中,请求服务器一般不希望列队等候,这样会大大拖慢程序的处理。如果发送数据包的SocketAsyncEventArgs只有一个,且当他正在工作的时候, 下一个请求也来访问,这时会抛出异常, 提示当前的套接字正在工作, 所以这不是我们愿意看到, 唯有增加SocketAsyncEventArgs对象来解决。 那么接下来的问题就是我怎么知道当前的SocketAsyncEventArgs对象是否正在工作呢. 很简单,我们新建一个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; }
- }
- }
接下来,我们还需要BufferManager类,这个类已经在服务端贴出来了,与服务端是一样的, 再贴一次:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Net.Sockets;
- using System.Text;
- using System.Threading.Tasks;
- namespace Plates.Client.Net
- {
- 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);
- }
- }
- }
接下来是重点实现了,别的不多说,看代码:
- 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
- }
- }
好了, 怎么使用, 那是再简单不过的事了, 当然连接同一个服务器的同一端口, 这个类你只需要初始化一次就可以了, 不要创建多个, 这样太浪费资源. 上面是定义了通讯的基础类, 那么接下来就是把相关的方法再包装一下, 做成供前台方便调用的含有静态方法的类就OK了.
- 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
- }
- }