400分最高悬赏,不够可加,求助IOCP完成端口达人,困扰了好几天的问题,大家来分析

daiwoo_wang 2018-01-09 07:15:05
最近要做一个高并发长连接的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
...全文
305 7 打赏 收藏 转发到动态 举报
写回复
用AI写文章
7 条回复
切换为时间正序
请发表友善的回复…
发表回复
warcraftmgq 2018-01-11
  • 打赏
  • 举报
回复
你的ProcessRecvData里面的server是什么变量,哪里来的?把处理数数据的代码贴全。用SocketAsyncEventArgs的话,发送和接收要用各自的SocketAsyncEventArgs对象。
kampoo 2018-01-11
  • 打赏
  • 举报
回复
IOCP处理上千个连接不会有问题,不会因为性能的原因导致软件强行关闭连接。扫了一遍代码,问题可能出在对带宽的考虑上,建议: 1. 核查所有CloseSocket调用的上下文,确保不会因为代码的意外BUG导致套接字关闭。 2. 看看代码有没有超时处理的代码,通过Heart-Beaten包保证连接不会因为超时而导致连接游荡。 3. 在互联网上没问题只能说明你的软件在大带宽情况下可以正常工作,而GPRS的带宽只有20K上下,小带宽需要考虑一个数据包被自动拆成多个报文发送的问题。设备发来的每个消息可能多次调用ProcessReceive。这会影响你的ProcessRecvData(e, data) < 0判断,在ProcessRecvData中需要保存前面收到的不完整数据,不要轻易关闭套接字。 4. GPRS数据卡会不会有问题。以前遇到部分地方运营商默认关闭GPRS甚至GPRS网络被逐步废弃、缺少维护的情况,咨询你的运营商。必要时或者有条件时申请GPRS-VPN卡。
daiwoo_wang 2018-01-10
  • 打赏
  • 举报
回复
你理解错了这个心跳,这个心跳是1分钟1下,是设备方要求回复的。设备会发心跳,最短间隔1分钟。这个问题的关键就在于怎么同步方式没问题,异步就有问题,是不是异步方式时有个地方要调用什么CLOSE或者END,否则异步方式每次ReceieveAsync后,立即跳到下一循环,又是ReceiveAsync一类的异步读,结果就类似于datareader读完后不关闭,当重复打开这个DR时,也会报错,说SQL SERVER关闭了的,原因 就是连接池满了。我想这个CONNECTION RESET多半也是类似的原因,导致连接池满一类的,只不过还没找到上面这段代码哪里有漏洞
  • 打赏
  • 举报
回复
还有就是心跳,我当年5秒心跳300客户端就能直接32线程16g服务器全部占满,说真的,心跳这个东西你需要好好调整。
  • 打赏
  • 举报
回复
但对于这个心跳报文,必须回复的,而且这个处理相当短,不会导致卡帧 你又不是udp又是长连接,还要心跳干啥,别人只要断开你这边就会出错的你明白吗,你还发什么心跳报文我真的不懂你啊
daiwoo_wang 2018-01-10
  • 打赏
  • 举报
回复
用同步通信这个是没问题的,异步通信时,不管是使用IOCP还是简单的写个异步接收,都会导致错误,我估计要么就是程序中某个地方重复发起了连接,导致基站的等待连接队列满。但如果是这样的话,为什么用局域网又没问题?现在收到的帧是后续处理的,但对于这个心跳报文,必须回复的,而且这个处理相当短,不会导致卡帧
  • 打赏
  • 举报
回复
IOCP没用过,就说说我的经验,20台以上的tcp通讯不应该卡帧,不然网吧的路由器都要用多高级的才能支撑啊…… 通讯软件一定要异步处理,所谓的异步处理就是说,数据通讯的模块只做通讯,可能做一下存入缓存或存入数据库,接下来对数据的处理由后续模块接手去做,而不是一边接受数据一边处理数据,你要达到这个实时我估计你要上超算。

110,533

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧