原文地址: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 = ;
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 > )
{
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, , );
}
}
}

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 = ;
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 = ;
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 = ; 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(, 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 > && e.SocketError == SocketError.Success)
{
//读取数据
byte[] data = new byte[e.BytesTransferred];
Array.Copy(e.Buffer, e.Offset, data, , e.BytesTransferred);
lock (token.Buffer)
{
token.Buffer.AddRange(data);
}
//注意:你一定会问,这里为什么要用do-while循环?
//如果当客户发送大数据流的时候,e.BytesTransferred的大小就会比客户端发送过来的要小,
//需要分多次接收.所以收到包的时候,先判断包头的大小.够一个完整的包再处理.
//如果客户短时间内发送多个小数据包时, 服务器可能会一次性把他们全收了.
//这样如果没有一个循环来控制,那么只会处理第一个包,
//剩下的包全部留在token.Buffer中了,只有等下一个数据包过来后,才会放出一个来.
do
{
//判断包的长度
byte[] lenBytes = token.Buffer.GetRange(, ).ToArray();
int packageLen = BitConverter.ToInt32(lenBytes, );
if (packageLen > token.Buffer.Count - )
{ //长度不够时,退出循环,让程序继续接收
break;
} //包够长时,则提取出来,交给后面的程序去处理
byte[] rev = token.Buffer.GetRange(, packageLen).ToArray();
//从数据池中移除这组数据
lock (token.Buffer)
{
token.Buffer.RemoveRange(, packageLen + );
}
//将数据包交给后台处理,这里你也可以新开个线程来处理.加快速度.
if (ReceiveClientData != null)
ReceiveClientData(token, rev);
//这里API处理完后,并没有返回结果,当然结果是要返回的,却不是在这里, 这里的代码只管接收.
//若要返回结果,可在API处理中调用此类对象的SendMessage方法,统一打包发送.不要被微软的示例给迷惑了.
} while (token.Buffer.Count > ); //继续接收. 为什么要这么写,请看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(-, 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 + ];
byte[] len = BitConverter.GetBytes(message.Length);
Array.Copy(len, buff, );
Array.Copy(message, , buff, , message.Length);
//token.Socket.Send(buff); //这句也可以发送, 可根据自己的需要来选择
//新建异步发送对象, 发送消息
SocketAsyncEventArgs sendArg = new SocketAsyncEventArgs();
sendArg.UserToken = token;
sendArg.SetBuffer(buff, , 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 = ; // 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 = ; /// <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 * , 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 = ;
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 > && e.SocketError == SocketError.Success)
{
//读取数据
byte[] data = new byte[e.BytesTransferred];
Array.Copy(e.Buffer, e.Offset, data, , e.BytesTransferred);
lock (m_buffer)
{
m_buffer.AddRange(data);
} do
{
//注意: 这里是需要和服务器有协议的,我做了个简单的协议,就是一个完整的包是包长(4字节)+包数据,便于处理,当然你可以定义自己需要的;
//判断包的长度,前面4个字节.
byte[] lenBytes = m_buffer.GetRange(, ).ToArray();
int packageLen = BitConverter.ToInt32(lenBytes, );
if (packageLen <= m_buffer.Count - )
{
//包够长时,则提取出来,交给后面的程序去处理
byte[] rev = m_buffer.GetRange(, packageLen).ToArray();
//从数据池中移除这组数据,为什么要lock,你懂的
lock (m_buffer)
{
m_buffer.RemoveRange(, packageLen + );
}
//将数据包交给前台去处理
DoReceiveEvent(rev);
}
else
{ //长度不够,还得继续接收,需要跳出循环
break;
}
} while (m_buffer.Count > );
//注意:你一定会问,这里为什么要用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 + ];
Array.Copy(BitConverter.GetBytes(sendBuffer.Length), buff, );
Array.Copy(sendBuffer, , buff, , 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, , 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", );
if (string.IsNullOrWhiteSpace(ip) || port <= ) 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秒执行一次
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测试通过,但因种种原因,效率方面未测试。

(转)C#SocketAsyncEventArgs实现高效能多并发TCPSocket通信的更多相关文章

  1. C#SocketAsyncEventArgs实现高效能多并发TCPSocket通信 (服务器实现)

    http://freshflower.iteye.com/blog/2285272 想着当初到处找不到相关资料来实现.net的Socket通信的痛苦与心酸, 于是将自己写的代码公布给大家, 让大家少走 ...

  2. 高效能人士必知铁律--note

    偶然看到了<高效能人士 必知铁律>这本书,我比较少看成功学,但是这本书把很多著名的成功学书籍整理出来,有时会让你耳目一新,有些观点尽管是常识,但是却加深了你对它们的理解,比如: 只要在积极 ...

  3. 高效能团队协作的JIRA实践

    http://www.csdn.net/article/2015-05-21/2824739?utm_source=tuicool 高效能团队是企业生存和发展的基石.任何企业面对当下的激烈竞争,要想脱 ...

  4. [转]如何写出高效能TSQL -深入浅出SQL Server Relational Engine (含 SQL 2014 in-memory Engine)

    [转]如何写出高效能TSQL -深入浅出SQL Server Relational Engine (含 SQL 2014 in-memory Engine) http://blogs.technet. ...

  5. 《高效能程序员的修炼》读后感 By Yong Zhang

    想不到我工作中经常GOOGLE搜寻技术问题的stack overflow网站的创办人竟然是<高效能程序员的修炼>一书的作者!看了一遍全书,果然名不虚传. 本书更多的从人文角度而非技术角度去 ...

  6. 高效能Windows人士的N个习惯之一:启动篇

    接触电脑十多年,经历了各种折腾阶段,这几年开始沉静下来,不再追求花哨的界面与应用,只注重工作的效率,逐渐养成了一套自己的操作习惯,感觉不错,特撰文分享.标题借用了一下<高效能人士的七个习惯> ...

  7. DevOps|高效能敏捷交付组织:特性团队(FeatureTeam)+Scrum

    这是<研发效能组织能力建设>的第三篇.特性团队和Scrum,这两个定义我们在之前的文章中都详细介绍了.这两个组织模式或者说管理实践,我都用过所以有些时候特别有感触.书本上纯粹的模式很容易理 ...

  8. [转载] 高流量大并发Linux TCP 性能调优

    原文: http://cenwj.com/2015/2/25/19 本文参考文章为: 优化Linux下的内核TCP参数来提高服务器负载能力 Linux Tuning 本文所面对的情况为: 高并发数 高 ...

  9. 高效能程序员的七个习惯【csdn】

    从网上看到的,感觉挺不错的! Beautiful is better than ugly. Explicit is better than implicit. Simple is better tha ...

随机推荐

  1. 数学 它的内容,方法和意义 第一卷 (A. D. 亚历山大洛夫 著)

    第一章 数学概观 (已看) 1. 数学的特点 2. 算术 3. 几何 4. 算术和几何 5. 初等数学时代 6. 变量的数学 7. 现代数学 8. 数学的本质 9. 数学发展的规律性 第二章 数学分析 ...

  2. Unity3D用户手册 学习

  3. ESB雏形 -- 项目企业服务总线初始

    今天要厚着脸皮给大家推荐一个自己做的通信中间件——ServiceAnt,目前已经在我们团队的两个产品线上投入了使用. ServiceAnt是什么 它最初的定位是ESB(企业服务总线),但目前还没有达到 ...

  4. nginx配置.htaccess伪静态

    https://blog.csdn.net/moqiang02/article/details/37695775

  5. maven的profile

    https://elim.iteye.com/blog/1900568 简介:配置不同环境要使用的profile,再idea操作画面可以选

  6. @RequestMapping、@Responsebody、@RequestBody和@PathVariable详解(转)

    一.预备知识:@RequestMapping RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上.用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径. @Requ ...

  7. 非GUI模式

    先启动jmeter的图形界面. 在自动时可以看到控制台输出的信息. 1.提示不用使用GUI进行负载测试. 2.命令行格式.   打开之前保存的百度的测试脚本.   线程数调为100,循环次数是2. R ...

  8. C++Builder XE7 up1 简单测试

    很久没用BCB了, 新装了BCBXE7up1试试了,发现有点找不到北了,好像与BCB6的一些默认设置项不一样,编译了一个空APP,提示找不到bpl 和 dll. 设置为不带包编译后,还是提示DLL找不 ...

  9. 解决 Sublime text3 中文显示乱码问题【亲测可用】

    一.安装包管理器 使用Ctrl+`快捷键或者通过View->Show Console菜单打开命令行,粘贴如下代码   import urllib.request,os; pf = 'Packag ...

  10. Simple Logging Facade for Java 简单日志门面(Facade)

    SLF4J是为各种 loging APIs提供一个简单统一的接口,从而使得最终用户能够在部署的时候配置自己希望的loging APIs实现.Logging API实现既可以选择直接实现SLF4J接口的 ...