我有一个类来处理与连接到我的服务器的客户端之间的通信。我能够处理从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实际上有数据,但它从不将它发送回客户端。它只发送“已执行”。
如果它有帮助,code is open source and available可以完整查看。您可以从GitHub克隆它并运行Desktop.Server.App项目。您连接的端口是5000。
答案 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");
}
因此导致我的邮件重复发送到客户端。