【问题标题】:Socket Programming C# Chat server, Cross thread in WinForms套接字编程 C# 聊天服务器,WinForms 中的跨线程
【发布时间】:2017-02-27 07:31:02
【问题描述】:

我尝试创建简单的聊天服务器,它可以处理多个客户端,并从所有客户端接收消息。 下面是我的代码。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace ChatServer
{

    public interface IServerPresenter
    {
        void Start(IServerView view);
        void StartServer(string address, int port);
        void CloseServer();

    }
    public class ServerPresenter : IServerPresenter
    {
        public IServerView ServerView { get; set; }

        private TcpListener Server;

        public void CloseServer()
        {
            throw new NotImplementedException();
        }

        public void Start(IServerView view)
        {
            ServerView = view;
        }

        public void StartServer(string address, int port)
        {
            try
            {
                Server = new TcpListener(IPAddress.Parse(address), port);
                Server.Start();
                ServerView.UpdateChatBox("Server is started, waiting for clients...");
                ListenForClientsAsync();

            }
            catch (Exception e)
            {
                ServerView.UpdateChatBox(e.Message);
            }
        }

        private async void ListenForClientsAsync()
        {

            try
            {
                while (true)
                {
                    var client = await Server.AcceptTcpClientAsync().ConfigureAwait(false);
                    if (client.Connected)
                    {
                        ReadStreamAsync(client);
                        ServerView.UpdateChatBox("Client connected.");
                    }
                }
            }
            catch (Exception e)
            {
                ServerView.UpdateChatBox(e.Message);
            }
        }

        private async void ReadStreamAsync(TcpClient client)
        {
            NetworkStream streamer = client.GetStream();

            Byte[] buff = new Byte[1024];
            String msg;

            int buffRecived = await streamer.ReadAsync(buff, 0, buff.Length).ConfigureAwait(false);
            msg = System.Text.Encoding.ASCII.GetString(buff);

            if (buffRecived > 0)
                ServerView.UpdateChatBox(msg);
        }
    }
}

所以如果我不使用 windows 窗体但命令行版本一切似乎都很好,多个客户端可以连接到套接字并发送消息。

但如果我使用的是 Windows 窗体版本(包含的代码),它会收到跨线程异常。我认为问题是

var client = await Server.AcceptTcpClientAsync().ConfigureAwait(false);

int buffRecived = await streamer.ReadAsync(buff ,0 , buff.Length).ConfigureAwait(false);

因为如果我删除 ConfigureAwait(false),看起来很好,但是....程序不能正常工作,因为如果客户端连接并发送消息,程序会等待另一个连接,并且不接收消息从连接的客户端。任何人都知道如何更改它以使其正常工作?

Form1.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ChatServer
{

    public interface IServerView
    {
        void UpdateChatBox(string message);
        void UpdateClientBox(string message);
    };
    public partial class ServerView : Form, IServerView
    {

        private IServerPresenter _presenter;
        public ServerView(IServerPresenter presenter)
        {
            _presenter = presenter;

            InitializeComponent();
        }


        public void UpdateChatBox(string message)
        {
            chatListBox.Items.Add(message);
        }

        public void UpdateClientBox(string message)
        {
            clientListBox.Items.Add(message);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            _presenter.StartServer(textBox1.Text, Convert.ToInt32(numericUpDown1.Value));
        }
    }
}

【问题讨论】:

  • 最简单的更改可能是在访问视图中的 UI 控件之前使用 IsInvokeRequired。见Make Thread-Safe Calls to Windows Forms Controls。请注意,您不必定义新的委托类型,只需使用内置的 Action<string> 类型即可。
  • @pinkfloydx33:ListenForClientsAsync() 同步运行是不正确的。任何带有await 语句的async 方法将在到达await 语句时返回。该方法的其余部分将异步执行。
  • 你说得对……我错过了,因为我陷入了没有等待的方法本身
  • @mikez:我不同意有任何需要使用IsInvokeRequired。即使在遗留代码中,这也不是真正必要的(只是无条件地调用),但现代 C# 习惯用法完全取消了显式调用,因此当然不需要检查该属性。使用async/await 允许编译器和运行时处理所有实际工作。
  • @PeterDuniho 当然不是完全有必要,它实际上只是为您节省了一些额外的分配,用于在您已经在 UI 线程上的场景中执行调用所需的部分。它也可以通过Progress<T> 来完成,它会自动编组回 UI 线程(假设它是在那里创建的)。

标签: c# winforms sockets async-await


【解决方案1】:

如果我删除ConfigureAwait(false),看起来还不错,但是....程序无法正常运行

不过,这是解决问题的第一步。通过调用ConfigureAwait(false),您是在告诉框架在继续执行异步操作时不要费心回到 UI 线程。因此,当您尝试与 UI 对象交互时,您是在错误的线程上。因此例外。

下一步是修复您的接收代码。代码中没有任何内容可以表明您似乎没有收到数据的原因。那部分应该工作。但是您并没有等待接收操作,这解释了为什么您的代码继续等待新连接。如果你不想这样,你的代码应该看起来更像这样:

private async void ListenForClientsAsync()
{

    try
    {
        while (true)
        {
            var client = await Server.AcceptTcpClientAsync();
            if (client.Connected)
            {
                ServerView.UpdateChatBox("Client connected.");
                await ReadStreamAsync(client);
            }
        }
    }
    catch (Exception e)
    {
        ServerView.UpdateChatBox(e.Message);
    }
}

private async Task ReadStreamAsync(TcpClient client)
{
    NetworkStream streamer = client.GetStream();

    Byte[] buff = new Byte[1024];
    String msg;
    int buffRecived;

    // receive data until connection is closed, i.e. receive completes
    // with 0-byte length.
    while ((buffRecived = await streamer.ReadAsync(buff, 0, buff.Length)) > 0)
    {
        msg = System.Text.Encoding.ASCII.GetString(buff);
        ServerView.UpdateChatBox(msg);
    }
}

注意事项:

  • 我还修改了您的ReadStreamAsync() 方法,使其能够读取所有发送的数据,直到连接关闭。
  • 如果您正在阅读文本,使用换行符分隔文本并使用StreamReader 逐行阅读文本可能更有意义。

【讨论】:

  • 交叉异常消失,但应用程序仅适用于单个客户端连接。如果我尝试连接另一个客户端,则不会发生任何事情。服务器应用看不到下一个连接的客户端,而只能看到第一个。
  • "应用程序仅适用于单个客户端连接" -- 我读到你的问题是说这就是你想要的。如果您希望能够处理多个客户端,只需从对 ReadStreamAsync() 的调用中删除 await。然后服务器将能够一次接受多个客户端。
猜你喜欢
  • 2013-03-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-11
  • 2017-08-26
  • 1970-01-01
  • 1970-01-01
  • 2020-10-10
相关资源
最近更新 更多