SocketAsyncEventArgs内存占用太大引发OutOfMemoryExceptions
最近把一个接收socket服务端软件升级为iocp方案,其实就是换成c#的socket的异步方法,主要参考的是微软写的Server类,然后再百度找到基于这个类实现的代码来改造
上线一两天后,软件崩溃了,查日志发现报了很多OutOfMemoryExceptions错误,都是在发送方法上。
因为我发送方法用的SocketAsyncEventArgs是自己new的,不是从SocketAsyncEventArgsPool池里分配的,所以发送后一直没被GC回收掉。
短短5分钟,就占用了1.5G
我发送的方法是这样的,微软它的的例子就没这个问题,因为它每个socket接收跟发送都是用的同一个SocketAsyncEventArgs。它是收包后,再发包,然后再收包,这个SocketAsyncEventArgs一直贯穿于里面,但实际场景根本没法跟微软那例子一样,我可能要主动发包给客户端,或者客户端发包给我,我不需要回包给客户,或者客户端发了粘包给我,我需要拆分开发两个包给客户端。所以我接收都是用的同一个SocketAsyncEventArgs,但发送是另外new的。
public void SendBytes(AsyncUserToken userToken, byte[] bytes) { if (userToken == null || userToken.Socket == null || !userToken.Socket.Connected) return; try { //新建异步发送对象, 发送消息 SocketAsyncEventArgs sendArg = new SocketAsyncEventArgs(); sendArg.UserToken = userToken; sendArg.SetBuffer(bytes, 0, bytes.Length); //将数据放置进去. userToken.Socket.SendAsync(sendArg); } catch (Exception e) { UIManager.LogException(e); } }
我后面也想过也在SocketAsyncEventArgsPool池里面分配,但它这个SetBuffer方法不太懂怎么用,我不知怎么把我的数据传进去,如果SetBuffer(bytes,0,bytes.Length),那它不就引用到我这个字节上,跟bufferManager的缓存数组没引用关系了。也还是会占用内存。
研究后,有两种解决方法,一种最简单,改用同步方法Send,改回后就非常稳定了
当然,异步方法还是可以用的,只是需要在它的回调方法里面SetBuffer(null,0,0),释放掉字节的引用,如下面这样,改完后内容也是非常稳定,基本在50M左右
public void SendBytes(AsyncUserToken userToken, byte[] bytes) { if (userToken == null || userToken.Socket == null || !userToken.Socket.Connected) return; try { //新建异步发送对象, 发送消息 SocketAsyncEventArgs sendArg = new SocketAsyncEventArgs(); sendArg.Completed += SendArg_Completed; //sendArg.UserToken = userToken; sendArg.SetBuffer(bytes, 0, bytes.Length); //将数据放置进去. userToken.Socket.SendAsync(sendArg); //userToken.Socket.Send(bytes); } catch (Exception e) { UIManager.LogException(e); } } private void SendArg_Completed(object sender, SocketAsyncEventArgs e) { ProcessSend(e); } private void ProcessSend(SocketAsyncEventArgs e) { e.SetBuffer(null, 0, 0); if (e.SocketError != SocketError.Success) { CloseClientSocket(e); } else { //UIManager.Log($"ProcessSend断线了{e.BytesTransferred}-{e.SocketFlags}"); } }