使用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
这里主要一些粘包的处理,这个网上有不少,不赘述。