为什么我的流读取器和编写器突然停止工作?

时间:2014-01-29 20:15:50

标签: c# networking chat streamreader streamwriter

好的,所以我正在尝试为一群在线玩DnD的朋友创建一个简单的TCP / IP聊天应用程序。最终我想添加更多功能,但是现在我只想让聊天工作!!

以下是我对主服务器的代码

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

以下是主要的沟通类,由每个客户单独使用

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

最后,这是Client类的代码:

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

我对上述代码的问题是:我可以完全正常连接到正在运行的服务器,并且我已经连接的服务器正确地提示我,然后它提示我输入昵称。

我在文本框中输入昵称,然后按发送。然而,在此之后,我停止从服务器一起接收消息。没什么。我甚至可以通过垃圾邮件连接按钮,它会不断显示相同的两条消息:

“已连接” “输入昵称”

我一直在努力解决这个问题近5个小时,我根本不知道发生了什么。我觉得它非常简单,因为解决方案总是很简单。

那么,慷慨的SO,你能解决我的问题吗?为什么我的streamreader和streamwriter突然停止工作?!?!?!

1 个答案:

答案 0 :(得分:1)

两件事:

首先,跳过if (reader.Peek() > 0)。只需致电reader.ReadLine();这将阻止,直到你有一条线可用。我不确定为什么,但即使在发送消息后,Peek返回-1,但ReadLine在该点返回一行,解决问题。无论如何,在Application.DoEvents()上旋转并没有帮助。

(同样,您可以跳过if (server.Pending()))。

其次,你使用Invoke是错误的;你不应该“调用”RunChat(),因为这是重复轮询新数据流的方法。这意味着您将在UI线程上运行整个方法正是您想要避免的。用户界面正忙着抽取Windows消息队列。您应该“仅调用”修改控件属性的代码。

(我怀疑这就是为什么你发现有必要使用Application.DoEvents()。如果你正确处理你的线程,你不应该需要它。)

(另外,第一个你要做的就是检查InvokeRequired。正如你现在的方法,你正在创建一个你永远不能使用的StreamReader。还有其他地方你可以这样做,但这不是主题。)

以下是两条建议:

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

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

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

或者,使用更经典的“调用”模式:

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

第一个优点是只创建一个Delegate对象;第二个每次创建一个新的。

最后,这是解决问题的一种非常C#1.2的方法。更新的方法将使用async / await来避免创建所有这些线程(更不用说System.Collections.Generic.Dictionary<,>而不是HashTable)。

相关问题