简单的TCP聊天客户端代码审查和问题

时间:2017-04-08 23:54:46

标签: c# asynchronous tcp

我对编程比较陌生,我自己做了一个简单的聊天应用程序;从今天开始思考AIM。这更像是一个代码审查请求,而不是一个问题列表。在大多数情况下,该应用程序使用一些小错误,我将在下面重点介绍。请参阅项目中的代码:https://github.com/N8STROMO/Basic-Chat-Tcp以及下面列出的代码。问题随之而来。感谢您提供的任何帮助!

这是服务器代码: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);
    }
  }
}
}

这是ServerUserConnection.cs:我无法理解这里的继承是如何工作的以及base关键字的作用。

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}");
    }
  }
}

以下是客户端代码:Client.cs

namespace Client
{
  public class ChatClient
  {
    private const int PORT = 500;
    TcpClient client = new TcpClient();
    public ChatForm chatForm;
    public ClientUserConnection userConnection;
    string data;

    [STAThread]
    public static void Main()
    {
      Application.EnableVisualStyles();
      Application.SetCompatibleTextRenderingDefault(false);
      var client = new ChatClient();
      var form = new ConnectForm(client);
      Application.Run(form);
    }

    /// <summary>
    /// This is called when the ConnectForm btnSubmit is pressed.
    /// </summary>
    /// <param name="ipAddress"></param>
    /// <param name="userName"></param>
    public void ConnectToServer(string ipAddress, string userName)
    {
      client.Connect(ipAddress, PORT);
      SendUserName(userName);
    }

    /// <summary>
    /// Sends user to the server
    /// </summary>
    public void SendUserName(string user)
    {
      userConnection = new ClientUserConnection(client, this, user);
      Utils.SendInformation(MessageType.Connect, client.GetStream(), user);
    }

    /// <summary>
    /// Sends a message to the server
    /// </summary>
    /// <param name="msg"></param>
    public void SendMessage(string msg)
    {
      Utils.SendInformation(MessageType.ChatMessage, userConnection.stream, msg);
    }
  }
}

这是ClientUserConnection.cs:有没有办法重构或优化这个,以便不是保持一个正在运行的消息列表而是发送消息并将其丢弃?同样,关于继承的问题也是如此。

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;
    }
  }
}

在此项目中,类库中还有三个文件:

这是MessageType.cs:

public enum MessageType
{
    Connect, UserList, ChatMessage
}

这是UserConnection.cs:我仍然需要处理断开连接的用户。如果其中一个聊天表单已关闭,则会导致应用程序崩溃。

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);
}

这是Utils.cs:我无法理解有两种叫做ReceiveInformation()的方法以及它们如何相互作用

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);
  }
}

0 个答案:

没有答案