问题描述:

Ok, so I'm attempting to create a simple Chat application over TCP/IP for a group of friends of mine who play DnD online. Eventually I want to add more features, but for now I just want the chat to work!!

Here is the code I have for the Main Server

class MainServer

{

IPAddress m_address = IPAddress.Parse("127.0.0.1");

Int32 m_port = 5550;

public static Hashtable userNicknames = new Hashtable(50);

public static Hashtable connectionToNick = new Hashtable(50);

public MainServer()

{

TcpListener listener = new TcpListener(m_address, m_port);

Thread listenThread = new Thread(new ParameterizedThreadStart(StartListening));

listenThread.Start(listener);

Console.WriteLine("Listening for incoming connection requests...");

}

private void StartListening(Object listener)

{

TcpListener server = (TcpListener)listener;

ClientCommCenter commC;

server.Start();

while (true)

{

if (server.Pending())

{

TcpClient client = server.AcceptTcpClient();

Console.WriteLine("Client has connected...");

commC = new ClientCommCenter(client);

}

}

}

public static void SendSystemMessage(string msg)

{

StreamWriter writer;

TcpClient[] connectedClients = new TcpClient[MainServer.userNicknames.Count];

MainServer.userNicknames.Values.CopyTo(connectedClients, 0);

for (int ii = 0; ii < connectedClients.Length; ii++)

{

try

{

if (msg.Trim().Equals(String.Empty))

continue;

writer = new StreamWriter(connectedClients[ii].GetStream());

writer.WriteLine("Message from server: " + msg);

writer.Flush();

writer = null;

}

catch (Exception e)

{

MainServer.userNicknames.Remove(MainServer.connectionToNick[connectedClients[ii]]);

MainServer.connectionToNick.Remove(connectedClients[ii]);

}

}

}

public static void SendMessageToAll(string nickname, string msg)

{

StreamWriter writer;

TcpClient[] connectedClients = new TcpClient[MainServer.userNicknames.Count];

MainServer.userNicknames.Values.CopyTo(connectedClients, 0);

for (int ii = 0; ii < connectedClients.Length; ii++)

{

try

{

if (msg.Trim().Equals(String.Empty))

continue;

writer = new StreamWriter(connectedClients[ii].GetStream());

writer.WriteLine(nickname + ": " + msg);

writer.Flush();

writer = null;

}

catch (Exception e)

{

String user = (string)MainServer.connectionToNick[connectedClients[ii]];

SendSystemMessage("ATTENTION: " + user + " has disconnected from chat");

MainServer.userNicknames.Remove(user);

MainServer.connectionToNick.Remove(connectedClients[ii]);

}

}

}

}

Here is the main communication class, used separately by each client

class ClientCommCenter

{

TcpClient m_client;

StreamReader m_reader;

StreamWriter m_writer;

String m_nickname;

public ClientCommCenter(TcpClient client)

{

m_client = client;

Thread chatThread = new Thread(new ThreadStart(StartChat));

chatThread.Start();

}

private String GetNick()

{

m_writer.WriteLine("Enter a nickname to begin.");

m_writer.Flush();

return m_reader.ReadLine();

}

private void StartChat()

{

m_reader = new StreamReader(m_client.GetStream());

m_writer = new StreamWriter(m_client.GetStream());

m_writer.WriteLine("Connected to DnD Chat!!");

m_nickname = GetNick();

while (MainServer.userNicknames.Contains(m_nickname))

{

m_writer.WriteLine("ERROR!!! Username already in use");

m_nickname = GetNick();

}

MainServer.userNicknames.Add(m_nickname, m_client);

MainServer.connectionToNick.Add(m_client, m_nickname);

MainServer.SendSystemMessage("****** " + m_nickname + " ****** has joined the chat!");

m_writer.WriteLine("Now connected....");

m_writer.Flush();

Thread startChatting = new Thread(new ThreadStart(runChat));

startChatting.Start();

}

private void runChat()

{

try

{

String clientMessage = String.Empty;

while(true){

clientMessage = m_reader.ReadLine();

MainServer.SendMessageToAll(m_nickname, clientMessage);

}

}

catch(Exception e)

{

Console.WriteLine(e);

}

}

}

And finally, here is the code for the Client class:

public partial class MainForm : Form

{

[DllImport("kernel32.dll")]

private static extern void ExitProcess(int a);

TcpClient client;

StreamReader m_reader;

StreamWriter m_writer;

public MainForm()

{

InitializeComponent();

}

private void MainForm_FormClosing(object sender, FormClosingEventArgs e)

{

e.Cancel = false;

Application.Exit();

if (m_reader != null)

{

m_reader.Dispose();

}

ExitProcess(0);

}

private void MainForm_KeyUp(object sender, KeyEventArgs e)

{

if (e.KeyCode == Keys.Enter)

{

SendChat();

}

}

private void SendChat()

{

TextBox txtChat = (TextBox)chatEntry;

if (chatEntry.Lines.Length >= 1)

{

m_writer.WriteLine(txtChat.Text);

m_writer.Flush();

chatEntry.Text = String.Empty;

chatEntry.Lines = null;

}

}

private void RunChat()

{

StreamReader reader = new StreamReader(client.GetStream());

while (true)

{

Application.DoEvents();

if (this.InvokeRequired)

{

this.Invoke(new MethodInvoker( delegate{

RunChat();

}));

}

if (reader.Peek() > 0)

{

chatDisplay.AppendText(reader.ReadLine() + "\r\n");

chatDisplay.SelectionStart = chatDisplay.Text.Length;

}

}

}

private void toolstripConnectButton_Click(object sender, EventArgs e)

{

client = new TcpClient("127.0.0.1", 5550);

m_writer = new StreamWriter(client.GetStream());

m_reader = new StreamReader(client.GetStream());

Thread chatThread = new Thread(new ThreadStart(RunChat));

chatThread.Start();

while (true)

{

Application.DoEvents();

}

}

private void sendButton_Click(object sender, EventArgs e)

{

SendChat();

}

}

The problem that I am having with the above code is this: I can connect to the running server perfectly fine, and I am correctly prompted by the server that I have connected, and it then prompts me for a nickname.

I type the nickname into the text box and press send. After this occurs however, I stop receiving messages from the server all together. Literally nothing. I can even spam the connect button and it constantly shows up with the same two messages:

"Connected"

"Enter a nickname"

I have been trying to figure this out for close to 5 hours now, and I simply have no idea what is going on. I have a feeling it is something incredibly simple, as the solution is ALWAYS simple.

So, generous people of SO, can you figure out my problem? Why does my streamreader and streamwriter suddenly stop working?!?!?!

网友答案:

Two things:

First, skip the if (reader.Peek() > 0). Just call reader.ReadLine(); this will block until you have a line available. I am not sure why, but even after sending the message, Peek is returning -1, but ReadLine returns a line at that point, fixing the problem. Anyway, spinning around on Application.DoEvents() is not helping matters.

(Similarly, you can skip if (server.Pending())).

Second, your use of Invoke is faulty; you should not be "Invoking" RunChat() because that is the method that repeatedly polls the stream for new data. This means you will run the entire method on the UI thread, which is precisely what you want to avoid. The UI is busy pumping the Windows message queue. You should "Invoke" only the code that modifies the control's properties.

(I suspect that is why you found it necessary to use Application.DoEvents() anyway. You shouldn't need it if you are handling your threading correctly.)

(Also, the first thing you should do is to check InvokeRequired. As your method is now, you're creating a StreamReader that you can never use. There are other places where you do that, but that's off topic.)

Here are two suggestions:

private void RunChat()
{
    StreamReader reader = new StreamReader(client.GetStream());

    Delegate invoker = new Action<string>(AppendChatText);

    while (true)
        Invoke(invoker, reader.ReadLine());
}

or, to use the more classic "invoke" pattern:

private void RunChat()
{
    StreamReader reader = new StreamReader(client.GetStream());

    while (true)
        AppendChatText(reader.ReadLine());
}

private void AppendChatText(string text)
{
    if (this.InvokeRequired)
    {
        this.Invoke((Action<string>)AppendChatText, text);
        return;
    }

    chatDisplay.AppendText(text + "\r\n");
    chatDisplay.SelectionStart = chatDisplay.Text.Length;
}

The first has the advantage of creating only one Delegate object; the second creates a new one each time.

Finally, this is a very C# 1.2 approach to the problem. A more up-to-date approach would use async/await to avoid creating all those threads (not to mention System.Collections.Generic.Dictionary<,> instead of HashTable).

相关阅读:
Top