bielu000
bielu000

Reputation: 2241

Socket Programming C# Chat server, Cross thread in WinForms

I try to create simple chat server, which handle multiple clients, and recives messages from all of them. Below is my code.

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

So If I don't use windows form but Commandline Version everything seems to be fine, multiple clients can connect to socket and send messages.

But If I'm using Windows Forms version (included code) it recives cross thread exception. I think the problem is

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

and

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

becouse if I remove ConfigureAwait(false), looks to be fine but....program doesn't work correctly, becouse If client connect and send a message, the program waits for another connection, and don't recive messages from connected client. Anyone have idea how I can change it to work correctly?

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

Upvotes: 0

Views: 1209

Answers (1)

Peter Duniho
Peter Duniho

Reputation: 70652

if I remove ConfigureAwait(false), looks to be fine but....program doesn't work correctly

Nevertheless, that is the first step in fixing your problem. By calling ConfigureAwait(false), you are telling the framework to not bother moving back to the UI thread when executing the continuation of your asynchronous operation. Hence you are on the wrong thread when you try to interact with UI objects. Hence the exception.

The next step would be to fix your receiving code. There's nothing in the code that would suggest why you don't seem to receive data. That part should work. But you aren't awaiting the receive operation, which explains why your code goes on to also wait for a new connection. If you don't want that, your code should probably look more like this:

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

Notes:

  • I also modified your ReadStreamAsync() method so that it will read all the data sent, until the connection is closed.
  • If you are reading text, it might make more sense to use line-break delimited text and use StreamReader to read the text line-by-line.

Upvotes: 1

Related Questions