通过Socket.BeginSend / EndSend发送数据不会刷新缓冲区吗?

时间:2015-05-19 05:22:18

标签: c# sockets asynchronous

我有一个类来处理与连接到我的服务器的客户端之间的通信。我能够处理从telnet客户端发送给我的数据包没有太大问题。

客户端连接类

namespace Mud.Engine.Components.WindowsServer
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net.Sockets;
    using System.Text;
    using Mud.Engine.Runtime.Game.Character;

    /// <summary>
    /// Handles the players networking state.
    /// </summary>
    public sealed class PlayerConnectionState
    {
        /// <summary>
        /// The size of the buffer that will hold data sent from the client
        /// </summary>
        private readonly int bufferSize;

        /// <summary>
        /// A temporary collection of incomplete messages sent from the client. These must be put together and processed.
        /// </summary>
        private readonly List<string> currentData = new List<string>();

        /// <summary>
        /// What the last chunk of data sent from the client contained.
        /// </summary>
        private string lastChunk = string.Empty;

        /// <summary>
        /// Instances a new PlayerConnectionState.
        /// </summary>
        /// <param name="player">An instance of a Player type that will be performing network communication</param>
        /// <param name="currentSocket">The Socket used to communicate with the client.</param>
        /// <param name="bufferSize">The storage size of the data buffer</param>
        public PlayerConnectionState(IPlayer player, Socket currentSocket, int bufferSize)
        {
            this.Player = player;
            this.CurrentSocket = currentSocket;

            this.bufferSize = bufferSize;
            this.Buffer = new byte[bufferSize];
        }

        /// <summary>
        /// Gets the Player instance associated with this state.
        /// </summary>
        public IPlayer Player { get; private set; }

        /// <summary>
        /// Gets the Socket for the player associated with this state.
        /// </summary>
        public Socket CurrentSocket { get; private set; }

        /// <summary>
        /// Gets the data currently in the network buffer
        /// </summary>
        public byte[] Buffer { get; private set; }

        /// <summary>
        /// Gets if the current network connection is in a valid state.
        /// </summary>
        public bool IsConnectionValid
        {
            get
            {
                return this.CurrentSocket != null && this.CurrentSocket.Connected;
            }
        }

        /// <summary>
        /// Starts listening for network communication sent from the client to the server
        /// </summary>
        public void StartListeningForData()
        {
            this.Buffer = new byte[bufferSize];
            this.CurrentSocket.BeginReceive(this.Buffer, 0, bufferSize, 0, new AsyncCallback(this.ReceiveData), null);
            this.Player.CommandManager.CommandCompleted += this.HandleCommandExecutionCompleted;
        }

        public void SendMessage(string message)
        {
            byte[] buffer = Encoding.ASCII.GetBytes(message);
            this.CurrentSocket.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(this.CompleteMessageSending), null);
        }

        private void CompleteMessageSending(IAsyncResult asyncResult)
        {
            this.CurrentSocket.EndSend(asyncResult);
        }

        private void HandleCommandExecutionCompleted(object sender, CommandCompletionArgs e)
        {
            this.SendMessage($"{e.Command} executed.\r\n");
        }

        /// <summary>
        /// Receives the input data from the user.
        /// </summary>
        /// <param name="result">The result.</param>
        private void ReceiveData(IAsyncResult result)
        {
            // If we are no longer in a valid state, dispose of the connection.
            if (!this.IsConnectionValid)
            {
                this.CurrentSocket?.Dispose();
                return;
            }

            int bytesRead = this.CurrentSocket.EndReceive(result);
            if (bytesRead == 0 || !this.Buffer.Any())
            {
                this.StartListeningForData();
                return;
            }

            ProcessReceivedData(bytesRead);
            this.StartListeningForData();
        }

        /// <summary>
        /// Process the data we received from the client.
        /// </summary>
        /// <param name="bytesRead"></param>
        private void ProcessReceivedData(int bytesRead)
        {
            // Encode our input string sent from the client
            this.lastChunk = Encoding.ASCII.GetString(this.Buffer, 0, bytesRead);

            // Temporary to avoid handling the telnet negotiations for now. 
            // This needs to be abstracted out in to a negotation class that will parse, send and receive negotiation requests.
            if (this.Buffer.First() == 255)
            {
                return;
            }

            // If the previous chunk did not have a new line feed, then we add this message to the collection of currentData.
            // This lets us build a full message before processing it.
            if (!lastChunk.Contains("\r\n"))
            {
                // Add this to our incomplete data stash and read again.
                this.currentData.Add(lastChunk);
                return;
            }

            // This message contained at least 1 new line, so we split it and process per line.
            List<string> messages = lastChunk.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries).ToList();

            foreach (string line in this.PruneReceivedMessages(messages))
            {
                this.Player.CommandManager.ProcessCommandForCharacter(this.Player, line);
            }
        }

        /// <summary>
        /// Runs through the messages collection and prepends data from a previous, incomplete, message
        /// and updates the internal message tracking state.
        /// </summary>
        /// <param name="messages"></param>
        private List<string> PruneReceivedMessages(List<string> messages)
        {            
            // Append the first line to the incomplete line given to us during the last pass if one exists.
            if (this.currentData.Any() && messages.Any())
            {
                messages[0] = string.Format("{0} {1}", string.Join(" ", this.currentData), messages[0]);
                this.currentData.Clear();
            }

            // If we have more than 1 line and the last line in the collection does not end with a line feed
            // then we add it to our current data so it may be completed during the next pass. 
            // We then remove it from the lines collection because it can be infered that the remainder will have
            // a new line due to being split on \n.
            if (messages.Count > 1 && !messages.Last().EndsWith("\r\n"))
            {
                this.currentData.Add(messages.Last());
                messages.Remove(messages.Last());
            }

            return messages;
        }
    }
}

问题主要集中在以下方法:

    public void StartListeningForData()
    {
        this.Buffer = new byte[bufferSize];
        this.CurrentSocket.BeginReceive(this.Buffer, 0, bufferSize, 0, new AsyncCallback(this.ReceiveData), null);
        this.Player.CommandManager.CommandCompleted += this.HandleCommandExecutionCompleted;
    }

    public void SendMessage(string message)
    {
        byte[] buffer = Encoding.ASCII.GetBytes(message);
        this.CurrentSocket.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(this.CompleteMessageSending), null);
    }

    private void CompleteMessageSending(IAsyncResult asyncResult)
    {
        this.CurrentSocket.EndSend(asyncResult);
    }

    private void HandleCommandExecutionCompleted(object sender, CommandCompletionArgs e)
    {
        this.SendMessage($"{e.Command} executed.\r\n");
    }

当我的东西处理消息时,它会创建一个新的CommandCompletionArg,将命令字符串传递给它,并将其发送到事件处理程序。调用HandleCommandExecutionCompleted处理程序时,e.Command属性确实保存通过Telnet客户端发送的数据。我希望看到我的命令回复给我。

输入:“Hello World” 预期结果:“Hello World已执行。” 结果:“已执行。”

当我键入其他命令时,附加命令将与之前的命令结果一起发送。看起来如果我发送的命令字符串长于我回显的字符串,它会将字符串添加到结尾。您可以在Windows和OS X上看到截图中的奇怪现象。

我做错了什么?我不明白为什么当我调试时,我可以看到e.Command实际上有数据,但它从不将它发送回客户端。它只发送“已执行”。

OS X Windows

如果它有帮助,code is open source and available可以完整查看。您可以从GitHub克隆它并运行Desktop.Server.App项目。您连接的端口是5000。

1 个答案:

答案 0 :(得分:1)

在学习了一些特殊字符后,我能够解决这个问题。从telnet客户端返回的消息已经附加了\ r \ n。我只是将内容从服务器转发回客户端,导致客户端将光标移回到行的开头,并使用&#34;覆盖原始消息&#34;

发送给客户的内容为Hello World\r executed.\r\n。在我从客户端收到的内容结尾处删除\ r \ n后,我能够将其作为“Hello World execute \ r \ n&n 39”发送回客户端。问题解决了。

我也多次订阅同一个活动。

    public void StartListeningForData()
    {
        this.Buffer = new byte[bufferSize];
        this.CurrentSocket.BeginReceive(this.Buffer, 0, bufferSize, 0, new AsyncCallback(this.ReceiveData), null);
        this.Player.CommandManager.CommandCompleted += this.HandleCommandExecutionCompleted;
    }

事件处理程序将调用SendMessage

    public void SendMessage(string message)
    {
        byte[] buffer = Encoding.ASCII.GetBytes(message);
        this.CurrentSocket.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(this.CompleteMessageSending), this.CurrentSocket);
    }

    private void CompleteMessageSending(IAsyncResult asyncResult)
    {
        var client = asyncResult.AsyncState as Socket;

        client.EndSend(asyncResult);
    }

    private void HandleCommandExecutionCompleted(object sender, CommandCompletionArgs e)
    {
        this.SendMessage($"{e.Command} executed.\r\n");
    }

因此导致我的邮件重复发送到客户端。