C# 调用EndReceive和BeginReceive时出现TcpClient异常

C# 调用EndReceive和BeginReceive时出现TcpClient异常,c#,.net,tcplistener,C#,.net,Tcplistener,我正在尝试实现包装器类,它将简单地连接到TCP服务器并等待数据。一旦数据从服务器提交,我将接收这些数据并将其传递给我的类的订阅者 所有这些都有效。现在,我想添加外部功能,在计时器上“重置”这个类(强制每隔一段时间重新连接一次),以保持连接的活动状态。我的想法是,可以根据需要多次调用Init方法来重置套接字。然而,我确实有各种例外 类别代码: namespace Ditat.GateControl.Service.InputListener { using System; usin

我正在尝试实现包装器类,它将简单地连接到TCP服务器并等待数据。一旦数据从服务器提交,我将接收这些数据并将其传递给我的类的订阅者

所有这些都有效。现在,我想添加外部功能,在计时器上“重置”这个类(强制每隔一段时间重新连接一次),以保持连接的活动状态。我的想法是,可以根据需要多次调用
Init
方法来重置套接字。然而,我确实有各种例外

类别代码:

namespace Ditat.GateControl.Service.InputListener
{
    using System;
    using System.ComponentModel;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;

    public class BaseTCPSocketListener : IInputListener
    {
        #region Events/Properties

        public event EventHandler<Exception> OnError;

        public event EventHandler<string> OnDataReceived;

        private string host;

        private int port;

        private int delayToClearBufferSeconds = 5;

        private TcpClient client;

        private readonly byte[] buffer = new byte[1024];

        /// <summary>
        /// Will accumulate data as it's received
        /// </summary>
        private string DataBuffer { get; set; }

        /// <summary>
        /// Store time of last data receipt. Need this in order to purge data after delay
        /// </summary>
        private DateTime LastDataReceivedOn { get; set; }

        #endregion

        public BaseTCPSocketListener()
        {
            // Preset all entries
            this.LastDataReceivedOn = DateTime.UtcNow;
            this.DataBuffer = string.Empty;

        }

        public void Init(string config)
        {
            // Parse info
            var bits = config.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
            this.host = bits[0];
            var hostBytes = this.host.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
            var hostIp = new IPAddress(new[] { byte.Parse(hostBytes[0]), byte.Parse(hostBytes[1]), byte.Parse(hostBytes[2]), byte.Parse(hostBytes[3]) });
            this.port = int.Parse(bits[1]);
            this.delayToClearBufferSeconds = int.Parse(bits[2]);

            // Close open client
            if (this.client?.Client != null)
            {
                this.client.Client.Disconnect(true);
                this.client = null;
            }

            // Connect to client
            this.client = new TcpClient();
            if (!this.client.ConnectAsync(hostIp, this.port).Wait(2500))
                throw new Exception($"Failed to connect to {this.host}:{this.port} in allotted time");

            this.EstablishReceiver();
        }

        protected void DataReceived(IAsyncResult result)
        {
            // End the data receiving that the socket has done and get the number of bytes read.
            var bytesCount = 0;
            try
            {
                bytesCount = this.client.Client.EndReceive(result);
            }
            catch (Exception ex)
            {
                this.RaiseOnErrorToClient(new Exception(nameof(this.DataReceived)));
                this.RaiseOnErrorToClient(ex);
            }

            // No data received, establish receiver and return
            if (bytesCount == 0)
            {
                this.EstablishReceiver();
                return;
            }

            // Convert the data we have to a string.
            this.DataBuffer += Encoding.UTF8.GetString(this.buffer, 0, bytesCount);

            // Record last time data received
            this.LastDataReceivedOn = DateTime.UtcNow;
            this.RaiseOnDataReceivedToClient(this.DataBuffer);

            this.DataBuffer = string.Empty;
            this.EstablishReceiver();
        }

        private void EstablishReceiver()
        {
            try
            {
                // Set up again to get the next chunk of data.
                this.client.Client.BeginReceive(this.buffer, 0, this.buffer.Length, SocketFlags.None, this.DataReceived, this.buffer);
            }
            catch (Exception ex)
            {
                this.RaiseOnErrorToClient(new Exception(nameof(this.EstablishReceiver)));
                this.RaiseOnErrorToClient(ex);
            }
        }

        private void RaiseOnErrorToClient(Exception ex)
        {
            if (this.OnError == null) return;

            foreach (Delegate d in this.OnError.GetInvocationList())
            {
                var syncer = d.Target as ISynchronizeInvoke;
                if (syncer == null)
                {
                    d.DynamicInvoke(this, ex);
                }
                else
                {
                    syncer.BeginInvoke(d, new object[] { this, ex });
                }
            }
        }

        private void RaiseOnDataReceivedToClient(string data)
        {
            if (this.OnDataReceived == null) return;

            foreach (Delegate d in this.OnDataReceived.GetInvocationList())
            {
                var syncer = d.Target as ISynchronizeInvoke;
                if (syncer == null)
                {
                    d.DynamicInvoke(this, data);
                }
                else
                {
                    syncer.BeginInvoke(d, new object[] { this, data });
                }
            }
        }
    }
}
private void EstablishReceiver()
{
    try
    {
        var state = new StateObject(client.Client);
        // Set up again to get the next chunk of data.
        this.client.Client.BeginReceive(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, this.DataReceived, state);
    }
    catch (Exception ex)
    {
        this.RaiseOnErrorToClient(new Exception(nameof(this.EstablishReceiver)));
        this.RaiseOnErrorToClient(ex);
    }
}
protected void DataReceived(IAsyncResult result)
{
    var state = (StateObject) result.AsyncState;

    // End the data receiving that the socket has done and get the number of bytes read.
    var bytesCount = 0;

    try
    {
        SocketError errorCode;
        bytesCount = state.Socket.EndReceive(result, out errorCode);
        if (errorCode != SocketError.Success)
        {
            bytesCount = 0;
        }
    }
    catch (Exception ex)
    {
        this.RaiseOnErrorToClient(new Exception(nameof(this.DataReceived)));
        this.RaiseOnErrorToClient(ex);
    }

    if (bytesCount > 0)
    {
        // Convert the data we have to a string.
        this.DataBuffer += Encoding.UTF8.GetString(state.Buffer, 0, bytesCount);

        // Record last time data received
        this.LastDataReceivedOn = DateTime.UtcNow;
        this.RaiseOnDataReceivedToClient(this.DataBuffer);

        this.DataBuffer = string.Empty;
        this.EstablishReceiver();
    }
}
我有例外。第一个异常是在
DataReceived

IAsyncResult对象未从相应的 此类上的异步方法

在下面的单击中,我从
EstablishReceiver

不允许发送或接收数据的请求,因为套接字为 未连接和(使用sendto在数据报套接字上发送时) 电话)没有提供地址


如何正确地确保套接字关闭并重新打开?

首先,您关闭的是TcpClient持有的套接字,而不是处理客户端本身。请尝试以下操作:

// Close open client
this.client?.Close();   // Disposes and releases resources
this.client = null;
IAsyncResult对象未从相应的 此类上的异步方法

这是为上一个套接字调用数据回调(
DataReceived()
)时发生的一个众所周知的问题。在这种情况下,您将调用
Socket.EndReceive()
,调用的
IAsyncResult
实例不正确,该实例引发上述异常

包含此问题的可能解决方法:存储套接字,在状态对象中对其调用了
BeginReceive()
,然后将其传递给
DataReceived
回调:

StateObject类

public class StateObject
{
    public Socket Socket { get; set; }

    public byte[] Buffer { get; } = new byte[1024];

    public StateObject(Socket socket)
    {
        Socket = socket;
    }
}
buildReceiver()方法:

namespace Ditat.GateControl.Service.InputListener
{
    using System;
    using System.ComponentModel;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;

    public class BaseTCPSocketListener : IInputListener
    {
        #region Events/Properties

        public event EventHandler<Exception> OnError;

        public event EventHandler<string> OnDataReceived;

        private string host;

        private int port;

        private int delayToClearBufferSeconds = 5;

        private TcpClient client;

        private readonly byte[] buffer = new byte[1024];

        /// <summary>
        /// Will accumulate data as it's received
        /// </summary>
        private string DataBuffer { get; set; }

        /// <summary>
        /// Store time of last data receipt. Need this in order to purge data after delay
        /// </summary>
        private DateTime LastDataReceivedOn { get; set; }

        #endregion

        public BaseTCPSocketListener()
        {
            // Preset all entries
            this.LastDataReceivedOn = DateTime.UtcNow;
            this.DataBuffer = string.Empty;

        }

        public void Init(string config)
        {
            // Parse info
            var bits = config.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
            this.host = bits[0];
            var hostBytes = this.host.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
            var hostIp = new IPAddress(new[] { byte.Parse(hostBytes[0]), byte.Parse(hostBytes[1]), byte.Parse(hostBytes[2]), byte.Parse(hostBytes[3]) });
            this.port = int.Parse(bits[1]);
            this.delayToClearBufferSeconds = int.Parse(bits[2]);

            // Close open client
            if (this.client?.Client != null)
            {
                this.client.Client.Disconnect(true);
                this.client = null;
            }

            // Connect to client
            this.client = new TcpClient();
            if (!this.client.ConnectAsync(hostIp, this.port).Wait(2500))
                throw new Exception($"Failed to connect to {this.host}:{this.port} in allotted time");

            this.EstablishReceiver();
        }

        protected void DataReceived(IAsyncResult result)
        {
            // End the data receiving that the socket has done and get the number of bytes read.
            var bytesCount = 0;
            try
            {
                bytesCount = this.client.Client.EndReceive(result);
            }
            catch (Exception ex)
            {
                this.RaiseOnErrorToClient(new Exception(nameof(this.DataReceived)));
                this.RaiseOnErrorToClient(ex);
            }

            // No data received, establish receiver and return
            if (bytesCount == 0)
            {
                this.EstablishReceiver();
                return;
            }

            // Convert the data we have to a string.
            this.DataBuffer += Encoding.UTF8.GetString(this.buffer, 0, bytesCount);

            // Record last time data received
            this.LastDataReceivedOn = DateTime.UtcNow;
            this.RaiseOnDataReceivedToClient(this.DataBuffer);

            this.DataBuffer = string.Empty;
            this.EstablishReceiver();
        }

        private void EstablishReceiver()
        {
            try
            {
                // Set up again to get the next chunk of data.
                this.client.Client.BeginReceive(this.buffer, 0, this.buffer.Length, SocketFlags.None, this.DataReceived, this.buffer);
            }
            catch (Exception ex)
            {
                this.RaiseOnErrorToClient(new Exception(nameof(this.EstablishReceiver)));
                this.RaiseOnErrorToClient(ex);
            }
        }

        private void RaiseOnErrorToClient(Exception ex)
        {
            if (this.OnError == null) return;

            foreach (Delegate d in this.OnError.GetInvocationList())
            {
                var syncer = d.Target as ISynchronizeInvoke;
                if (syncer == null)
                {
                    d.DynamicInvoke(this, ex);
                }
                else
                {
                    syncer.BeginInvoke(d, new object[] { this, ex });
                }
            }
        }

        private void RaiseOnDataReceivedToClient(string data)
        {
            if (this.OnDataReceived == null) return;

            foreach (Delegate d in this.OnDataReceived.GetInvocationList())
            {
                var syncer = d.Target as ISynchronizeInvoke;
                if (syncer == null)
                {
                    d.DynamicInvoke(this, data);
                }
                else
                {
                    syncer.BeginInvoke(d, new object[] { this, data });
                }
            }
        }
    }
}
private void EstablishReceiver()
{
    try
    {
        var state = new StateObject(client.Client);
        // Set up again to get the next chunk of data.
        this.client.Client.BeginReceive(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, this.DataReceived, state);
    }
    catch (Exception ex)
    {
        this.RaiseOnErrorToClient(new Exception(nameof(this.EstablishReceiver)));
        this.RaiseOnErrorToClient(ex);
    }
}
protected void DataReceived(IAsyncResult result)
{
    var state = (StateObject) result.AsyncState;

    // End the data receiving that the socket has done and get the number of bytes read.
    var bytesCount = 0;

    try
    {
        SocketError errorCode;
        bytesCount = state.Socket.EndReceive(result, out errorCode);
        if (errorCode != SocketError.Success)
        {
            bytesCount = 0;
        }
    }
    catch (Exception ex)
    {
        this.RaiseOnErrorToClient(new Exception(nameof(this.DataReceived)));
        this.RaiseOnErrorToClient(ex);
    }

    if (bytesCount > 0)
    {
        // Convert the data we have to a string.
        this.DataBuffer += Encoding.UTF8.GetString(state.Buffer, 0, bytesCount);

        // Record last time data received
        this.LastDataReceivedOn = DateTime.UtcNow;
        this.RaiseOnDataReceivedToClient(this.DataBuffer);

        this.DataBuffer = string.Empty;
        this.EstablishReceiver();
    }
}
DataReceived()方法:

namespace Ditat.GateControl.Service.InputListener
{
    using System;
    using System.ComponentModel;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;

    public class BaseTCPSocketListener : IInputListener
    {
        #region Events/Properties

        public event EventHandler<Exception> OnError;

        public event EventHandler<string> OnDataReceived;

        private string host;

        private int port;

        private int delayToClearBufferSeconds = 5;

        private TcpClient client;

        private readonly byte[] buffer = new byte[1024];

        /// <summary>
        /// Will accumulate data as it's received
        /// </summary>
        private string DataBuffer { get; set; }

        /// <summary>
        /// Store time of last data receipt. Need this in order to purge data after delay
        /// </summary>
        private DateTime LastDataReceivedOn { get; set; }

        #endregion

        public BaseTCPSocketListener()
        {
            // Preset all entries
            this.LastDataReceivedOn = DateTime.UtcNow;
            this.DataBuffer = string.Empty;

        }

        public void Init(string config)
        {
            // Parse info
            var bits = config.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
            this.host = bits[0];
            var hostBytes = this.host.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
            var hostIp = new IPAddress(new[] { byte.Parse(hostBytes[0]), byte.Parse(hostBytes[1]), byte.Parse(hostBytes[2]), byte.Parse(hostBytes[3]) });
            this.port = int.Parse(bits[1]);
            this.delayToClearBufferSeconds = int.Parse(bits[2]);

            // Close open client
            if (this.client?.Client != null)
            {
                this.client.Client.Disconnect(true);
                this.client = null;
            }

            // Connect to client
            this.client = new TcpClient();
            if (!this.client.ConnectAsync(hostIp, this.port).Wait(2500))
                throw new Exception($"Failed to connect to {this.host}:{this.port} in allotted time");

            this.EstablishReceiver();
        }

        protected void DataReceived(IAsyncResult result)
        {
            // End the data receiving that the socket has done and get the number of bytes read.
            var bytesCount = 0;
            try
            {
                bytesCount = this.client.Client.EndReceive(result);
            }
            catch (Exception ex)
            {
                this.RaiseOnErrorToClient(new Exception(nameof(this.DataReceived)));
                this.RaiseOnErrorToClient(ex);
            }

            // No data received, establish receiver and return
            if (bytesCount == 0)
            {
                this.EstablishReceiver();
                return;
            }

            // Convert the data we have to a string.
            this.DataBuffer += Encoding.UTF8.GetString(this.buffer, 0, bytesCount);

            // Record last time data received
            this.LastDataReceivedOn = DateTime.UtcNow;
            this.RaiseOnDataReceivedToClient(this.DataBuffer);

            this.DataBuffer = string.Empty;
            this.EstablishReceiver();
        }

        private void EstablishReceiver()
        {
            try
            {
                // Set up again to get the next chunk of data.
                this.client.Client.BeginReceive(this.buffer, 0, this.buffer.Length, SocketFlags.None, this.DataReceived, this.buffer);
            }
            catch (Exception ex)
            {
                this.RaiseOnErrorToClient(new Exception(nameof(this.EstablishReceiver)));
                this.RaiseOnErrorToClient(ex);
            }
        }

        private void RaiseOnErrorToClient(Exception ex)
        {
            if (this.OnError == null) return;

            foreach (Delegate d in this.OnError.GetInvocationList())
            {
                var syncer = d.Target as ISynchronizeInvoke;
                if (syncer == null)
                {
                    d.DynamicInvoke(this, ex);
                }
                else
                {
                    syncer.BeginInvoke(d, new object[] { this, ex });
                }
            }
        }

        private void RaiseOnDataReceivedToClient(string data)
        {
            if (this.OnDataReceived == null) return;

            foreach (Delegate d in this.OnDataReceived.GetInvocationList())
            {
                var syncer = d.Target as ISynchronizeInvoke;
                if (syncer == null)
                {
                    d.DynamicInvoke(this, data);
                }
                else
                {
                    syncer.BeginInvoke(d, new object[] { this, data });
                }
            }
        }
    }
}
private void EstablishReceiver()
{
    try
    {
        var state = new StateObject(client.Client);
        // Set up again to get the next chunk of data.
        this.client.Client.BeginReceive(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, this.DataReceived, state);
    }
    catch (Exception ex)
    {
        this.RaiseOnErrorToClient(new Exception(nameof(this.EstablishReceiver)));
        this.RaiseOnErrorToClient(ex);
    }
}
protected void DataReceived(IAsyncResult result)
{
    var state = (StateObject) result.AsyncState;

    // End the data receiving that the socket has done and get the number of bytes read.
    var bytesCount = 0;

    try
    {
        SocketError errorCode;
        bytesCount = state.Socket.EndReceive(result, out errorCode);
        if (errorCode != SocketError.Success)
        {
            bytesCount = 0;
        }
    }
    catch (Exception ex)
    {
        this.RaiseOnErrorToClient(new Exception(nameof(this.DataReceived)));
        this.RaiseOnErrorToClient(ex);
    }

    if (bytesCount > 0)
    {
        // Convert the data we have to a string.
        this.DataBuffer += Encoding.UTF8.GetString(state.Buffer, 0, bytesCount);

        // Record last time data received
        this.LastDataReceivedOn = DateTime.UtcNow;
        this.RaiseOnDataReceivedToClient(this.DataBuffer);

        this.DataBuffer = string.Empty;
        this.EstablishReceiver();
    }
}
不允许发送或接收数据的请求,因为套接字为 未连接和(使用sendto在数据报套接字上发送时) 电话)没有提供地址


上面的
DataReceived()
方法还包含对第二个异常的修复。在断开的套接字上调用
BeginReceive()
(从
EstablishReceiver()
)会导致异常。如果以前的读取带来了0字节,则不应在套接字上调用
BeginReceive()

问题是关闭客户端时将调用DataReceived。您只需要向该方法标识它不应该做任何事情,因为您已经故意结束了该过程。您可以添加一个bool:

    private bool ignoreCallback;
    public void Init(string config)
    {
        // Parse info
        var bits = config.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
        this.host = bits[0];
        var hostBytes = this.host.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
        var hostIp = new IPAddress(new[] { byte.Parse(hostBytes[0]), byte.Parse(hostBytes[1]), byte.Parse(hostBytes[2]), byte.Parse(hostBytes[3]) });
        this.port = int.Parse(bits[1]);
        this.delayToClearBufferSeconds = int.Parse(bits[2]);

        // Close open client
        if (this.client?.Client != null)
        {
            ignoreCallback = true;
            this.client.Client.Disconnect(true);
            this.client = null;
        }

        // Connect to client
        this.client = new TcpClient();
        if (!this.client.ConnectAsync(hostIp, this.port).Wait(2500))
            throw new Exception($"Failed to connect to {this.host}:{this.port} in allotted time");

        this.EstablishReceiver();
    }

    protected void DataReceived(IAsyncResult result)
    {
        if (ignoreCallback)
        {
            ignoreCallback = false;
            return;
        }

        ...

第一个异常是由一个基本线程争用错误引起的,代码在触发旧客户端的DataReceived事件之前更改
client
变量。查看MSDN代码示例,了解如何使用IAsyncResult.AsyncState检索正确的套接字。第二个异常是调用BeginReceive导致的,即使DataReceived返回0字节。0表示套接字已断开连接,因此您要退出。使用Dispose()更好,使用try/catch捕获ODE。出现这种问题是Microsoft添加异步/等待模式的基本原因。警告。此代码似乎是基于您将接收消息的假设构建的。这不是TCP抽象。TCP连接以双向字节流的形式进行处理。这意味着您不能假设任何要接收的呼叫都会得到一个完整的“东西”。因此,您不能假设您收到的是一个完整的UTF8字符串。你可能有一个只包含UTF 8字符一部分的缓冲区(你的下一个缓冲区将包含该字符的其余部分)。如果你想要消息传递,你可以自己在TCP上实现消息帧,或者移动到一个更高级别的协议,该协议已经在消息方面起作用。回答得很好!确保每一行代码都是正确的,否则你会遇到像我这样的情况,我花了好几个小时搜索侦听器和处理程序套接字的一次交换;)