C# 简单的TCP聊天客户端代码回顾和问题

C# 简单的TCP聊天客户端代码回顾和问题,c#,asynchronous,tcp,C#,Asynchronous,Tcp,我对编程比较陌生,我自己动手做了一个简单的聊天应用程序;想想当年的目标吧。这与其说是一个问题列表,不如说是一个代码审查请求。在大多数情况下,该应用程序会出现一些小错误,我将在下面重点介绍这些错误。请参阅项目代码:以及下面列出的代码。问题一直存在。感谢您可能提供的任何帮助 以下是服务器代码:Server.cs namespace Server { public class Server { // This is the port that the server will be list

我对编程比较陌生,我自己动手做了一个简单的聊天应用程序;想想当年的目标吧。这与其说是一个问题列表,不如说是一个代码审查请求。在大多数情况下,该应用程序会出现一些小错误,我将在下面重点介绍这些错误。请参阅项目代码:以及下面列出的代码。问题一直存在。感谢您可能提供的任何帮助

以下是服务器代码:Server.cs

namespace Server
{
  public class Server
  {
  // This is the port that the server will be listening on 
  const int PORT = 500;
  // This is where the usernames and their connections will be held
  readonly Dictionary<string, ServerUserConnection> userToConnections = new Dictionary<string, ServerUserConnection>();
  readonly TcpListener listener;

  public static void Main()
  {
    // Constructor for the ChatServer
    Server server = new Server();

    while(true)
    {
    Thread.Sleep(1); // Temp to keep alive
    }
  }

  /// <summary>
  /// Listens for data from clients 
  /// </summary>
  public Server()
  {
    listener = TcpListener.Create(PORT);
    listener.Start();
    WaitForConnections();
  }

  /// <summary>
  /// Begins an asynchronous operation to accept an incoming connection attempt
  /// </summary>
  private void WaitForConnections()
  {
    listener.BeginAcceptTcpClient(OnConnect, null);
  }

  /// <summary>
  /// This method is executed asynchronously
  /// Connects the client to the server
  /// Broadcasts the user to client to be displayed on the chatform
  /// Then waits for another connection to be established
  /// </summary>
  /// <param name="ar"></param>
  void OnConnect(IAsyncResult ar)
  {
    //Asynchronously accepts an incoming connection attempt and creates a new TcpClient to handle remote host communication.
    TcpClient client = listener.EndAcceptTcpClient(ar);
    Console.WriteLine("Connected");

    ReceiveUser(client);

    BroadcastUserList();

    WaitForConnections();
  }

  /// <summary>
  /// Connects a user to the server and adds them to the dictionary userToConnections
  /// </summary>
  /// <param name="client"></param>
  public void ReceiveUser(TcpClient client)
  {
    ServerUserConnection connection = new ServerUserConnection(this, client); // Constructor
    userToConnections.Add(connection.userName, connection);
  }

  /// <summary>
  /// For each user that is connected append the userList to include that user
  /// TODO Do not need to keep a running list of users; send the user over then throw it away
  /// </summary>
  void BroadcastUserList()
  {
    string userList = "";
    foreach(var connection in userToConnections)
    {
      userList += $"{connection.Value.userName},";
    }

    SendMsgToAll(MessageType.UserList, null, userList);
  }

  /// <summary>
  /// Pushes out messages to the connected clients
  /// </summary>
  /// <param name="type"></param>
  /// <param name="user"></param>
  /// <param name="message"></param>
  public void SendMsgToAll(MessageType type, ServerUserConnection user, string message)
  {
    Console.WriteLine($"{user?.userName}: {message}");

    foreach(var connection in userToConnections)
    {
      Console.WriteLine($"Sending to {connection.Value.userName}");
      Utils.SendInformation(type, connection.Value.stream, message);
    }
  }
}
}
这是UserConnection.cs:我仍然需要处理断开用户连接的问题。如果某个聊天表单关闭,则会使应用程序崩溃。

namespace Server
{
  public class ServerUserConnection : UserConnection 
  {
    readonly Server server;

    /// <summary>
    /// Facilitates connection
    /// </summary>
    /// <param name="server"></param>
    /// <param name="client"></param>
    public ServerUserConnection(Server server, TcpClient client) : base(client, GetUsername(client)) // Inherits from UserConnection()
    {
      this.server = server;
    }

    private static string GetUsername(TcpClient client)
    {
      NetworkStream stream = client.GetStream();
      if(stream.CanRead)
      {
        // Receives infromation from the stream, determines MessageType, and returns username 
        string userName = Utils.ReceiveInformation(stream, client, out MessageType type); 
        Console.WriteLine(userName);
        return userName;
      }

      return null;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="type"></param>
    /// <param name="message"></param>
    protected override void OnRead(MessageType type, string message)
    {
      if(type != MessageType.ChatMessage)
      {
        return;
      }

      server.SendMsgToAll(MessageType.ChatMessage, this, $"{userName} {message}");
    }
  }
}
public class ClientUserConnection : UserConnection
{
  public readonly Client.ChatClient chatClient;
  public string message = "";
  public string userListText;

  /// <summary>
  /// 
  /// </summary>
  /// <param name="client"></param>
  /// <param name="chatClient"></param>
  /// <param name="userName"></param>
  public ClientUserConnection(TcpClient client, Client.ChatClient chatClient, string userName) : base(client, userName) // Inherits from UserConnection()
  {
    this.chatClient = chatClient;
  }

  /// <summary>
  /// When the data is reads determine what kind of message it is 
  /// Parse/split out the message and user; display only relevant data
  /// TODO Do not need to keep a running list of messages; send the message over then throw it away
  /// </summary>
  /// <param name="type"></param>
  /// <param name="message"></param>
  protected override void OnRead(MessageType type, string message)
  {
    if(type == MessageType.ChatMessage)
    {
      int iSpace = message.IndexOf(" ");

      if(iSpace < 0)
      {
        // if error
        return;
      }

      string from = message.Substring(0, iSpace);
      string chatMessage = message.Substring(iSpace + 1, message.Length - iSpace - 1);
      this.message += $"[{from}]: {chatMessage}{Environment.NewLine}";
    }
    else if(type == MessageType.UserList)
    {
      string[] userList = message.Split(',');
      string userListText = "";
      for(int i = 0; i < userList.Length; i++)
      {
        userListText += $"{userList[i]}{Environment.NewLine}";
      }
      this.userListText = userListText;
    }
  }
}
public abstract class UserConnection
{
  public readonly TcpClient client;
  public readonly NetworkStream stream;
  public readonly string userName;
  byte[] data;

  /// <summary>
  /// 
  /// </summary>
  /// <param name="client"></param>
  /// <param name="userName"></param>
  public UserConnection(TcpClient client, string userName)
  {
    this.client = client;
    this.userName = userName;
    stream = client.GetStream();

    data = new byte[client.ReceiveBufferSize];

    WaitForData();
  }

  /// <summary>
  /// 
  /// </summary>
  private void WaitForData()
  {
    Console.WriteLine("Wait");

    stream.BeginRead(data, 0, data.Length, OnReadData, null);
  }

  /// <summary>
  /// SocketException: An existing connection was forcibly closed by the remote host
  /// </summary>
  /// <param name="ar"></param>
  void OnReadData(IAsyncResult ar)
  {
    Console.WriteLine("Read");

    int result = stream.EndRead(ar); // TODO disconnect & error handling

    Console.WriteLine("Read done");
    if(result <= 0)
    {
      Console.WriteLine("Error reading");
      return;
    }

    string message = Utils.ReceiveInformation(data, result, out MessageType type);

    OnRead(type, message);

    WaitForData();
  }

  /// <summary>
  /// 
  /// </summary>
  /// <param name="type"></param>
  /// <param name="message"></param>
  protected abstract void OnRead(MessageType type, string message);
}
公共抽象类用户连接
{
公共只读TCP客户端;
公共只读网络流;
公共只读字符串用户名;
字节[]数据;
/// 
/// 
/// 
/// 
/// 
公共用户连接(TcpClient客户端,字符串用户名)
{
this.client=client;
this.userName=用户名;
stream=client.GetStream();
数据=新字节[client.ReceiveBufferSize];
WaitForData();
}
/// 
/// 
/// 
私有void WaitForData()
{
控制台。写入线(“等待”);
stream.BeginRead(data,0,data.Length,OnReadData,null);
}
/// 
///SocketException:远程主机强制关闭了现有连接
/// 
/// 
void OnReadData(IAsyncResult ar)
{
控制台。写入线(“读取”);
int result=stream.EndRead(ar);//TODO断开连接和错误处理
控制台。写入线(“读取完成”);

如果(结果你粘贴了一堵巨大的代码墙。你似乎有很好的问题,但你需要将你的代码片段限制为仅相关的代码,否则用户会在不断向下滚动时晕眩……或者,如果你真的在寻找代码审查,则会有一个问题。请阅读他们的问题,以验证你的问题是否是一个好问题适合,在发布之前。@LynnCrumbling你知道在代码审查上发布的合适协议是什么吗?你是否像我在这里做的那样包括代码块。我应该写一个概述,只考虑存储库吗?我知道项目必须按照他们的规则工作,但我的仍然有一些错误。我在这里相对较新,如何包括maller代码段没有完整的堆栈溢出上下文?感谢您提供的任何建议。我想在这里进行富有成效的交互。您有没有研究过SignalR?
public enum MessageType
{
    Connect, UserList, ChatMessage
}
public abstract class UserConnection
{
  public readonly TcpClient client;
  public readonly NetworkStream stream;
  public readonly string userName;
  byte[] data;

  /// <summary>
  /// 
  /// </summary>
  /// <param name="client"></param>
  /// <param name="userName"></param>
  public UserConnection(TcpClient client, string userName)
  {
    this.client = client;
    this.userName = userName;
    stream = client.GetStream();

    data = new byte[client.ReceiveBufferSize];

    WaitForData();
  }

  /// <summary>
  /// 
  /// </summary>
  private void WaitForData()
  {
    Console.WriteLine("Wait");

    stream.BeginRead(data, 0, data.Length, OnReadData, null);
  }

  /// <summary>
  /// SocketException: An existing connection was forcibly closed by the remote host
  /// </summary>
  /// <param name="ar"></param>
  void OnReadData(IAsyncResult ar)
  {
    Console.WriteLine("Read");

    int result = stream.EndRead(ar); // TODO disconnect & error handling

    Console.WriteLine("Read done");
    if(result <= 0)
    {
      Console.WriteLine("Error reading");
      return;
    }

    string message = Utils.ReceiveInformation(data, result, out MessageType type);

    OnRead(type, message);

    WaitForData();
  }

  /// <summary>
  /// 
  /// </summary>
  /// <param name="type"></param>
  /// <param name="message"></param>
  protected abstract void OnRead(MessageType type, string message);
}
public class Utils
{
  /// <summary>
  /// 
  /// </summary>
  /// <param name="stream"></param>
  /// <param name="connection"></param>
  /// <param name="type"></param>
  /// <returns></returns>
  public static string ReceiveInformation(NetworkStream stream, TcpClient connection, out MessageType type)
  {
    byte[] bytes = new byte[connection.ReceiveBufferSize];
    int length = stream.Read(bytes, 0, bytes.Length);
    return ReceiveInformation(bytes, length, out type);
  }

  /// <summary>
  /// 
  /// </summary>
  /// <param name="bytes"></param>
  /// <param name="length"></param>
  /// <param name="type"></param>
  /// <returns></returns>
  public static string ReceiveInformation(byte[] bytes, int length, out MessageType type)
  {
    string data = Encoding.ASCII.GetString(bytes, 0, length);
    int iSpace = data.IndexOf(' ');
    if(iSpace < 0)
    {
      // TODO
    }

    string typeString = data.Substring(0, iSpace);
    type = (MessageType)Enum.Parse(typeof(MessageType), typeString);
    string message = data.Substring(iSpace + 1, data.Length - iSpace - 1);

    return message;
  }

  /// <summary>
  /// 
  /// </summary>
  /// <param name="type"></param>
  /// <param name="stream"></param>
  /// <param name="message"></param>
  public static void SendInformation(MessageType type, NetworkStream stream, string message)
  {
    Byte[] sendBytes = Encoding.UTF8.GetBytes($"{type} {message}");
    stream.Write(sendBytes, 0, sendBytes.Length);
  }
}