使用 SocketAsyncEventArgs 实现通信服务器的问题

Pigeon汪 2015-02-23 04:35:28
目前遇到2个问题,
1:建立SocketAsyncEventArgs池与不使用SocketAsyncEventArgs池有性能上的区别吗?
2:对于用SocketAsyncEventArgs实现的通信服务器,因为没有Socket.ReceiveTimeout 属性,有木有什么好的方法判断Client心跳超时了?
...全文
1003 13 打赏 收藏 转发到动态 举报
写回复
用AI写文章
13 条回复
切换为时间正序
请发表友善的回复…
发表回复
烟波钓 2015-04-07
  • 打赏
  • 举报
回复
// 摘要: // 从 System.Net.Sockets.NetworkStream 开始异步读取。 // // 参数: // buffer: // 类型 System.Byte 的数组,它是内存中用于存储从 System.Net.Sockets.NetworkStream 读取的数据的位置。 // // offset: // buffer 中开始存储数据的位置。 // // size: // 要从 System.Net.Sockets.NetworkStream 中读取的字节数。 // // callback: // 在 System.Net.Sockets.NetworkStream.BeginRead(System.Byte[],System.Int32,System.Int32,System.AsyncCallback,System.Object) // 完成时执行的 System.AsyncCallback 委托。 // // state: // 包含用户定义的任何附加数据的对象。 // // 返回结果: // 表示异步调用的 System.IAsyncResult。 // // 异常: // System.ArgumentNullException: // buffer 参数为 null。 // // System.ArgumentOutOfRangeException: // offset 参数小于 0。- 或 -offset 参数大于 buffer 参数的长度。- 或 -size 小于 0。- 或 -size 大于 buffer // 的长度减去 offset 参数的值。 // // System.IO.IOException: // 基础 System.Net.Sockets.Socket 被关闭。- 或 -从网络读取时出现错误。- 或 -访问套接字时出错。有关更多信息,请参见备注部分。 // // System.ObjectDisposedException: // System.Net.Sockets.NetworkStream 是关闭的。
Sunny5816 2015-04-07
  • 打赏
  • 举报
回复
最近也在研究socket通信,可以看一下我的博客 http://www.cnblogs.com/networkcomms/
mirrorspace 2015-04-06
  • 打赏
  • 举报
回复
你好 求解 客户端代码的构造函数16行 和 回调函数的25行.都有异步读取的方法,这个点解?
引用 6 楼 sp1234 的回复:
而客户端的基础类则比较简单,因为它复用了TcpServerBase类中的一点代码
using System;
using System.Collections.Generic;
using System.Net.Sockets;

namespace Common
{
    public class TcpClientBase : TcpClient
    {
        public TcpClientBase(string host, int port, int bufferSize = 320000, bool asyncExecution = true)
            : base(host, port)
        {
            this.Host = host;
            this.Port = port;
            this.AsyncExecution = asyncExecution;
            Buffer = new byte[bufferSize];
            this.GetStream().BeginRead(Buffer, 0, Buffer.Length, ClientRead, Buffer);
        }

        private void ClientRead(IAsyncResult h)
        {
            var stream = this.GetStream();
            TcpServerBase.ExecuteRead(stream, h, Buffer, Container, AsyncExecution, OnMessage, OnError);
            try
            {
                stream.BeginRead(Buffer, 0, Buffer.Length, ClientRead, Buffer);
            }
            catch (Exception ex)
            {
                if (OnError != null)
                    OnError(stream, ex);
            }
        }

        public void SendMessage(string msg, Action<Exception> error = null)
        {
            TcpServerBase.SendMessage(this.GetStream(), msg, error);
        }

        public event TcpServerBase.OnMessageEventHandler OnMessage;
        public event TcpServerBase.OnErrorEventHandler OnError;

        private byte[] Buffer;
        private List<byte> Container = new List<byte>();

        public string Host { get; set; }
        public int Port { get; private set; }
        public bool AsyncExecution { get; set; }
    }
}
你可以看到只要捕获 OnOpen、OnMessage 事件,调用 SendMessage 方法就行了,不用管是怎么实现的。
mirrorspace 2015-04-06
  • 打赏
  • 举报
回复
嗯..还有跳出循环那段 没有break 用了goto begin for(){ goto begin }
  • 打赏
  • 举报
回复
var跟强类型声明是一样的,这就是个语法糖……
mirrorspace 2015-04-06
  • 打赏
  • 举报
回复
引用 6 楼 sp1234 的回复:
你可以看到只要捕获 OnOpen、OnMessage 事件,调用 SendMessage 方法就行了,不用管是怎么实现的。
弱问一下大神 :服务器端代码 第32行 得到的是一个NetworkStream 但是为什么要用var呢.怎么不用强类型?
wyx100 2015-02-24
  • 打赏
  • 举报
回复
1楼正解。。。。
  • 打赏
  • 举报
回复
而客户端的基础类则比较简单,因为它复用了TcpServerBase类中的一点代码
using System;
using System.Collections.Generic;
using System.Net.Sockets;

namespace Common
{
    public class TcpClientBase : TcpClient
    {
        public TcpClientBase(string host, int port, int bufferSize = 320000, bool asyncExecution = true)
            : base(host, port)
        {
            this.Host = host;
            this.Port = port;
            this.AsyncExecution = asyncExecution;
            Buffer = new byte[bufferSize];
            this.GetStream().BeginRead(Buffer, 0, Buffer.Length, ClientRead, Buffer);
        }

        private void ClientRead(IAsyncResult h)
        {
            var stream = this.GetStream();
            TcpServerBase.ExecuteRead(stream, h, Buffer, Container, AsyncExecution, OnMessage, OnError);
            try
            {
                stream.BeginRead(Buffer, 0, Buffer.Length, ClientRead, Buffer);
            }
            catch (Exception ex)
            {
                if (OnError != null)
                    OnError(stream, ex);
            }
        }

        public void SendMessage(string msg, Action<Exception> error = null)
        {
            TcpServerBase.SendMessage(this.GetStream(), msg, error);
        }

        public event TcpServerBase.OnMessageEventHandler OnMessage;
        public event TcpServerBase.OnErrorEventHandler OnError;

        private byte[] Buffer;
        private List<byte> Container = new List<byte>();

        public string Host { get; set; }
        public int Port { get; private set; }
        public bool AsyncExecution { get; set; }
    }
}
你可以看到只要捕获 OnOpen、OnMessage 事件,调用 SendMessage 方法就行了,不用管是怎么实现的。
  • 打赏
  • 举报
回复
使用SocketAsyncEventArgs的代码显得比较麻烦和复杂(多出来二三十行代码),我只是在Silverlight中使用它。在桌面和windows service中,还是使用傻瓜一些的方式比较方便。 给这里给你贴一个不使用 SocketAsyncEventArgs 的代码,可以作为参考。(这里假设每一个消息都是以“换行回车”结束的) 服务器端:
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace Common
{
    public class TcpServerBase : TcpListener
    {
        /// <summary>
        /// 初始化tcp服务器。
        /// </summary>
        /// <param name="port">服务器端应用端口号。</param>
        /// <param name="bufferSize">为每一个客户端通道预留的buffer的大小。</param>
        /// <param name="asyncExecution">如果为true,在worker子线程(从系统线程池中分配)中触发OnMessage事件;如果为false,在I/O线程中触发。</param>
        public TcpServerBase(int port, int bufferSize = 8000, bool asyncExecution = true)
            : base(new IPEndPoint(IPAddress.Any, port))
        {
            this.BufferSize = bufferSize;
            this.AsyncExecution = asyncExecution;
            this.Start();
            this.BeginAcceptTcpClient(ClientConnected, null);
        }

        private void ClientConnected(IAsyncResult handler)
        {
            try
            {
                var client = this.EndAcceptTcpClient(handler);
                var stream = client.GetStream();
                if (OnOpen != null)
                    OnOpen((IPEndPoint)client.Client.RemoteEndPoint, stream);
                var buffer = new byte[BufferSize];
                var container = new List<byte>();
                stream.BeginRead(buffer, 0, buffer.Length, ProcessReceive, new object[] { stream, buffer, container });
            }
            finally
            {
                this.BeginAcceptTcpClient(ClientConnected, null);
            }
        }

        private void ProcessReceive(IAsyncResult h)
        {
            var objs = (object[])h.AsyncState;
            var stream = (NetworkStream)objs[0];
            var buffer = (byte[])objs[1];
            var container = (List<byte>)objs[2];
            ExecuteRead(stream, h, buffer, container, this.AsyncExecution, OnMessage, OnError);
            try
            {
                stream.BeginRead(buffer, 0, buffer.Length, ProcessReceive, objs);
            }
            catch (Exception ex)
            {
                if (OnError != null)
                    OnError(stream, ex);
            }
        }

        /// <summary>
        /// 读取对方传送的数据,分隔为一个或者多个命令行(以换行回车为结束符号),然后回调onMessage委托方法。
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="h"></param>
        /// <param name="buffer"></param>
        /// <param name="container"></param>
        /// <param name="AsyncExecution"></param>
        /// <param name="onMessage"></param>
        /// <param name="onError"></param>
        internal static void ExecuteRead(NetworkStream stream, IAsyncResult h, byte[] buffer, List<byte> container,
            bool AsyncExecution, OnMessageEventHandler onMessage, OnErrorEventHandler onError)
        {
            try
            {
                var len = stream.EndRead(h);
                for (var i = 0; i < len; i++)
                    container.Add(buffer[i]);
                ExecuteCommand(stream, container, AsyncExecution, onMessage, onError);
            }
            catch (Exception ex)
            {
                if (onError != null)
                    onError(stream, ex);
            }
        }

        private static void ExecuteCommand(NetworkStream stream, List<byte> container, bool AsyncExecution,
            OnMessageEventHandler onMessage, OnErrorEventHandler onError)
        {
        begin:
            for (var i = 0; i < container.Count - 1; i++)
            {
                if (container[i] == 0x0d && container[i + 1] == 0x0a)
                {
                    if (onMessage != null)
                    {
                        var command = Encoding.UTF8.GetString(container.ToArray(), 0, i);
                        if (AsyncExecution)
                            ThreadPool.QueueUserWorkItem(h => ExecuteMessage(stream, command, onMessage, onError));
                        else
                            ExecuteMessage(stream, command, onMessage, onError);
                    }
                    container.RemoveRange(0, i + 2);
                    goto begin;
                }
            }
        }

        private static void ExecuteMessage(NetworkStream stream, string command,
           OnMessageEventHandler onMessage, OnErrorEventHandler onError)
        {
            try
            {
                onMessage(stream, command);
            }
            catch (Exception ex)
            {
                if (onError != null)
                    onError(stream, ex);
            }
        }

        /// <summary>
        /// 通过NetworkStream向对方发送一条消息。
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="msg">要发送的命令。如果包含换行回车,应该替换为其它符号(例如仅有换行符)。如果含有二进制数据,应当将数据部分转为base64编码。</param>
        /// <param name="error"></param>
        public static void SendMessage(NetworkStream stream, string msg, Action<Exception> error = null)
        {
            if (msg.Contains("\r\n"))
            {
                error(new Exception("命令中不能包含换行回车。"));
                return;
            }

            var container = new List<byte>();
            container.AddRange(Encoding.UTF8.GetBytes(msg));
            container.Add(0x0d);
            container.Add(0x0a);
            var buffer = container.ToArray();
            stream.BeginWrite(buffer, 0, buffer.Length, x =>
            {
                try
                {
                    stream.EndWrite(x);
                }
                catch (Exception ex)
                {
                    if (error != null)
                        error(ex);
                }
            }, null);
        }

        public delegate void OnOpenEventHandler(IPEndPoint endpoint, NetworkStream stream);
        public delegate void OnCloseEventHandler(NetworkStream stream);
        public delegate void OnMessageEventHandler(NetworkStream stream, string message);
        public delegate void OnErrorEventHandler(NetworkStream stream, Exception ex);

        public event OnOpenEventHandler OnOpen;
        public event OnMessageEventHandler OnMessage;
        public event OnErrorEventHandler OnError;

        public int Port { get; private set; }
        public int BufferSize { get; private set; }
        public bool AsyncExecution { get; private set; }
    }
}
  • 打赏
  • 举报
回复
如果你实现的“池”运行时有bug,那么就不使用。如果没有bug,就使用呗。反正都已经实现了。如果没有实现,那么你可以测试一下,看看如果不实现“池”会降低多少性能?! 我估计建立一个“池”可能主要是给“短连接”使用的,因为只有这个时候才会频繁地(每秒几百次)创建这类对象。而长连接则只是在连接时创建2个这类对象(一个用于读,另一个用于写),创建之后就自动重复使用了。所以长连接不会平凡地重建Connection,也就不会重复地创建这类对象。那么使用“池”的意义可能就几乎为0了。 业务层面的“心跳”跟tcp层的心跳完全是两回事(底层tcp机制本身就有心跳,只不过可能是1个多小时才跳一次)。当心跳发生时,它会被记录在内存数据集合或者外部存储设备上。你想知道对方是否在线,检查一下对方最后一次发来的心跳的时间从而算出对方在线状态(例如一些 IM 就是差不多5分钟甚至更长时间才更新一下用户在线状态)。这不是人家给你提供的,这需要你自己去实现。而如果你想知道网络是否通,就要实际发一个消息给对方,而不是单纯去看“ReceiveTimeout ”。
devmiao 2015-02-23
  • 打赏
  • 举报
回复
异步的可以提高性能。
本拉灯 2015-02-23
  • 打赏
  • 举报
回复
如果你懂的复用SocketAsyncEventArgs 这个实例后的变量,那肯定比那个对象池好,如果每次都要New 那还是用对象池吧。 心跳超时,不一定就设什么属性,可以建设一个数据包,定时向服务端发送这个包做为心跳包即可。在发送或接收时出异常说明断线了。

110,889

社区成员

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

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

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