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