StreamWriter not sending/receiving from NetworkStream in multi-thread telnet server

Întrebare StreamWriter not sending/receiving from NetworkStream in multi-thread telnet server

  • Tuesday, February 28, 2012 1:51 AM
     
      Has Code

    Greetings,

    Sorry for the long Title, but I wasn't to sure how to properly sum up the issue that I am currently facing.

    At the moment I have a working Telnet server.  It listens for incoming connections and for each new connection, it passes the Socket off to a Connection Manager class that manages all of the servers current connections.  The Connection Manager class instances a new Character class and provides it with a reference to the Socket so that it may communicate with the server.  I'm using a StreamReader and StreamWriter attached to the NetworkStream to send and receive user input.  This doesn't seem to be working properly with how I have it implemented.

    When a user types the phrase "Say hello" I am removing "Say " and then looping through all of the current connections in the Connection Manager (which is static) and sending the content of the messages to each connected client.  However on all of my connected telnet clients, the message never gets sent or received.  I'm not sure if this is a multi-thread issue or if I'm not using the streams correctly.

    Any help would be greatly appreciated!  I believe I've included all of the code you'd need to compile the code.  The server is started with a new Instance of the Server class and then invoking the Server.Start() method.

    Server Class

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.IO;
    using System.Net;
    using System.Net.Sockets;
    using System.Threading;
    
    using MudEngine.Core;
    using MudEngine.Core.Interfaces;
    using MudEngine.Game;
    using MudEngine.Game.Characters;
    
    namespace MudEngine.Networking
    {
        [Category("Networking")]
        public class Server
        {
            [Category("Networking")]
            [Description("The name of this server")]
            public String Name { get; set; }
    
            [Category("Networking")]
            public Int32 Port { get; set; }
    
            [Category("Networking")]
            [Description("The Message Of The Day that is presented to users when they connect.")]
            public String MOTD { get; set; }
    
            [Category("Networking")]
            [Description("Maximum number of people that can connect and play on this server at any time.")]
            public Int32 MaxConnections { get; set; }
    
            [Category("Networking")]
            [Description("Maximum number of poeple that can be queued for connection.")]
            public Int32 QueuedConnectionLimit { get; set; }
    
            [Browsable(false)]
            public Boolean Enabled { get; private set; }
    
            /// <summary>
            /// Sets up a server on the specified port.
            /// </summary>
            /// <param name="port"></param>
            /// <param name="maxConnections"></param>
            public Server(Int32 port)
            {
                Logger.WriteLine("Initializing Mud Server Settings");
                this.Port = port;
                this.MaxConnections = 50;
                this.QueuedConnectionLimit = 20;
                this.Name = "New Server";
    
                this._Server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                this._Server.Bind(new IPEndPoint(IPAddress.Any, this.Port));
            }
    
            /// <summary>
            /// Starts the game server.  It will listen on a different thread of incoming connections
            /// </summary>
            public void Start(StandardGame game)
            {
                //Start the server.
                Logger.WriteLine("Starting Mud Server.");
    
                //Start listening for connections
                this._Server.Listen(this.QueuedConnectionLimit);
    
                //Launch the new connections listener on a new thread.
                this._ConnectionPool = new Thread(AcceptConnection);
                this._ConnectionPool.Start();
    
                //Flag the server as enabled.
                this.Enabled = true;
    
                //Save a reference to the currently active game
                this._Game = game;
    
                Logger.WriteLine("Server started.");
            }
    
            public void Stop()
            {
                //Flag the server as disabled.
                this.Enabled = false;
    
                //Stop the thread from listening for accepting new conections
                this._ConnectionPool.Abort();
    
                //Disconnect all of the currently connected clients.
                ConnectionManager.DisconnectAll();
    
                //Stop the server.
                this._Server.Close();
            }
    
            private void AcceptConnection()
            {
                //While the server is enabled, constantly check for new connections.
                while (this.Enabled)
                {
                    //Grab the new connection.
                    Socket incomingConnection = this._Server.Accept();
                    
                    //Send it to the Connection Manager so a Character can be instanced.
                    ConnectionManager.AddConnection(this._Game, incomingConnection);
                }
            }
    
            private Socket _Server;
            private Thread _ConnectionPool;
            private StandardGame _Game;
        }
    }
    
    Connection Manager class
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.IO;
    using System.Net;
    using System.Net.Sockets;
    using System.Threading;
    
    using MudEngine.Game;
    using MudEngine.Game.Characters;
    
    namespace MudEngine.Networking
    {
        public static class ConnectionManager
        {
            //Collection of currently connected players.
            public static List<StandardCharacter> Connections;
    
            /// <summary>
            /// Creates a new character for the player and sets it up on the server.
            /// </summary>
            /// <param name="game"></param>
            /// <param name="connection"></param>
            public static void AddConnection(StandardGame game, Socket connection)
            {
                //Exception checking.
                if (Connections == null)
                    Connections = new List<StandardCharacter>();
    
                //Instance a new character and provide it with the Socket.
                StandardCharacter character = new StandardCharacter(game, connection);
                
                //Add it to the Connections collection
                Connections.Add(character);
    
                //Invoke the Characters Server connection method
                character.Connect();
            }
    
            /// <summary>
            /// Removes the specified player character from the server.
            /// </summary>
            /// <param name="character"></param>
            public static void RemoveConnection(StandardCharacter character)
            {
                Connections.Remove(character);
            }
    
            /// <summary>
            /// Disconnects all of the currently connected clients.
            /// </summary>
            public static void DisconnectAll()
            {
                if (Connections == null)
                    return;
    
                foreach (StandardCharacter character in Connections)
                {
                    character.Disconnect();
                }
                Connections.Clear();
            }
        }
    }
    
    Character class (Connected client)
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.IO;
    using System.Text;
    using System.Net;
    using System.Net.Sockets;
    using System.Threading;
    
    using MudEngine.GameScripts;
    using MudEngine.Core.Interfaces;
    using MudEngine.Networking;
    
    namespace MudEngine.Game.Characters
    {
        public class StandardCharacter : INetworked
        {
            public StandardGame Game { get; private set; }
    
            public CharacterRoles Role { get; protected set; }
    
            public CharacterStats Stats { get; protected set; }
    
            public String Password { get; private set; }
    
            public Boolean Immovable { get; set; }
    
            //TODO: Add current location to characters
            //public IEnvironment CurrentLocation
    
            public StandardCharacter(StandardGame game) : base()
            {
                this.Game = game;
    
                this.OnConnectEvent += new OnConnectHandler(OnConnect);
            }
    
            public StandardCharacter(StandardGame game, Socket connection) : this(game)
            {
                this._Connection = connection;
    
                this._Reader = new StreamReader(new NetworkStream(this._Connection, false));
                this._Writer = new StreamWriter(new NetworkStream(this._Connection, true));
            }
    
            internal void ExecuteCommand(string message)
            {
                //Process commands here.
                if (message.ToLower().StartsWith("say"))
                {
                    foreach (StandardCharacter character in ConnectionManager.Connections)
                    {
                        character.SendMessage(message.Substring("Say ".Length));
                        _Writer.Flush();
                    }
                }
            }
    
            public void SendMessage(string message)
            {
                _Writer.WriteLine(message);
                //_Writer.Flush();
            }
    
            public void Disconnect()
            {
                Console.WriteLine("Disconnecting...");
    
                //Close our currently open socket.
                this._Connection.Close();
    
                //Remove this character from the Connection Manager
                ConnectionManager.RemoveConnection(this);
                Console.WriteLine("Disconnect Complete.");
    
                //Stop the Update() thread.
                this._LoopThread.Abort();
            }
    
            public void Connect()
            {
                _LoopThread = new Thread(Update);
                _LoopThread.Start();
    
                //_Writer.WriteLine("");
                //OnConnectEvent();
            }
    
            public void Update()
            {
                try
                {
                    while (this.Game.Enabled)
                    {
                        _Writer.Flush();
                        String line = _Reader.ReadLine();
                        ExecuteCommand(line);
                    }
                }
                catch
                {
                }
                finally
                {
                    this.Disconnect();
                }
            }
    
            String CleanString(string line)
            {
                if ((!String.IsNullOrEmpty(line)) && (line.Length > 0))
                {
                    System.Text.StringBuilder sb = new System.Text.StringBuilder(line.Length);
                    foreach (char c in line)
                    {
                        if (char.IsSymbol(c)) continue;
    
                        sb.Append(char.IsControl(c) ? ' ' : c);
                    }
                    //String newLine = sb.ToString().Trim().Substring(2);
                    return sb.ToString();
                }
                else
                    return String.Empty;
            }
    
            public delegate void OnConnectHandler();
            public event OnConnectHandler OnConnectEvent;
            public void OnConnect()
            {
                _Writer.WriteLine("Welcome to the sample server!");
            }
    
            private Socket _Connection;
            private Thread _LoopThread;
            private StreamReader _Reader;
            private StreamWriter _Writer;
        }
    }
    

    The Logger calls only send a Console.WriteLine() message to the console.  The CharacterStats and CharacterRole Properties in StandardCharacter are not used anywhere.

    Thanks in advance for any help!  I've been stuck with this bug for the last day now and can't get it figured out! 

    Johnathon


    Follow me on Twitter: http://twitter.com/scionwest Project Manager: Mud Designer http://MudDesigner.Codeplex.com

All Replies

  • Tuesday, February 28, 2012 3:26 AM
     
     

    I do not quite understand what to do with a Telnet server when you have a server class?

    Look at your byte array buffer too. What do you use to buffer your string?

    chanmm


    chanmm

  • Tuesday, February 28, 2012 3:51 AM
     
     

    Server class is the telnet server.  The Character class is the representation of the Telnet clients that are connected to it.  Maybe I'm approaching this the wrong way, but I've never done any network coding before so this is new ground lol.

    The byte array buffer I didn't think I needed one for the StreamWriter, do I?  StreamWriter works without any kind of buffer when I'm sending _Writer.WriteLine("Test"); within the Update() method in the dedicated thread.  It fails when I make that call outside of the threaded loop.


    Follow me on Twitter: http://twitter.com/scionwest Project Manager: Mud Designer http://MudDesigner.Codeplex.com

  • Tuesday, February 28, 2012 11:59 PM
     
     
    Are there other approaches you recommend doing for a telnet server?

    Follow me on Twitter: http://twitter.com/scionwest Project Manager: Mud Designer http://MudDesigner.Codeplex.com

  • Wednesday, February 29, 2012 1:41 AM
     
     

    I fixed part of this issue by setting StreamWriter.AutoFlush to true.  Now any message I send from one client, is shown on all clients.  However this only occurred after Client 1 has sent the message twice.  The first message sent is lost and never received. Any message after the 1st message sent is always shown on the clients.  I'm not sure what's happening with the first message, but on all clients, it never gets delivered.  Any subsequent message is shown however.

    Any tips?


    Follow me on Twitter: http://twitter.com/scionwest Project Manager: Mud Designer http://MudDesigner.Codeplex.com

  • Wednesday, February 29, 2012 1:50 AM
     
     
    I fixed the last issue that I had.

    Follow me on Twitter: http://twitter.com/scionwest Project Manager: Mud Designer http://MudDesigner.Codeplex.com

  • Friday, March 02, 2012 2:30 AM
    Moderator
     
     

    Hi Scionwest,

    How's it going? Do you have any updates about the previous issue?


    Bob Shen [MSFT]
    MSDN Community Support | Feedback to us

  • Saturday, March 03, 2012 2:02 AM
     
      Has Code

    Hi there,  No I'm still struggling to get a working multi-threaded telnet server running.  I've re-wrote the server code several times and just can't seem to get something that works the way I want it to.  The code above was replaced with new source that's spread across a couple different files.  

    The current issue that I am having, is when I disconnect from the server, my Disconnect code never gets called.  The next client that connects then invokes the Disconnect code when they enter their first command and then they are disconnected and have to re-connect.  It seems like the disconnect call is queued and then passed on to the next user.

    Server code (Starts with the Start() method)

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.IO;
    using System.Net;
    using System.Net.Sockets;
    using System.Threading;
    
    using MudEngine.Core;
    using MudEngine.Core.Interfaces;
    using MudEngine.Game;
    using MudEngine.Game.Characters;
    
    namespace MudEngine.Networking
    {
        public enum ServerStatus
        {
            Stopped = 0,
            Starting = 1,
            Running = 2,
        } 
    
        [Category("Networking")]
        public class Server
        {
            public ServerStatus Status { get; private set; }
    
            public Int32 Port { get; private set; }
    
            public Int32 MaxConnections { get; private set; }
    
            public Int32 MaxQueuedConnections { get; private set; }
    
            public ConnectionManager ConnectionManager { get; private set; }
    
            public Boolean Enabled { get; private set; }
    
            public String MOTD { get; set; }
    
            public Server(StandardGame game, Int32 port)
            {
                this.Port = port;
                this.Status = ServerStatus.Stopped;
                this.MaxConnections = 100;
                this.MaxQueuedConnections = 10;
    
                this._Game = game;
                this._Server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            }
    
            public void Start(Int32 maxConnections, Int32 maxQueueSize)
            {
                if (this.Status != ServerStatus.Stopped)
                    return;
    
                this.Status = ServerStatus.Starting;
    
                this.MaxConnections = maxConnections;
                this.ConnectionManager = new ConnectionManager(this.MaxConnections);
    
                try
                {
                    IPEndPoint ip = new IPEndPoint(IPAddress.Any, this.Port);
                    this._Server.Bind(ip);
                    this._Server.Listen(this.MaxQueuedConnections);
    
                    this.Status = ServerStatus.Running;
                    this.Enabled = true;
    
                    this._ServerThread = new Thread(ServerLoop);
                    this._ServerThread.Start();
                }
                catch
                {
                    this.Status = ServerStatus.Stopped;
                }
            }
    
            public void Stop()
            {
            }
    
            private void ServerLoop()
            {
                while (this.Status == ServerStatus.Running)
                {
                    this.ConnectionManager.AddConnection(this._Game, this._Server.Accept());
                }
            }
    
            private StandardGame _Game;
            private Socket _Server;
            private Thread _ServerThread;
        }
    }
    


    Connection Manager (used to manage all of the connections)

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.IO;
    using System.Net;
    using System.Net.Sockets;
    using System.Threading;
    
    using MudEngine.Game;
    using MudEngine.Game.Characters;
    
    namespace MudEngine.Networking
    {
        public class ConnectionManager
        {
            //Collection of currently connected players.
            internal List<Thread> _ConnectedThreads;
            internal List<StandardCharacter> _ConnectedCharacters;
    
            public ConnectionManager(Int32 maxPlayers)
            {
                this._ConnectedCharacters = new List<StandardCharacter>(maxPlayers);
                this._ConnectedThreads = new List<Thread>(maxPlayers);
            }
    
            /// <summary>
            /// Creates a new character for the player and sets it up on the server.
            /// </summary>
            /// <param name="game"></param>
            /// <param name="connection"></param>
            public void AddConnection(StandardGame game, Socket connection)
            {
                //Instance a new character and provide it with the Socket.
                StandardCharacter character = new StandardCharacter(game, "New Player", "New networked client.",  connection);
    
                //Invoke the Characters Server connection method
                character.Connect(connection);
                this._ConnectedCharacters.Add(character);
                this._ConnectedThreads.Add(new Thread(ReceiveDataThread));
    
                Int32 index = this._ConnectedThreads.Count - 1;
                this._ConnectedThreads[index].Start(index);
            }
    
            public StandardCharacter[] GetConnectedCharacters()
            {
                return this._ConnectedCharacters.ToArray();
            }
    
            private void ReceiveDataThread(Object index)
            {
                StandardCharacter character = this._ConnectedCharacters[(Int32)index];
                character.Initialize();
    
                while (character.Game.Server.Status == ServerStatus.Running &&
                    character.Enabled)
                {
                    try
                    {
                        character.ExecuteCommand(character.GetInput());
                    }
                    catch
                    {
                        RemoveConnection(character);
                    }
                }
            }
    
            /// <summary>
            /// Removes the specified player character from the server.
            /// </summary>
            /// <param name="character"></param>
            public void RemoveConnection(StandardCharacter character)
            {
                this._ConnectedCharacters.Remove(character);
                //Stop the Update() thread.
                character._LoopThread.Abort();
            }
    
            /// <summary>
            /// Disconnects all of the currently connected clients.
            /// </summary>
            public void DisconnectAll()
            {
                for (Int32 i = 0; i < this._ConnectedCharacters.Count; i++)
                {
                    this._ConnectedCharacters[i].Disconnect();
                    this._ConnectedThreads[i].Abort();
                }
    
                this._ConnectedCharacters.Clear();
                this._ConnectedThreads.Clear();
            }
        }
    }
    

    Standard Character (The connected clients Player class)

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.IO;
    using System.Text;
    using System.Net;
    using System.Net.Sockets;
    using System.Threading;
    using System.Xml.Linq;
    using System.Text.RegularExpressions;
    
    using MudEngine.GameScripts;
    using MudEngine.Core.Interfaces;
    using MudEngine.Networking;
    using MudEngine.Core;
    
    namespace MudEngine.Game.Characters
    {
        /// <summary>
        /// Standard Character class used by all character based objects
        /// </summary>
        public class StandardCharacter : BaseScript, INetworked, ISavable, IUpdatable, IGameComponent
        {
            /// <summary>
            /// Gets a reference to the currently active game.
            /// </summary>
            public StandardGame Game { get; private set; }
    
            public string Filename { get; set; }
    
            /// <summary>
            /// Gets what this Characters role on the server is.
            /// </summary>
            public CharacterRoles Role { get; protected set; }
    
            /// <summary>
            /// Gets what this characters stats are.
            /// </summary>
            public CharacterStats Stats { get; protected set; }
    
            //TODO: Should be Private/Protected?
            public String Password { get; set; }
    
            /// <summary>
            /// Flags this object as non-movable in the world.
            /// </summary>
            public Boolean Immovable { get; set; }
    
            public Boolean Enabled { get; set; }
    
            //TODO: Add current location to characters
            //public IEnvironment CurrentLocation
    
            protected CommandSystem Commands { get; private set; }
    
            public StandardCharacter(StandardGame game, String name, String description) : base(game, name, description)
            {
                this.Game = game;
    
                //Instance this Characters personal Command System with a copy of the command
                //collection already loaded and prepared by the Active Game.
                this.Commands = new CommandSystem(CommandSystem.Commands);
                
                this.OnConnectEvent += new OnConnectHandler(OnConnect);
            }
    
            public StandardCharacter(StandardGame game, String name, String description, Socket connection) : this(game, name, description)
            {
                this._Connection = connection;
    
                this._Reader = new StreamReader(new NetworkStream(this._Connection, false));
                this._Writer = new StreamWriter(new NetworkStream(this._Connection, true));
    
                this._Writer.AutoFlush = true; //Flushes the stream automatically.
                this._InitialMessage = true; //Strips Telnet client garbage text from initial message sent from client.
            }
    
            public override bool Save(String filename)
            {
                base.Save(filename, true);
    
                SaveData.AddSaveData("Immovable", Immovable.ToString());
                SaveData.AddSaveData("Password", Password);
    
                this.SaveData.Save(filename);
    
                return true;
            }
    
    
            public override void Load(string filename)
            {
                base.Load(filename);
            }
    
            public void Initialize()
            {
                //throw new NotImplementedException();
                this.Enabled = true;
            }
    
            public void Destroy()
            {
                throw new NotImplementedException();
            }
    
            internal void ExecuteCommand(string command)
            {
                Commands.Execute(command, this);
    
                SendMessage("");
                SendMessage("Command:", false);
            }
    
            public void SendMessage(String data)
            {
                this.SendMessage(data, true);
            }
    
            public void SendMessage(String data, Boolean newLine)
            {
                try
                {
                    System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
                    if (newLine)
                        data += "\n\r";
    
                        this._Connection.Send(encoding.GetBytes(data));
                }
                catch (Exception)
                {
                    Disconnect();
                }
            }
    
            public String GetInput()
            {
                List<byte> buffer = new List<byte>();
                while (true)
                {
                    try
                    {
                        byte[] buf = new byte[1];
                        Int32 recved = this._Connection.Receive(buf);
    
                        if (recved > 0)
                        {
                            if (buf[0] == '\n' && buffer.Count > 0)
                            {
                                if (buffer[buffer.Count - 1] == '\r')
                                    buffer.RemoveAt(buffer.Count - 1);
    
                                String str;
                                System.Text.UTF8Encoding enc = new System.Text.UTF8Encoding();
                                str = enc.GetString(buffer.ToArray());
                                return str;
                            }
                            else
                                buffer.Add(buf[0]);
                        }
                    }
                    catch (Exception e)
                    {
                        Disconnect();
                        return e.Message;
                    }
                }
            }
    
            public void Disconnect()
            {
                Console.WriteLine("Disconnecting...");
    
                //Close our currently open socket.
                this._Connection.Close();
    
                //Remove this character from the Connection Manager
                //ConnectionManager.RemoveConnection(this, );
                Console.WriteLine("Disconnect Complete.");
            }
    
            public void Connect(Socket connection)
            {
                this._Connection = connection;
    
                _LoopThread = new Thread(Update);
                _LoopThread.Start();
    
                OnConnectEvent();
            }
    
            public void Update()
            {
                try
                {
                    while (this.Game.Enabled)
                    {
                        _Writer.Flush();
                        //String line = CleanString(GetInput());
                        //Console.WriteLine(line);
                        //ExecuteCommand(line);
                    }
                }
                catch
                {
                }
                finally
                {
                    this.Disconnect();
                }
            }
    
            String CleanString(String line)
            {
                /*
                if ((!String.IsNullOrEmpty(line)) && (line.Length > 0))
                {
                    System.Text.StringBuilder sb = new System.Text.StringBuilder(line.Length);
                    foreach (char c in line)
                    {
                        if (char.IsSymbol(c)) continue;
    
                        sb.Append(char.IsControl(c) ? ' ' : c);
                    }
                    String newLine = sb.ToString().Trim().Substring(2).Trim();
                    return newLine;
                }
                else
                    return String.Empty;
                 * */
    
                Regex invalidChars = new Regex(
        @"(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\uFEFF\uFFFE\uFFFF]",
        RegexOptions.Compiled);
    
                if (String.IsNullOrEmpty(line))
                    return "";
                else
                    return invalidChars.Replace(line, "");
            }
    
            public delegate void OnConnectHandler();
            public event OnConnectHandler OnConnectEvent;
            public void OnConnect()
            {
                _Writer.WriteLine(this.Game.Server.MOTD);
            }
    
            public delegate void OnDisconnectHandler();
            public event OnDisconnectHandler OnDisconnectEvent;
            public void OnDisconnect()
            {
    
            }
    
            public delegate void OnLoginHandler();
            public event OnLoginHandler OnLoginEvent;
            public void OnLogin()
            {
    
            }
    
            private Socket _Connection;
            internal Thread _LoopThread;
            private StreamReader _Reader;
            private StreamWriter _Writer;
            private Boolean _InitialMessage;
        }
    }
    


    Follow me on Twitter: http://twitter.com/scionwest Project Manager: Mud Designer http://MudDesigner.Codeplex.com

  • Tuesday, March 06, 2012 9:22 AM