6
\$\begingroup\$

This is the first time I'm dealing with C# as I'm accustomed to Java sockets, so I do want your full review and anything you think I can accomplish better or optimize in the code will be very appreciated.

In these few classes what I have tried to accomplish is a login and registration server based on C# .Net working as a windows application communicating with a GUI built with Unity5 along with a client side with a MySQL database.

Along with that system I have implemented an email activation system on registration that requires link activation.

Everything works pretty well and I thought this could be a good time to stop working and let other people help me fully understand if my TCP socket implementation and practice is correct.

First is the Windows Application Login Server.

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Mail;
using System.Net.Mime;
using System.Net.Sockets;
using System.IO;
using System.Security.Cryptography;
using MySql.Data.MySqlClient;

namespace LoginServer
{
    public partial class frmLoginServer : Form
    {
        public string TBLog
        {
            get { return tbLog.Text; }
            set { tbLog.AppendText(value); }
        }

        public List<ClientSocket> _clientSockets { get; set; }

        private byte[] _buffer = new byte[1024];
        private Socket _serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        //MySql Objects
        private string connStr = @"server=localhost;user=root;password=1234;database=exodus_db";
        private MySqlConnection mysqlConn = null;
        private MySqlDataReader mysqlRdr = null;
        private HTTPServer webServer = new HTTPServer();

        public frmLoginServer()
        {
            InitializeComponent();
            CheckForIllegalCrossThreadCalls = false;
            if (!Directory.Exists("log"))
                Directory.CreateDirectory("log");
            _clientSockets = new List<ClientSocket>();
        }

        private void frmLoginServer_Load(object sender, EventArgs e)
        {
            SetupServer();
        }

        private void SetupServer()
        {
            lStatus.Text = "Setting up Login server...";
            tbLog.AppendText("Setting up Login server...\n");
            MySqlSetup();
            _serverSocket.Bind(new IPEndPoint(IPAddress.Any, 32211));
            _serverSocket.Listen(1);
            lStatus.Text = "Login server is running...";
            tbLog.AppendText("Login server is running...\n");
            lStatus.BackColor = Color.Green;
            Task.Factory.StartNew(() => webServer.StartServer());
            tbLog.AppendText("HTTP server is running...\n");
            tLog.Enabled = true;
            _serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null);
        }

        private void MySqlSetup()
        {
            try
            {
                mysqlConn = new MySqlConnection(connStr);
                mysqlConn.Open();
                tbLog.AppendText("Connected to MySQL (" + mysqlConn.ServerVersion + ")\n");
            }
            catch (MySqlException e)
            {
                tbLog.AppendText("MySQL Error: " + e.ToString() + "\n");
                return;
            }
            finally
            {
                if (mysqlConn != null)
                    mysqlConn.Close();
            }
        }

        private void AcceptCallback(IAsyncResult ar)
        {
            Socket s = _serverSocket.EndAccept(ar);
            _clientSockets.Add(new ClientSocket(s));
            lbClients.Items.Add(s.RemoteEndPoint.ToString());
            lClientsConnected.Text = "Clients connected: " + _clientSockets.Count.ToString();
            tbLog.AppendText("New client connected from " + s.RemoteEndPoint.ToString() + "\n");
            s.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), s);
            _serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null);
        }

        private void ReceiveCallback(IAsyncResult ar)
        {
            Socket s = (Socket)ar.AsyncState;
            if (s.Connected)
            {
                int receivedBytes;
                try
                {
                    receivedBytes = s.EndReceive(ar);
                }
                catch (Exception)
                {
                    for (int i = 0; i < _clientSockets.Count; i++)
                    {
                        if (_clientSockets[i]._socket.RemoteEndPoint.ToString().Equals(s.RemoteEndPoint.ToString()))
                        {
                            lbClients.Items.RemoveAt(lbClients.Items.IndexOf(s.RemoteEndPoint.ToString()));
                            tbLog.AppendText("Client " + s.RemoteEndPoint.ToString() + " disconnected." + "\n");
                            _clientSockets.RemoveAt(i);
                            lClientsConnected.Text = "Clients connected: " + _clientSockets.Count.ToString();
                        }
                    }
                    return;
                }
                if (receivedBytes != 0)
                {
                    byte[] dataBuffer = new byte[receivedBytes];
                    Array.Copy(_buffer, dataBuffer, receivedBytes);
                    string textReceived = Encoding.ASCII.GetString(dataBuffer);
                    HandlePacket(s, textReceived);
                }
                else
                {
                    for (int i = 0; i < _clientSockets.Count; i++)
                    {
                        if (_clientSockets[i]._socket.RemoteEndPoint.ToString().Equals(s.RemoteEndPoint.ToString()))
                        {
                            lbClients.Items.RemoveAt(lbClients.Items.IndexOf(s.RemoteEndPoint.ToString()));
                            _clientSockets.RemoveAt(i);
                            lClientsConnected.Text = "Clients connected: " + _clientSockets.Count.ToString();
                        }
                    }
                }
            }
            s.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), s);
        }

        private void SendData(Socket s, string msgToSend)
        {
            byte[] data = Encoding.ASCII.GetBytes(msgToSend);
            s.BeginSend(data, 0, data.Length, SocketFlags.None, new AsyncCallback(SendCallback), s);
            _serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null);
        }

        private void SendCallback(IAsyncResult ar)
        {
            Socket s = (Socket)ar.AsyncState;
            s.EndSend(ar);
        }

        private void SendToSelected(string msgToSend)
        {
            for (int i = 0; i < lbClients.SelectedItems.Count; i++)
            {
                for (int j = 0; j < _clientSockets.Count; j++)
                {
                    if (lbClients.SelectedItems[i].ToString().Equals(_clientSockets[j]._socket.RemoteEndPoint.ToString()))
                    {
                        SendData(_clientSockets[j]._socket, msgToSend);
                    }
                }
            }
        }

        private void Broadcast(string msgToSend)
        {
            for (int i = 0; i < _clientSockets.Count; i++)
            {
                SendData(_clientSockets[i]._socket, msgToSend);
            }
        }

        private void Respond(Socket s, string msgToSend)
        {
            SendData(s, msgToSend);
        }

        private void HandlePacket(Socket s, string data)
        {
            string[] splittedData = data.Split('/');
            switch (splittedData[0])
            {
                default:
                    tbLog.AppendText("Unknown packet: " + data + "\n");
                    break;
                case "0x000":
                    tbLog.AppendText("Login request: " + splittedData[1] + " " + splittedData[2] + "\n");
                    SendData(s, HandleLogin(splittedData[1], splittedData[2]));
                    break;
                case "0x001":
                    tbLog.AppendText("Logout request: " + splittedData[1] + " " + splittedData[2] + "\n");
                    // TODO: HandleLogout(splittedData[1], splittedData[2]);
                    break;
                case "0x002":
                    tbLog.AppendText("Registration request: " + splittedData[1] + " " + splittedData[2] + " " + splittedData[3] + "\n");
                    SendData(s, HandleRegister(splittedData[1], splittedData[2], splittedData[3]));
                    break;
            }
        }

        private string HandleLogin(string user, string pass)
        {
            string msgToRespond = "";
            try
            {
                mysqlConn = new MySqlConnection(connStr);
                mysqlConn.Open();
                const string cmdString = "SELECT * FROM user WHERE user_name = @Username";
                MySqlCommand cmd = new MySqlCommand(cmdString, mysqlConn);
                cmd.Parameters.AddWithValue("@Username", user);
                cmd.Prepare();
                mysqlRdr = cmd.ExecuteReader();
                if (mysqlRdr.HasRows)
                {
                    while (mysqlRdr.Read())
                    {
                        // mysqlReader :: 0->ID (int), 1->Username (string), 2->hash (string), 3->salt (string), 4-> email (string), 5-> Activation Field (String) 
                        if (mysqlRdr.GetString(2) == (StringToMD5(pass, mysqlRdr.GetString(3)))
                            && mysqlRdr.GetString(5) == "VERIFIED")
                        {
                            Console.WriteLine(user + " login Succesful.\n");
                            msgToRespond = "0x000";
                        }
                        else if (mysqlRdr.GetString(2) != StringToMD5(pass, mysqlRdr.GetString(3)))
                        {
                            tbLog.AppendText("Error: Wrong Password for user " + user + "\n");
                            msgToRespond = "0x001";
                        }
                        else
                        {
                            tbLog.AppendText("Error: " + user + " account is not activated\n");
                            msgToRespond = "0x002";
                        }
                    }
                }
                else
                {
                    tbLog.AppendText("Error: " + user + " does not exist!");
                    msgToRespond = "0x003";
                }
            }
            catch (MySqlException e)
            {
                tbLog.AppendText("MySQL Error: " + e.ToString());
            }
            finally
            {
                if (mysqlRdr != null)
                    mysqlRdr.Close();
                if (mysqlConn != null)
                    mysqlConn.Close();
            }
            return msgToRespond;
        }

        private string HandleRegister(string user, string pass, string email)
        {
            string msgToRespond = "";
            // Check for duplicates, if none found register user.
            if (!isExist("user_name", user) && !isExist("email", email))
            {
                try
                {
                    mysqlConn = new MySqlConnection(connStr);
                    mysqlConn.Open();
                    string salt = GenerateSalt();
                    string activationCode = Guid.NewGuid().ToString();
                    const string cmdString = "INSERT INTO user ( user_name, password, salt, email, activation_code )" +
                        "VALUES" +
                        "(@Username, @Password, @Salt, @Email, @ActivationCode)";
                    MySqlCommand cmd = new MySqlCommand(cmdString, mysqlConn);
                    cmd.Parameters.AddWithValue("@Username", user);
                    cmd.Parameters.AddWithValue("@Password", StringToMD5(pass, salt));
                    cmd.Parameters.AddWithValue("@Salt", salt);
                    cmd.Parameters.AddWithValue("@Email", email);
                    cmd.Parameters.AddWithValue("@ActivationCode", activationCode);
                    cmd.Prepare();

                    mysqlRdr = cmd.ExecuteReader();
                    Console.WriteLine("Registrated succesfully, account validation email will be sent shortly.");
                    msgToRespond = "0x000";
                    SendActivationEmail(email, user, activationCode);
                }
                catch (MySqlException e)
                {
                    tbLog.AppendText("MySQL Error: " + e.ToString());
                }
                finally
                {
                    if (mysqlRdr != null)
                        mysqlRdr.Close();
                    if (mysqlConn != null)
                        mysqlConn.Close();
                }
            }
            else if (isExist("user_name", user))
            {
                tbLog.AppendText("Error: " + user + " already exists!");
                msgToRespond = "0x001";
            }
            else {
                tbLog.AppendText("Error: " + email + "already exists!");
                msgToRespond = "0x002";
            }
            return msgToRespond;
        }

        private void LogToFile()
        {
            string file = ".\\log\\" + DateTime.Now.ToString("yyyy-M-d") + ".txt";
            if (!File.Exists(file))
            {
                File.Create(file).Dispose();
                using (TextWriter tw = new StreamWriter(file))
                {
                    tw.WriteLine("This is the log file of the " + DateTime.Now.ToString("yyyy-M-d"));
                    tw.Close();
                }
            }
            File.AppendAllLines(file, tbLog.Lines);
            tbLog.Clear();
            tbLog.AppendText("Log saved!\n");
        }

        private void bSendToSelected_Click(object sender, EventArgs e)
        {
            SendToSelected(tbInput.Text);
            tbLog.AppendText("Send to selected: " + tbInput.Text + "\n");
        }

        private void bBroadcast_Click(object sender, EventArgs e)
        {
            Broadcast(tbInput.Text);
            tbLog.AppendText("Send to all: " + tbInput.Text + "\n");
        }

        private void tLog_Tick(object sender, EventArgs e)
        {
            LogToFile();
        }

        private bool isExist(string DBField, string userInput)
        {
            bool isExistFlag = false;
            try
            {
                MySqlConnection mysqlConn = new MySqlConnection(connStr);
                mysqlConn.Open();
                string cmdString = "SELECT COUNT(*) FROM user WHERE " + DBField + "= @userInput";
                MySqlCommand cmd = new MySqlCommand(cmdString, mysqlConn);
                cmd.Parameters.AddWithValue("@userInput", userInput);
                cmd.Prepare();
                // Returns 0 if not exists, 1 for duplicate.
                int trueFalse = Convert.ToInt32(cmd.ExecuteScalar());
                if (trueFalse > 0)
                {
                    isExistFlag = true;
                }
                else {
                    isExistFlag = false;
                }
            }
            catch (MySqlException e)
            {
                tbLog.AppendText("MySQL Error: " + e.ToString());
            }
            finally
            {
                if (mysqlConn != null)
                    mysqlConn.Close();
            }
            return isExistFlag;
        }

        private void SendActivationEmail(string email, string userName, string activationCode)
        {
            using (MailMessage mm = new MailMessage("exodusgameac@gmail.com", email))
            {
                string acUrl = ("http://localhost:8080/Thisisitac?ActivationCode=" + activationCode);
                MailMessage message = new MailMessage();
                string fromEmail = "xyz";
                string password = "***";
                string toEmail = email;
                message.From = new MailAddress(fromEmail);
                message.To.Add(toEmail);
                message.Subject = "Thisisit Account Activation";
                string body = "<body>Hello " + userName + ","
                            + "<br /><br />Please click the following link to activate your account"
                            + "<br /><a href = " + acUrl + ">Click here to activate your account.</a>"
                            + "<br /><br />Thanks</body>";
                AlternateView htmlView = AlternateView.CreateAlternateViewFromString(body, new ContentType("text/html"));
                message.AlternateViews.Add(htmlView);
                message.IsBodyHtml = true;
                message.DeliveryNotificationOptions = DeliveryNotificationOptions.OnFailure;

                using (SmtpClient smtpClient = new SmtpClient("smtp.gmail.com", 587))
                {
                    smtpClient.EnableSsl = true;
                    smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
                    smtpClient.UseDefaultCredentials = false;
                    smtpClient.Credentials = new NetworkCredential(fromEmail, password);

                    try
                    {
                        smtpClient.Send(message);
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine("Error: Failed to send email, {0}", e);
                    }
                }
            }
        }

        // Encryption from string to MD5 Hash
        private string StringToMD5(string pass, string salt)
        {
            MD5 md5 = new MD5CryptoServiceProvider();
            byte[] bytes = md5.ComputeHash(Encoding.UTF8.GetBytes(pass + salt));
            string result = BitConverter.ToString(bytes).Replace("-", String.Empty);
            return result;
        }

        // RNGEncryption generating random Salt
        private string GenerateSalt()
        {
            using (RNGCryptoServiceProvider salt = new RNGCryptoServiceProvider())
            {
                byte[] data = new byte[4];
                salt.GetBytes(data);
                string result = BitConverter.ToString(data, 0);
                return result;
            }
        }

        //
        // Add new functions above this line Here
        //
    }

    public class ClientSocket
    {
        public Socket _socket { get; set; }
        public string _name;

        public ClientSocket(Socket s)
        {
            this._socket = s;
        }
    }

}

The HTTPServer implemented inside the application used for reading verification attempts.

using System;
using System.Net;
using System.Data;
using System.Threading.Tasks;
using MySql.Data.MySqlClient;

namespace LoginServer
{
    public class HTTPServer
    {

        private readonly frmLoginServer form;

        public async Task StartServer()
        {
            try {
                SetupServer();
            }
            catch (Exception e)
            {
                form.TBLog = "HTTPServer Error: " +e.ToString();
            }
        }

        private void SetupServer()
        {
            var listener = new HttpListener();

            listener.Prefixes.Add("http://localhost:8080/");
            listener.Prefixes.Add("http://127.0.0.1:8080/");

            listener.Start();
            while (true)
            {
                try
                {
                    HttpListenerContext context = listener.GetContext(); //Block until a connection comes in
                    context.Response.StatusCode = 200;
                    context.Response.SendChunked = true;
                    string clientIP = context.Request.RemoteEndPoint.ToString();
                    AccountActivation(context);
                }
                catch (Exception e)
                {

                    form.TBLog = "HTTPServer Error: " + e.ToString();
                }
            }
        }

        private void AccountActivation(HttpListenerContext context)
        {
            string connectionString = @"server=localhost;user=root;password=1234;database=exodus_db";
            string activationCode = !string.IsNullOrEmpty(context.Request.QueryString["ActivationCode"]) ? context.Request.QueryString["ActivationCode"] : Guid.Empty.ToString();
            using (MySqlConnection con = new MySqlConnection(connectionString))
            {
                using (MySqlCommand cmd = new MySqlCommand("UPDATE user SET activation_code='VERIFIED' WHERE activation_code=@ActivationCode"))
                {
                    using (MySqlDataAdapter sda = new MySqlDataAdapter())
                    {
                        cmd.CommandType = CommandType.Text;
                        cmd.Parameters.AddWithValue("@ActivationCode", activationCode);
                        cmd.Connection = con;
                        con.Open();
                        int rowsAffected = cmd.ExecuteNonQuery();
                        con.Close();
                        if (rowsAffected == 1)
                        {
                            byte[] buffer = System.Text.Encoding.UTF8.GetBytes("<HTML><BODY> Account verified </BODY></HTML>");
                            context.Response.OutputStream.Write(buffer, 0, buffer.Length);
                        }
                        else
                        {
                            byte[] buffer = System.Text.Encoding.UTF8.GetBytes("<HTML><BODY> Verification code doesn't exist </BODY></HTML>");
                            context.Response.OutputStream.Write(buffer, 0, buffer.Length);
                        }
                    }
                }

            }
        }
    }
}

Now for the Client side, starting with the LoginController(these scripts are attached to unity5 gameobjects and public vars assigned via. unity editor)

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using System.Collections;
using System;
using System.Net;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;

public class LoginController : MonoBehaviour {

    // Login UI Objects (Input fields and Buttons)
    public Button loginButton;
    public Button registerButton;
    public InputField userNameInput;
    public InputField passwordInput;

    // default IP and Port
    public string serverAddress = "127.0.0.1";
    public int serverPort = 32211;

    // Socket objects
    private TcpClient _client;
    private NetworkStream _stream;
    private Thread _thread;

    private byte[] _buffer = new byte[1024];
    private string receiveMsg = "";
    private bool isConnected = false;

    //
    // Connection Handler
    //
    void Start(){
        SetupConnection ();
    }

    void OnApplicationQuit() {
        CloseConnection ();
    }

    private void SetupConnection(){
        try{
            _thread = new Thread(ReceiveData);
            _thread.IsBackground = true;
            _client = new TcpClient(serverAddress, serverPort);
            _stream = _client.GetStream();
            _thread.Start();
            isConnected = true;
        } catch (Exception e) {
            CloseConnection ();
            Debug.Log (e.ToString ());
        }
    }

    private void ReceiveData(){
        if (!isConnected)
            return;

        int numberOfBytesRead = 0;
        while (isConnected && _stream.CanRead) {
            try{
                numberOfBytesRead = _stream.Read (_buffer, 0, _buffer.Length);
                receiveMsg = Encoding.ASCII.GetString (_buffer, 0, numberOfBytesRead);
                _stream.Flush ();
                HandleResponse(receiveMsg);
                receiveMsg = "";
            } catch (Exception e) {
                CloseConnection ();
                Debug.Log (e.ToString ());
            } 
        }
    }

    private void CloseConnection() {
        if (isConnected) {
            _thread.Interrupt ();
            _stream.Close ();
            _client.Close ();
            isConnected = false;
            receiveMsg = "";
        }
    }

    public void SendData(string msgToSend){
        byte[] bytesToSend = Encoding.ASCII.GetBytes (msgToSend);
        if (_stream.CanWrite)
            _stream.Write (bytesToSend, 0, bytesToSend.Length);
    }

    //
    // UI Functions
    //
    public void OnLoginClick()
    {
        if (userNameInput.text.Equals(""))
        {
            Debug.Log ("Empty Username");
        } else if (passwordInput.text.Equals("")) {
            Debug.Log ("Empty Password");
        } else {
            string msgToSend  = ("0x000/" +userNameInput.text + "/" + passwordInput.text);
            SendData(msgToSend);
        }
    }

    public void HandleResponse(string response){
        switch (response) {
        default:
            Debug.Log ("Error: Unknown response: " + response);
            break;
        case "0x000":
            Debug.Log ("Succesfully logged in");
            break;
        case "0x001":
            Debug.Log("Error: Wrong password");
            break;
        case "0x002":
            Debug.Log("Error :Account not activated");
            break;
        case "0x003":
            Debug.Log("Error: User name does not exist");
            break;
        }
    }

    public void OnRegisterClick()
    {
        CloseConnection ();
        SceneManager.LoadScene (1);
    }
}

The register controller looks very much the same, just handles the packets differently with the appropriate response.

\$\endgroup\$
  • \$\begingroup\$ Did you consider just importing a NuGet package that handles the low level socket messaging for you? Much less code to maintain and debug... \$\endgroup\$ – Mattias Åslund Jun 8 '16 at 21:36
  • \$\begingroup\$ had no idea .Net have a package manager, will definitely take a look into that nevertheless I would still appreciate any review on the code I wrote. \$\endgroup\$ – Daniel Netzer Jun 9 '16 at 6:52
4
\$\begingroup\$

You seem to be doing a lot of low-level byte manipulation when using the sockets. Once you have a NetworkStream, just wrap it inside a StreamReader/StreamWriter and let them handle sending strings over the wire.

When designing a protocol, please take into account that sending Hello World and calling = _stream.Read (_buffer, 0, _buffer.Length); on the client rarely results in reading Hello World in one go. It can return more data like Hello WorldSecond Message Sent By Server or can return just a part, Hello W, making the rest available on a future call to Read().

Begin/End methods are part of the old .NET async APIs. While they still work ok, using the newer async/await (.NET 4.5+) results in much less code which is almost as easy to read as synchronous one.

For instance: .NET offers the TcpListener which provides TcpListener.AcceptTcpClientAsync(). The TcpClient when provides GetStream() which provides you with a NetworkStream which you can pass to a StreamReader which then has ReadLineAsync(). Should you use newlines as a message separator in your protocol, this ends up providing you with each individual message, as a string, without you having to manage any buffers or threads manually.

Also pay attention when calling UI code from a method that may not execute on the UI thread. WinForms have a property InvokeRequired that can be used to check if you can directly access UI elements or need to call Invoke to marshal them on the UI thread.

Don't use SELECT * in a query to then access the columns by index. It's too fragile. You add an extra column and boom, the code starts to fail. Either explicitly specify the columns (preferred) or at least ask the reader what is the index for column X instead of hard-coding it.

When disposing of resources, using using is much cleaner than adding a finally block which closes the resource. You're using using in GenerateSalt(), but that's about it.

When writing to a file, consider helper functions:

File.Create(file).Dispose();
using (TextWriter tw = new StreamWriter(file))
{
    tw.WriteLine("This is the log file of the " + DateTime.Now.ToString("yyyy-M-d"));
    tw.Close();
}

can simply be replaced by a call to File.WriteAllText() or File.WriteAllLines().

\$\endgroup\$
  • \$\begingroup\$ so you basiclly advice me to replace the entire architecture to async/wait instead of Begin/End? either way thats not realy the review I was looking for since this specific code is just for account creation and login which is a very small amount of bytes transfer. will look further into the async / await you suggested. \$\endgroup\$ – Daniel Netzer Jun 9 '16 at 10:04
  • \$\begingroup\$ That's a suggestion that allows you to get rid of a lot of infrastructure code from your code (e.g. you no longer have to create a new thread just to receive data from the socket). The part about sending messages without a way to distinguish where one ends and another one begins will affect you're application either way. You'll probably use that socket for you're entire application conversation, not just for logging in (otherwise it's kind of useless) so mixing up/misinterpreting messages will easily lead to problems. I've updated my answer with a few extra points as well. \$\endgroup\$ – D. Jurcau Jun 9 '16 at 10:27
  • \$\begingroup\$ Appreciate the edit and the example given, any chance you can add an example or give a link that shows how to simplify the socketing and threading part perhaps since this is my major concern regarding the program, scalability and speed mostly. \$\endgroup\$ – Daniel Netzer Jun 9 '16 at 10:45
  • 1
    \$\begingroup\$ My main concern is the fact that every client socket is writing to the same buffer. \$\endgroup\$ – wgraham Jun 9 '16 at 10:50
  • \$\begingroup\$ Good catch, missed that. @DanielNetzer I've added links to higher level classes related to tcp networking. Before diving into async/await you should understand the Task Parallel Library. async/await is basically syntactic sugar over this library. \$\endgroup\$ – D. Jurcau Jun 9 '16 at 11:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.