简体   繁体   English

Tcp客户端服务器表单应用问题

[英]Tcp client server form app issue

I have stuck with a problem and that is when I try to write from my server to my multiple clients, it keeps on writing the empty string. 我遇到了一个问题,那就是当我尝试从我的服务器写入我的多个客户端时,它继续编写空字符串。 I'm sharing my code and its multiple client server app in which clients can write to server while server can write to all running clients. 我正在共享我的代码和多个客户端服务器应用程序,客户端可以在其中写入服务器,而服务器可以写入所有正在运行的客户端。 Clients writing to server quiet fine. 写客户端的客户端很安静。 but server isn't :( 但服务器不是:(

Server code: 服务器代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using System.Collections;

namespace ServerGui
{
    public partial class Form1 : Form
    {
        TcpListener tcpListen;
        Thread listenThread;
        String read = "";
        ArrayList collect = new ArrayList();
        int counter = 0;
        ArrayList NS = new ArrayList();
        TcpClient general = null;
        readonly ManualResetEvent mre = new ManualResetEvent(false);

        public Form1()
        {
            InitializeComponent();
        }

        private void connectServerToolStripMenuItem_Click(object sender, EventArgs e)
        {
            tcpListen = new TcpListener(IPAddress.Any, 3000);
            listenThread = new Thread(new ThreadStart(ListenForClients));
            listenThread.Start();
            connectServerToolStripMenuItem.Enabled = false;
        }

        public void ListenForClients()
        {
            tcpListen.Start();

            while (true)
            {

                TcpClient client = tcpListen.AcceptTcpClient();
                collect.Insert(counter, client);
                general = (TcpClient)collect[counter];
                if (textBox1.InvokeRequired)
                {
                    textBox1.Invoke(new MethodInvoker(delegate { textBox1.AppendText(client.Client.LocalEndPoint.ToString() + "  Connected.");
                    textBox1.AppendText(Environment.NewLine); }));
                }
                else
                {
                    textBox1.AppendText(client.Client.LocalEndPoint.ToString() + "  Connected.");
                    textBox1.AppendText(Environment.NewLine);
                }
                Thread clientThread = new Thread(new ParameterizedThreadStart(Reader));
                clientThread.Start(collect[counter]);
                Thread clientThread1 = new Thread(new ParameterizedThreadStart(Writer));
                clientThread1.Start(collect[counter]);
                counter++;
            }
        }
        public void Reader(object client)
        {
            TcpClient tcpClient = (TcpClient)client;
            NetworkStream clientStream = tcpClient.GetStream();
            StreamReader sr = new StreamReader(clientStream);
            while (true)
            {
                try
                {
                    read = sr.ReadLine();
                }
                catch
                {
                    if (textBox1.InvokeRequired)
                    {
                        textBox1.Invoke(new MethodInvoker(delegate
                        {
                              textBox1.AppendText(tcpClient.Client.LocalEndPoint.ToString() +
 " disconnected.");
                            textBox1.AppendText(Environment.NewLine);
                        }));
                    }
                    else
                    {
                        textBox1.AppendText(tcpClient.Client.LocalEndPoint.ToString() + " disconnected.");
                        textBox1.AppendText(Environment.NewLine);
                    }
                    return;
                }
                if (textBox1.InvokeRequired)
                {
                    textBox1.Invoke(new MethodInvoker(delegate
                    {
                        textBox1.AppendText("<" +         tcpClient.Client.LocalEndPoint.ToString() + "> saying: " + read);
                        textBox1.AppendText(Environment.NewLine);
                    }));
                }
                else 
                {
                    textBox1.AppendText("<" + tcpClient.Client.LocalEndPoint.ToString() + "> saying: " + read);
                    textBox1.AppendText(Environment.NewLine);
                }
            }

            tcpClient.Close();
        }

        public void Writer(object client)
        {
            TcpClient tcpClient = (TcpClient)client;
            NetworkStream ns = tcpClient.GetStream();
            mre.WaitOne();
            while (true)
            {
                StreamWriter sw = new StreamWriter(ns);

                    string str = textBox2.Text;
                    sw.WriteLine(str);
                    sw.Flush();

                if (textBox2.InvokeRequired)
                {
                    textBox2.Invoke(new MethodInvoker(delegate 
                        {
                            textBox2.Clear();
                        }));
                }
                else
                textBox2.Clear();
                }
            tcpClient.Close();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            mre.Set();
            textBox1.AppendText(textBox2.Text);
            textBox1.AppendText(Environment.NewLine);
            textBox2.Clear();
        }

        private void CheckKeys(object sender, System.Windows.Forms.KeyPressEventArgs e)
        {
            if (e.KeyChar == (char)13)
            {
                mre.Set();
                textBox1.AppendText(textBox2.Text);
                textBox1.AppendText(Environment.NewLine);
                textBox2.Clear();
            }
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }
    }
}

Client code: 客户代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.IO;

namespace ClientGcui
{
    public partial class Form1 : Form
    {
        NetworkStream general = null;
        public Form1()
        {
            InitializeComponent();

        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

        private void connectToServerToolStripMenuItem_Click(object sender, EventArgs e)
        {
            String str = "";
            TcpClient client = new TcpClient();
            IPEndPoint server = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 3000);
            client.Connect(server);
            textBox1.AppendText("Connected to server");
            textBox1.AppendText(Environment.NewLine);
            NetworkStream clientStream = client.GetStream();
            general = clientStream;
            StreamReader sr = new StreamReader(clientStream);
            StreamWriter sw = new StreamWriter(clientStream);
            Thread reader = new Thread(new ParameterizedThreadStart(readserver));
            reader.Start(sr);
        }
        public void readserver(object client)
        {
            StreamReader sr = (StreamReader)client;
            string read = "";
            while (true)
            {
                try
                {
                    read = sr.ReadLine();
                }
                catch
                {
                    if (textBox1.InvokeRequired)
                    {
                        textBox1.Invoke(new MethodInvoker(delegate
                        {
                            textBox1.AppendText("Disconnected from Server.");
                            textBox1.AppendText(Environment.NewLine);
                        }));
                    }
                    else
                    {
                        textBox1.AppendText("Disconnected from Server.");
                        textBox1.AppendText(Environment.NewLine);
                    }
                }
                if (textBox1.InvokeRequired)
                {
                    textBox1.Invoke(new MethodInvoker(delegate
                    {
                        textBox1.AppendText(sr.ReadLine());
                        textBox1.AppendText(Environment.NewLine);
                    }));
                }
                else
                {
                    textBox1.AppendText(sr.ReadLine());
                    textBox1.AppendText(Environment.NewLine);
                }
                }
            sr.Close();
            }


        private void button1_Click(object sender, EventArgs e)
        {
            StreamWriter sw = new StreamWriter(general);
            sw.WriteLine(textBox2.Text);
            sw.Flush();
            textBox1.AppendText(textBox2.Text);
            textBox1.AppendText(Environment.NewLine);
            textBox2.Clear();
        }

        private void CheckKeys(object sender, System.Windows.Forms.KeyPressEventArgs e)
        {
            if (e.KeyChar == (char)13)
            {
                StreamWriter sw = new StreamWriter(general);
                sw.WriteLine(textBox2.Text);
                sw.Flush();
                textBox1.AppendText(textBox2.Text);
                textBox1.AppendText(Environment.NewLine);
                textBox2.Clear();
            }
        }
    }
}

What's with 'textBox1', 'textbox2', 'button1'. 什么是'textBox1','textbox2','button1'。 There dosn't seem to be one application-specific component name in this code - you know, the stuff that makes it easily and quickly understandable to experienced developers quickly looking through posts. 在这段代码中似乎没有一个特定于应用程序的组件名称 - 您知道,这些东西使经验丰富的开发人员能够轻松快速地理解这些内容。 Why have you not edited the component names to something meaningful before posting here? 在发布此处之前,为什么没有将组件名称编辑为有意义的内容? It's not like your English is bad, (which I could then understand), your question is clear enough. 这不像你的英语不好(我可以理解),你的问题很清楚。

Anyway, your writer is not Invoke()ing the textbox2 read and then the first writer to clear textBox2 wins the race and stuffs up all the other writers who then read nothing. 无论如何,你的作家不是调用textbox2读取,然后第一个清除textBox2的作者赢得了比赛,并填补了所有其他作者,然后什么都没读。

..and in the CheckKeys your are signalling the mre and then running on an clearing the textbox2 before the race-winning writer even gets a chance to read it. ..并且在CheckKeys中你发出信号,然后在比赛获胜的作家甚至有机会阅读它之前清理文本框2。

I can't see where you reset the mre which, anyway, is the wrong synchro object to use. 我无法看到你重置mre的位置,无论如何,是使用错误的同步对象。

You cannot skimp on multithreaded code. 你不能吝啬多线程代码。 You have to do it correctly. 你必须正确地做到这一点。

There are some possibilities for comms with your writer thread. 您的编写器线程可以使用通信。

1) Use a lock-protected, reference-counted object to hold on to your outgoing text from textbox2 until all the writers have done with it. 1)使用受锁保护的引用计数对象来保持textbox2中的传出文本,直到所有作者都完成它为止。 This implies that you know how many writers there are - something you are no keeping track of, at present. 这意味着你知道有多少作家 - 目前你无法跟踪这些作家。 The problem with this is that writers must not exit without deccing the refCount of all 'hold' instances whose refCount was initialized to include. 这样做的问题是编写者不能在不判断refCount初始化为包含的所有“hold”实例的refCount的情况下退出。 I doubt that you will get this right. 我怀疑你会做到这一点。

2) Use an object to hold on to your outgoing text from textbox2. 2)使用对象来保持textbox2中的传出文本。 Also, submit this instance to a timeout queue that holds onto a reference for, say, 10 minutes, by which time all writers will definitely have used it. 此外,将此实例提交到一个超时队列,该队列保留一个参考,例如10分钟,届时所有作者肯定会使用它。 This is a reasonable approach. 这是一种合理的方法。

3) Use an object to hold on to your outgoing text from textbox2 for each writer and submit a different copy on a producer-consumer queue to each writer. 3)使用对象为每个编写者保留textbox2中的传出文本,并在生产者 - 使用者队列上向每个编写者提交不同的副本。 This means that you need a vector of writers that must be kept up-to-date as clients connect and disconnect. 这意味着您需要一个编写器向量,在客户端连接和断开连接时必须保持最新。

4) There was a fourth way that I thought of when writing the other three, but I've forgot it now. 4)在编写其他三个时,我想到了第四种方式,但我现在已经忘记了。

I would go with (2) - least chance of something not working. 我会选择(2) - 最不可能出现问题。

Oh - forgot, signalling all the client threads. 哦 - 忘了,发信号通知所有客户端线程。 If you use an MRE, where are you going to reset it? 如果您使用MRE,您将在哪里重置它? How can you tell when all clients have read stuff and are about to wait again? 你怎么知道所有客户什么时候读过东西并且即将再次等待? You can't, and I suspect that this is why you have no resetEvent in your code - you don't know where to put it. 你不能,我怀疑这就是你的代码中没有resetEvent的原因 - 你不知道把它放在哪里。

Thining about it, given that you have a GC language, it might be simplest and safest to give each writer its own BlockingCollection and queue the reference to each text object to all writers. 考虑到你有一个GC语言,为每个编写器提供自己的BlockingCollection并将对每个文本对象的引用排队到所有编写器可能是最简单和最安全的。 Again, we are back to a, (thread-safe), collection of writers that have new entries put in on connect and removed on disconnect. 同样,我们回到了一个(线程安全的)写入器集合,它们在连接时放入新条目,在断开连接时删除。 Even with the thread-safe collection, you must expect, and catch, the odd exception because the main thread tries to issue a text object ref. 即使使用线程安全集合,您也必须预期并捕获奇怪的异常,因为主线程尝试发出文本对象引用。 to a writer that has just disconnected by not quite got round to removing itself from the collection yet. 对于一个刚刚断开连接的作家来说,还没有完全从收集中移除自己。

Also, a thread per writer is a bit of overkill. 另外,每个作者的一个帖子有点矫枉过正。 For max. 最大 clients, you would be better off using the threadPool for writes. 客户端,最好使用threadPool进行写入。 You would only then need one 'SeverClientSocket' class with would have its own read thread, a queueUpForWrite() method for the main thread to add to and a writeQueue() method for a pool thread to call. 你只需要一个'SeverClientSocket'类,它有自己的读线程,一个queueUpForWrite()方法用于添加主线程,一个writeQueue()方法用于调用池线程。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM