Search code examples
c#formsservermessage

Form1 does not start


So I'm developing an Server-Client app, and yesterday I solved a problem with the messages.

(Server side not reading message correctly)

Today, I'm facing a new problem. My objective is to click in a website button by his ID, using messages that come from the Client. So I created a mini WebBrowser that displays the Website I want to use. The problem is that if I start the server with the mini WebBrowser, the browser itself wont show up, but if I remove

serverfunction();

it will show up but the server wont be open, i will gladly accept any help. TIA

Source code C#

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

namespace WindowsFormsApplication2
{

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            serverfunction();
        }

        private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {

        }

        public void serverfunction()
        {
            int port = 80;
            IPAddress localAddr = IPAddress.Parse("192.168.1.68");
            TcpListener server = new TcpListener(localAddr, port);

            server.Start();

            byte[] bytes = new byte[2048];
            string data;

            while (true)
            {
                TcpClient client = server.AcceptTcpClient();
                NetworkStream stream = client.GetStream();
                int i;
                i = stream.Read(bytes, 0, bytes.Length);
                data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
                Global.message = StripExtended(data);
                Console.WriteLine(Global.message);
            }
        }

        static string StripExtended(string arg)
        {
            StringBuilder buffer = new StringBuilder(arg.Length); //Max length
            foreach (char ch in arg)
            {
                UInt16 num = Convert.ToUInt16(ch);//In .NET, chars are UTF-16
                //The basic characters have the same code points as ASCII, and the extended characters are bigger
                if ((num >= 32u) && (num <= 126u)) buffer.Append(ch);
            }
            return buffer.ToString();
        }
    }

    public class Global
    {
        public static string message = "";
    } 
}

Solution

  • The issue is that your ServerFunction call creates an infinite loop

    while (true)
    {
        // ...
    }
    

    You can solve that easily by pushing it to a background thread.

    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    
    namespace WindowsFormsApplication2
    {
    
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
                Task.Run(() => serverfunction());
            }
    
            private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
            {
    
            }
    
            public void serverfunction()
            {
                int port = 80;
                IPAddress localAddr = IPAddress.Parse("192.168.1.68");
                TcpListener server = new TcpListener(localAddr, port);
    
                server.Start();
    
                byte[] bytes = new byte[2048];
                string data;
    
                while (true)
                {
                    TcpClient client = server.AcceptTcpClient();
                    NetworkStream stream = client.GetStream();
                    int i;
                    i = stream.Read(bytes, 0, bytes.Length);
                    data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
                    Global.message = StripExtended(data);
                    Console.WriteLine(Global.message);
                }
            }
    
            static string StripExtended(string arg)
            {
                StringBuilder buffer = new StringBuilder(arg.Length); //Max length
                foreach (char ch in arg)
                {
                    UInt16 num = Convert.ToUInt16(ch);//In .NET, chars are UTF-16
                    //The basic characters have the same code points as ASCII, and the extended characters are bigger
                    if ((num >= 32u) && (num <= 126u)) buffer.Append(ch);
                }
                return buffer.ToString();
            }
        }
    
        public class Global
        {
            public static string message = "";
        }
    }
    

    You'll want to make sure you marshall any UI interaction back to the UI thread.

    However I don't believe that to be the best solution. You should avoid a while loop all together. Instead, as described by my answer in another post, you should use the Begin/EndInvoke pattern with the Socket class. Also, I typically frown on "global" variables like what you have.

    You could create a client connection like the following:

    ServerMessageArgs

    We need an EventArgs class that will be used to push messages received to the Form.

    public class ServerMessageArgs : EventArgs
    {
        public ServerMessageArgs(string message)
        {
            this.ServerMessage = message;
        }
    
        public string ServerMessage { get; private set; }
    }
    

    ClientConnection

    This class handles connecting to a server, and processing any message received. When data is received, it raises the event handler and passes it along to the Form.

    public class ClientConnection : IDisposable
    {
    
        private const int _bufferSize = 1024;
        private Socket socket = null;
    
        public event EventHandler Connected;
        public event EventHandler<ServerMessageArgs> ServerMessageArrived;
    
        public bool IsConnected { get; private set; }
    
        public void ConnectToServerAsync(string url, int port)
        {
            var endPoint = this.GetIPEndPointFromHostName(url, port, false);
            this.socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
    
            this.socket.BeginConnect(endPoint, new AsyncCallback(this.ConnectedCallback), this.socket);
    
            this.IsConnected = true;
        }
    
        public void Dispose()
        {
            if (this.socket.Connected)
            {
                this.socket.Disconnect(false);
            }
    
            this.socket.Dispose();
        }
    
        public void SendMessage(string message)
        {
            // Strip the ending carriage return and linefeed characters and re-add them
            // We do this in the event that only one out of the two were provided, or they 
            // came in out of order.
            message = $"{message.TrimEnd('\r', '\n')}\r\n";
    
            // Convert the string data to byte data using ASCII encoding.
            byte[] byteData = Encoding.UTF8.GetBytes(message);
    
            // Begin sending the data to the remote device.
            this.socket.BeginSend(byteData, 0, byteData.Length, 0,
                new AsyncCallback(SendCallback), this.socket);
        }
    
        private void SendCallback(IAsyncResult ar)
        {
            try
            {
                // Complete sending the data to the remote device.
                int bytesSent = this.socket.EndSend(ar);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }
    
        private void ConnectedCallback(IAsyncResult result)
        {
            this.socket.EndConnect(result);
            var handler = this.Connected;
            if (handler == null)
            {
                return;
            }
    
            handler(this, new EventArgs());
    
            this.ReceiveData();
        }
    
        private void ReceiveData()
        {
            var buffer = new byte[_bufferSize];
    
            this.socket.BeginReceive(
                buffer,
                0,
                _bufferSize,
                0,
                new AsyncCallback(ReceiveCallback),
                buffer);
        }
    
        private void ReceiveCallback(IAsyncResult result)
        {
            if (!this.IsConnected)
            {
                return;
            }
    
            byte[] buffer = (byte[])result.AsyncState;
            int bytesRead = this.socket.EndReceive(result);
            var contentBuilder = new StringBuilder();
            string content = string.Empty;
    
            if (bytesRead > 0)
            {
                // There might be more data, so store the data received so far.
                contentBuilder.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
    
                foreach (string message in contentBuilder.ToString().Split('\n'))
                {
                    this.OnServerMessageArrived(message);
                }
    
                //  Get the rest of the data.
                this.socket.BeginReceive(
                    (buffer = new byte[_bufferSize]),
                    0,
                    _bufferSize,
                    0,
                    new AsyncCallback(ReceiveCallback),
                    buffer);
            }
        }
    
        private void OnServerMessageArrived(string content)
        {
            var handler = this.ServerMessageArrived;
            if (handler == null)
            {
                return;
            }
    
            handler(this, new ServerMessageArgs(content));
        }
    
        private IPEndPoint GetIPEndPointFromHostName(string hostName, int port, bool throwIfMoreThanOneIP)
        {
            var addresses = Dns.GetHostAddresses(hostName);
            if (addresses.Length == 0)
            {
                throw new ArgumentException(
                    "Unable to retrieve address from specified host name.",
                    "hostName"
                );
            }
            else if (throwIfMoreThanOneIP && addresses.Length > 1)
            {
                throw new ArgumentException(
                    "There is more that one IP address to the specified host.",
                    "hostName"
                );
            }
            return new IPEndPoint(addresses[0], port); // Port gets validated here.
        }
    }
    

    Form1

    Now the Form just looks like:

    public partial class Form1 : Form
    {
        private ClientConnection client;
    
        public Form1()
        {
            InitializeComponent();
            client = new ClientConnection();
        }
    
        private void Form1_Load(object sender, EventArgs e)
        {
            client.ServerMessageArrived += (s, message) =>
            {
                Console.WriteLine(message);
                client.SendMessage($"{message} received by server.");
            };
    
            client.Connected += (s, args) => Console.WriteLine("Connected.");
            client.ConnectToServerAsync("192.168.1.68", 80);
        }
    }
    

    I was not able to test the above, due to not knowing what data is expected back, but this code was lifted from an IRC app that I wrote, using the async pattern and pruning the app of while loops.

    Edit

    After posting the client connection code above, I realized that your question was regarding the server hanging. I would still remove the while loop. You can use the async pattern I mentioned in the link above, which was for server-side. In order to ensure the answer is part of this post, I will share the code here as well.

    Server Status

    We need to have a way of checking what the current server status is. This can be as simple as a small enum.

    public enum ServerStatus
    {
        /// <summary>
        /// The server has stopped.
        /// </summary>
        Stopped,
    
        /// <summary>
        /// Server is in the process of starting.
        /// </summary>
        Starting,
    
        /// <summary>
        /// Server is up and running.
        /// </summary>
        Running
    }
    

    ConnectedArgs

    Next, the Server class needs to raise some events so that the Form code-behind can be told when a client connects, and when the client has sent the server a message. We will provide a class called ConnectedArgs as our event handler argument. This class will hold a reference to our actual client, using the wrapper class we will create next.

    public class ConnectedArgs : EventArgs
    {
        public ConnectedArgs(ConnectionState state)
        {
            this.ConnectedClient = state;
        }
    
        public ConnectionState ConnectedClient { get; private set; }
    }
    

    ConnectionState

    This class is responsible for holding the Socket associated with the connected client, and handle receiving the clients message data asynchronously. This uses the BeginInvoke and EndInvoke async pattern included with the Socket.

    This class will have an event that will be used to notify the Form that a new message was received. Note that this was pulled from one of my existing projects, so the data parsing basically checks the buffer and if the buffer does not include a \r\n, it is considered incomplete. It caches it and waits for the next chunk of data from the client to process and try and complete. You will want to replace the ProcessReceivedData method with your custom method that handles processing the received data. When you are done, just push the results in to the OnDataReceived method so your Form can be given it.

    public sealed class ConnectionState
    {
        /// <summary>
        /// The size of the buffer that will hold data sent from the client
        /// </summary>
        private readonly int bufferSize;
    
        /// <summary>
        /// A temporary collection of incomplete messages sent from the client. These must be put together and processed.
        /// </summary>
        private readonly List<string> currentData = new List<string>();
    
        /// <summary>
        /// What the last chunk of data sent from the client contained.
        /// </summary>
        private string lastChunk = string.Empty;
    
        public ConnectionState(Socket currentSocket, int bufferSize)
        {
            this.CurrentSocket = currentSocket;
            this.bufferSize = bufferSize;
            this.Buffer = new byte[bufferSize];
        }
    
        /// <summary>
        /// This event is raised when the server has received new, valid, data from the client.
        /// </summary>
        public event EventHandler<string> DataReceived;
    
        /// <summary>
        /// Gets the Socket for the player associated with this state.
        /// </summary>
        public Socket CurrentSocket { get; private set; }
    
        /// <summary>
        /// Gets the data currently in the network buffer
        /// </summary>
        public byte[] Buffer { get; private set; }
    
        /// <summary>
        /// Gets if the current network connection is in a valid state.
        /// </summary>
        public bool IsConnectionValid
        {
            get
            {
                return this.CurrentSocket != null && this.CurrentSocket.Connected;
            }
        }
    
        /// <summary>
        /// Starts listening for network communication sent from the client to the server
        /// </summary>
        public void StartListeningForData()
        {
            this.Buffer = new byte[bufferSize];
            this.CurrentSocket.BeginReceive(this.Buffer, 0, bufferSize, 0, new AsyncCallback(this.ReceiveData), null);
        }
    
        /// <summary>
        /// Receives the input data from the user.
        /// </summary>
        /// <param name="result">The result.</param>
        private void ReceiveData(IAsyncResult result)
        {
            // If we are no longer in a valid state, dispose of the connection.
            if (!this.IsConnectionValid)
            {
                this.CurrentSocket?.Dispose();
                return;
            }
    
            int bytesRead = this.CurrentSocket.EndReceive(result);
            if (bytesRead == 0 || !this.Buffer.Any())
            {
                this.StartListeningForData();
                return;
            }
    
            ProcessReceivedData(bytesRead);
            this.StartListeningForData();
        }
    
        /// <summary>
        /// Process the data we received from the client.
        /// </summary>
        /// <param name="bytesRead"></param>
        private void ProcessReceivedData(int bytesRead)
        {
            // Encode our input string sent from the client
            this.lastChunk = Encoding.ASCII.GetString(this.Buffer, 0, bytesRead);
    
            // If the previous chunk did not have a new line feed, then we add this message to the collection of currentData.
            // This lets us build a full message before processing it.
            if (!lastChunk.Contains("\r\n"))
            {
                // Add this to our incomplete data stash and read again.
                this.currentData.Add(lastChunk);
                return;
            }
    
            // This message contained at least 1 new line, so we split it and process per line.
            List<string> messages = lastChunk.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries).ToList();
    
            foreach (string line in this.PruneReceivedMessages(messages))
            {
                this.OnDataReceived(line);
            }
        }
    
        /// <summary>
        /// Runs through the messages collection and prepends data from a previous, incomplete, message
        /// and updates the internal message tracking state.
        /// </summary>
        /// <param name="messages"></param>
        private List<string> PruneReceivedMessages(List<string> messages)
        {
            // Append the first line to the incomplete line given to us during the last pass if one exists.
            if (this.currentData.Any() && messages.Any())
            {
                messages[0] = string.Format("{0} {1}", string.Join(" ", this.currentData), messages[0]);
                this.currentData.Clear();
            }
    
            // If we have more than 1 line and the last line in the collection does not end with a line feed
            // then we add it to our current data so it may be completed during the next pass. 
            // We then remove it from the lines collection because it can be infered that the remainder will have
            // a new line due to being split on \n.
            if (messages.Count > 1 && !messages.Last().EndsWith("\r\n"))
            {
                this.currentData.Add(messages.Last());
                messages.Remove(messages.Last());
            }
    
            return messages;
        }
    
        private void OnDataReceived(string data)
        {
            var handler = this.DataReceived;
            if (handler == null)
            {
                return;
            }
    
            handler(this, data);
        }
    }
    

    Server

    Now that we have the client async receiving and processing of data, we need to write the server component to actually accept incoming Socket connections asynchronously.

    This class will have two events that the Form will subscribe to. One for when a client connects, and another for when the client disconnects. Each client that is connected, is assigned a ConnectionState and cached in a collection of List<ConnectionState>. This removes the need for you to keep a fragile counter of the number of clients connected.

    You could optionally wire up a timer that periodically prunes the List<ConnectionState> collection. You can check if each instance in the collection has its IsConnectionValid set to true. If it returns false, remove it from the collection.

    /// <summary>
    /// The Default Desktop game Server
    /// </summary>
    public sealed class Server
    {
        /// <summary>
        /// The user connection buffer size
        /// </summary>
        private const int UserConnectionBufferSize = 1024;
    
        /// <summary>
        /// The server socket
        /// </summary>
        private Socket serverSocket;
    
        /// <summary>
        /// The player connections
        /// </summary>
        private List<ConnectionState> connectedClients;
    
        /// <summary>
        /// Used for making access to the connectedClients collection thread-safe
        /// </summary>
        private object lockObject = new object();
    
        /// <summary>
        /// Initializes a new instance of the <see cref="Server"/> class.
        /// </summary>
        public Server()
        {
            this.Status = ServerStatus.Stopped;
            this.connectedClients = new List<ConnectionState>();
        }
    
        /// <summary>
        /// Occurs when a client connects to the server.
        /// </summary>
        public event EventHandler<ConnectedArgs> ClientConnected;
    
        /// <summary>
        /// Occurs when a client is disconnected from the server.
        /// </summary>
        public event EventHandler<ConnectedArgs> ClientDisconnected;
    
        /// <summary>
        /// Gets or sets the port that the server is running on.
        /// </summary>
        public int Port { get; set; }
    
        /// <summary>
        /// Gets or sets the maximum queued connections.
        /// </summary>
        public int MaxQueuedConnections { get; set; }
    
        /// <summary>
        /// Gets the current server status.
        /// </summary>
        public ServerStatus Status { get; private set; }
    
        public void Start()
        {
            if (this.Status != ServerStatus.Stopped)
            {
                throw new InvalidOperationException("The server is either starting or already running. You must stop the server before starting it again.");
            }
            else if (this.Port == 0)
            {
                throw new InvalidOperationException("You can not start the server on Port 0.");
            }
    
            this.Status = ServerStatus.Starting;
    
            // Get our server address information
            IPHostEntry serverHost = Dns.GetHostEntry(Dns.GetHostName());
            var serverEndPoint = new IPEndPoint(IPAddress.Any, this.Port);
    
            // Instance the server socket, bind it to a port.
            this.serverSocket = new Socket(serverEndPoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            this.serverSocket.Bind(serverEndPoint);
            this.serverSocket.Listen(this.MaxQueuedConnections);
    
            // Begin listening for connections.
            this.serverSocket.BeginAccept(new AsyncCallback(this.ConnectClient), this.serverSocket);
    
            this.Status = ServerStatus.Running;
        }
    
        /// <summary>
        /// Stops the server.
        /// </summary>
        public void Stop()
        {
            this.DisconnectAll();
    
            // We test to ensure the server socket is still connected and active.
            this.serverSocket.Blocking = false;
            try
            {
                this.serverSocket.Send(new byte[1], 0, 0);
    
                // Message was received meaning it's still receiving, so we can safely shut it down.
                this.serverSocket.Shutdown(SocketShutdown.Both);
            }
            catch (SocketException e)
            {
                // Error code 10035 indicates it works, but will block the socket.
                // This means it is still receiving and we can safely shut it down.
                // Otherwise, it's not receiving anything and we don't need to shut down.
                if (e.NativeErrorCode.Equals(10035))
                {
                    this.serverSocket.Shutdown(SocketShutdown.Both);
                }
            }
            finally
            {
                this.Status = ServerStatus.Stopped;
            }
        }
    
        /// <summary>
        /// Disconnects the specified IServerPlayer object.
        /// </summary>
        /// <param name="connection">The client to disconnect.</param>
        public void Disconnect(ConnectionState connection)
        {
            if (connection != null && connection.IsConnectionValid)
            {
                connection.CurrentSocket.Shutdown(SocketShutdown.Both);
                this.connectedClients.Remove(connection);
                this.OnClientDisconnected(connection);
            }
        }
    
        /// <summary>
        /// Disconnects everyone from the server.
        /// </summary>
        public void DisconnectAll()
        {
            // Loop through each connection and disconnect them.
            foreach (ConnectionState state in this.connectedClients)
            {
                Socket connection = state.CurrentSocket;
                if (connection != null && connection.Connected)
                {
                    connection.Shutdown(SocketShutdown.Both);
                    this.OnClientDisconnected(state);
                }
            }
    
            this.connectedClients.Clear();
        }
    
        /// <summary>
        /// Called when a client connects.
        /// </summary>
        private void OnClientConnected(ConnectionState connection)
        {
            EventHandler<ConnectedArgs> handler = this.ClientConnected;
            if (handler == null)
            {
                return;
            }
    
            handler(this, new ConnectedArgs(connection));
        }
    
        /// <summary>
        /// Called when a client disconnects.
        /// </summary>
        private void OnClientDisconnected(ConnectionState connection)
        {
            EventHandler<ConnectedArgs> handler = this.ClientDisconnected;
            if (handler == null)
            {
                return;
            }
    
            handler(this, new ConnectedArgs(connection));
        }
    
        /// <summary>
        /// Connects the client to the server and then passes the connection responsibilities to the client object.
        /// </summary>
        /// <param name="result">The async result.</param>
        private void ConnectClient(IAsyncResult result)
        {
            // Connect and register for network related events.
            Socket connection = this.serverSocket.EndAccept(result);
    
            // Send our greeting
            byte[] buffer = Encoding.ASCII.GetBytes("Welcome to the Music App Server!");
            connection.BeginSend(buffer, 0, buffer.Length, 0, new AsyncCallback(asyncResult => connection.EndReceive(asyncResult)), null);
    
            // Fetch the next incoming connection.
            this.serverSocket.BeginAccept(new AsyncCallback(this.ConnectClient), this.serverSocket);
    
            this.CompleteClientSetup(new ConnectionState(connection, UserConnectionBufferSize));
        }
    
        /// <summary>
        /// Caches the ConnectionState and has the state begin listening to client data.
        /// </summary>
        /// <param name="connectionState"></param>
        private void CompleteClientSetup(ConnectionState connectionState)
        {
            lock (this.lockObject)
            {
                this.connectedClients.Add(connectionState);
            }
    
            // Start receiving data from the client.
            connectionState.StartListeningForData();
            this.OnClientConnected(connectionState);
        }
    }
    

    Form

    Now that we have our server code put together, the Form can be constructed. I just did a simple form, with a single button for connecting and a single multi-line textbox for viewing the data.

    Form Layout

    I added an event handler for the Form's Loading event and for the Button's Clicked event.

    public partial class Form1 : Form
    {
        private Server server;
    
        public Form1()
        {
            InitializeComponent();
            server = new Server { Port = 9180, MaxQueuedConnections = 100 };
        }
    
        private void Form1_Load(object sender, EventArgs e)
        {
            server.ClientConnected += OnClientConnected;
            server.ClientDisconnected += OnClientDisconnected;
        }
    
        private void OnClientDisconnected(object sender, ConnectedArgs e)
        {
            this.Invoke(new Action(() => this.textBox1.AppendText("A Client disconnected.\n")));
        }
    
        private void OnClientConnected(object sender, ConnectedArgs e)
        {
            this.Invoke(new Action(() => 
            {
                this.textBox1.AppendText("New Client Connected.\n");
                e.ConnectedClient.DataReceived += OnClientSentDataToServer;
            }));
        }
    
        private void OnClientSentDataToServer(object sender, string e)
        {
            this.Invoke(new Action(() => this.textBox1.AppendText($"{e}\n")));
        }
    
        private void button1_Click(object sender, EventArgs e)
        {
            this.textBox1.AppendText("Server starting.\n");
            server.Start();
            this.textBox1.AppendText("Server running.\n");
        }
    }
    

    This gives you a nice, clean, way to handle the server-side. Anytime you can avoid a while loop in your server, the better off you are. The actually code is a lot larger than what you posted, but will run much better on your server during runtime.