400分最高悬赏,不够可加,求助IOCP完成端口达人,困扰了好几天的问题,大家来分析
最近要做一个高并发长连接的TCP服务器,在网上学习了一下后,以某CSDN博主的代码为框架,在此先给出原文地址,以示尊重:http://blog.csdn.net/zhujunxxxx/article/details/43573879
在开始贴出我的代码前,先说说问题:
概况: 预计会有上千的GRPS终端连接到本服务器,并保持长时间连接,也就是这是一个工业应用,主要是维持设备在线并采集数据。在使用同步通信时没有问题,当然一旦设备数据稍微大一点,比如到了20台以上以后,就会出现严重的卡顿现象。也就不多说了。所以必须用IOCP。然后问题如下:
1. 一旦使用IOCP,或者自己用异步Socket去试图编写一个服务器,那么会发现GPRS设备每隔一段时间就换端口发起重连,也 就是原SOCKET通道断了。不管采用移动的还是联通的,都有这个问题。跟踪调试发现是在recieve时,得到一个connection reset错,于是IOCP关闭这个SOCKET,一旦关闭,设备立即有反应,马上发起一个新的连接,这就是看到的换端口连接的问题。但该设备如果用以太网连接,则一切正常。
2. 调用IOCP服务器的异步发送函数,居然得不到IOCOMPLETE事件。
下面贴出代码,在出现问题的地方有注释,希望大家一起分析。为节省篇幅,代码前面不重复,可以参看上文贴的地址
从这里开始:
public void Start()
{
...见原文
}
#endregion
#region Stop
/// <summary>
/// 停止服务
/// </summary>
public void Stop()
{
...见原文
}
#endregion
#region Accept
/// <summary>
/// 从客户端开始接受一个连接操作
/// </summary>
private void StartAccept(SocketAsyncEventArgs asyniar)
{
if (asyniar == null)
{
asyniar = new SocketAsyncEventArgs();
asyniar.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
}
else
{
//socket must be cleared since the context object is being reused
asyniar.AcceptSocket = null;
}
_maxAcceptedClients.WaitOne();
if (!_serverSock.AcceptAsync(asyniar))
{
ProcessAccept(asyniar);
//如果I/O挂起等待异步则触发AcceptAsyn_Asyn_Completed事件
//此时I/O操作同步完成,不会触发Asyn_Completed事件,所以指定BeginAccept()方法
}
}
/// <summary>
/// accept 操作完成时回调函数
/// </summary>
/// <param name="sender">Object who raised the event.</param>
/// <param name="e">SocketAsyncEventArg associated with the completed accept operation.</param>
private void OnAcceptCompleted(object sender, SocketAsyncEventArgs e)
{
ProcessAccept(e);
}
/// <summary>
/// 监听Socket接受处理
/// </summary>
/// <param name="e">SocketAsyncEventArg associated with the completed accept operation.</param>
private void ProcessAccept(SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
Socket s = e.AcceptSocket;//和客户端关联的socket
if (s.Connected)
{
try
{
Interlocked.Increment(ref _clientCount);//原子操作加1
SocketAsyncEventArgs asyniar = _objectPool.Pop();
asyniar.UserToken = s;
Log4Debug(String.Format("客户 {0} 连入, 共有 {1} 个连接。", s.RemoteEndPoint.ToString(), _clientCount));
if (!s.ReceiveAsync(asyniar))//投递接收请求
{
ProcessReceive(asyniar);
}
}
catch (SocketException ex)
{
Log4Debug(String.Format("接收客户 {0} 数据出错, 异常信息: {1} 。", s.RemoteEndPoint, ex.ToString()));
//TODO 异常处理
}
//投递下一个接受请求
StartAccept(e);
}
}
}
#endregion
#region 发送数据
/// <summary>
/// 异步的发送数据
/// </summary>
/// <param name="e"></param>
/// <param name="data"></param>
public void Send(SocketAsyncEventArgs e, byte[] data)
{
if (e.SocketError == SocketError.Success)
{
Socket s = e.AcceptSocket;//和客户端关联的socket
if (s.Connected)
{
Array.Copy(data, 0, e.Buffer, 0, data.Length);//设置发送数据
//e.SetBuffer(data, 0, data.Length); //设置发送数据
if (!s.SendAsync(e))//投递发送请求,这个函数有可能同步发送出去,这时返回false,并且不会引发SocketAsyncEventArgs.Completed事件
{
// 同步发送时处理发送完成事件
ProcessSend(e);
}
else
{
原文调用了这个函数,很奇怪的是当我用这个函数去发起server.Send(e,b)这样的异步发送时,这个函数会跳转到这里,也就是虽然我调用了异步发送,但实际是同步发送的,于是关闭SOCKET,我不知道为什么要关闭SOCKET,这将导致我后续动作无法完成,提示SOCKET已经关闭了,这里大家看看
//CloseClientSocket(e);
}
}
}
}
/// <summary>
/// 同步的使用socket发送数据
/// </summary>
/// <param name="socket"></param>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <param name="size"></param>
/// <param name="timeout"></param>
public void Send(Socket socket, byte[] buffer, int offset, int size, int timeout)
{
..见原文,不改动
}
/// <summary>
/// 发送完成时处理函数 ,尽管使用了server的异步发送,但从来没有到达这里
/// </summary>
/// <param name="e">与发送完成操作相关联的SocketAsyncEventArg对象</param>
private void ProcessSend(SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
Socket s = (Socket)e.UserToken;
//TODO
}
else
{
CloseClientSocket(e);
}
}
#endregion
#region 接收数据
/// <summary>
///接收完成时处理函数
/// </summary>
/// <param name="e">与接收完成操作相关联的SocketAsyncEventArg对象</param>
private void ProcessReceive(SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)//if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
{
// 检查远程主机是否关闭连接
if (e.BytesTransferred > 0)
{
Socket s = (Socket)e.UserToken;
//判断所有需接收的数据是否已经完成
if (s.Available == 0)
{
//从侦听者获取接收到的消息。
//String received = Encoding.ASCII.GetString(e.Buffer, e.Offset, e.BytesTransferred);
//echo the data received back to the client
//e.SetBuffer(e.Offset, e.BytesTransferred);
byte[] data = new byte[e.BytesTransferred];
Array.Copy(e.Buffer, e.Offset, data, 0, data.Length);//从e.Buffer块中复制数据出来,保证它可重用
//TODO 处理数据
if (ProcessRecvData(e, data) < 0)
{
这里做我的业务处理,处理收到的报文
//** 出错了,不用接收下一段数据,直接关闭当前接收通道后返回
CloseClientSocket(e);
return;
}
//增加服务器接收的总字节数。
}
if (!s.ReceiveAsync(e))//为接收下一段数据,投递接收请求,这个函数有可能同步完成,这时返回false,并且不会引发SocketAsyncEventArgs.Completed事件
{
//同步接收时处理接收完成事件
ProcessReceive(e);
}
}
}
else
{
错误在这里产生,因为socket.error=connection reset,,注意,不是connection reset by peer,于是导致关闭SOCKET,一旦关闭,设备立即会发起一个重新连接,只不过换了个端口
CloseClientSocket(e);
}
}
private void OnIOCompleted(object sender, SocketAsyncEventArgs e)
{
端口完成时的操作,在接收时可以跟踪到这里,但在调用server.发送时,无法引起这个事件
}
后面的CLOSE什么的,与原文一致。
这里,在我的业务处理里,传入了SocketAsyncEvent的参数,于是在处理完后,回应给设备一个OK报文,发送方法如下:
Socket service = (Socket)e.UserToken;
....
....纠结在这里一段时间,到底是用server.send还是直接有功SOCKET.SendAsync,这里代码有点混乱
SocketAsyncEventArgs se = new SocketAsyncEventArgs();
se.UserToken = e.UserToken;
se.SetBuffer(b, 0, b.Length);
Socket iSe = (Socket)se.UserToken;
se.AcceptSocket = service;
server.Send(se, b);
到这里就可以了,问题在文章开头给出了描述,大家给看看,为什么会产生一个conection rest,为什么无法引起发送事件的IOCOMPLETE