1 Востаннє редагувалося pika1989 (30.01.2017 16:07:29)

Тема: Чат з використанням класів TcpClient та TcpListener

У мене завдання написати чат, використовуючи класи TcpClient та TcpListener. Чат має бути мультиклієнтний.
Я маю сторону сервера та сторону клієнта у різних програмах.
Ось код програми серверної частини (програма консольна):

Прихований текст
 class Program
    {
        /*
         * This dictionary for saving all logins and password in it
         */
        static Dictionary<string, string> logins = new Dictionary<string, string>();

        /*
         *This dictionary for saving ip asddresses in it
         */
        static Dictionary<string, string> addresses = new Dictionary<string, string>();

        /*
         *This list for saving users whose online
         */
        static List<string> userOnline = new List<string>();

        static TcpListener listener;

        /*
         * This method gets from user's message user's login, password
         */
        static void parseUserMessage(string userMessage, ref string userLogin, ref string userPassword)
        {
            userMessage = userMessage.Substring(userMessage.IndexOf(':') + 1);

            string[] userInfo = userMessage.Split('/');

            userLogin = userInfo[0];
            userPassword = userInfo[1];
        }

        static void getUserLogin(string userMessage, ref string userLogin)
        {
            userMessage = userMessage.Substring(userMessage.IndexOf(':') + 1);
            string[] userInfo = userMessage.Split('/');

            userLogin = userInfo[0];
        }

        static string getMd5Hash(MD5 md5Hash, string input)
        {
            byte[] data = md5Hash.ComputeHash(Encoding.ASCII.GetBytes(input));

            StringBuilder sBuilder = new StringBuilder();

            for (int i = 0; i < data.Length; ++i)
            {
                sBuilder.Append(data[i].ToString("x2"));
            }

            return sBuilder.ToString();
        }

        static bool isCorrectUser(string login, string password)
        {
            MD5 md5Hash = MD5.Create();

            string hash = getMd5Hash(md5Hash, password);

            for (int i = 0; i < logins.Count; ++i)
            {
                if(String.Compare(logins[login],hash) == 0)
                {
                    return true;
                }
            }
            
            return false;
        }

        /*
         * This method check whether exists user with this login
         * in base
         * If user exists than method returns true, else false
         */
        static bool isExistUser(string login)
        {
            for (int i = 0; i < logins.Count; ++i)
            {
                if (logins.ContainsKey(login))
                    return true;
            }
            
            return false;
        }

        static void HandleClient()
        {
            string userLogin = null;
            string userPassword = null;
            string userAddress = null;

            TcpClient client = listener.AcceptTcpClient();
            Socket stream = client.Client;
            byte[] buffer = new byte[2048];
            stream.Receive(buffer);

            userAddress = (client.Client.RemoteEndPoint as IPEndPoint).Address.ToString();

            string userMessage = Encoding.ASCII.GetString(buffer).TrimEnd('\0');
            string request = userMessage.Substring(0, userMessage.IndexOf(':'));
    
       /*
            * This part for debugging
        */
            StreamWriter file = File.AppendText("log.txt");
            file.WriteLine(request);
            file.Close();

            switch (request)
            {
                case "Login":
                    parseUserMessage(userMessage, ref userLogin, ref userPassword);
                    
                    if (isExistUser(userLogin))
                    {
                        if (isCorrectUser(userLogin, userPassword))
                        {
                            stream.Send(Encoding.ASCII.GetBytes("Good"));
                            userOnline.Add(userLogin);
                        }
                        else
                        {
                            stream.Send(Encoding.ASCII.GetBytes("Wrong"));
                        }
                    }
                    else
                    {
                        MD5 md5Hash = MD5.Create();
                        string hashPassword = getMd5Hash(md5Hash, userPassword);
                        stream.Send(Encoding.ASCII.GetBytes("Good"));

                        logins.Add(userLogin, hashPassword);
                        addresses.Add(userLogin, userAddress);
                        userOnline.Add(userLogin);
                    }
                    break;

                /*
         * Just for testing. There is must be another code
         */
        case "Send":
                    stream.Send(Encoding.ASCII.GetBytes("Good"));
                    break;

                case "GetListOnline":
                    string loginsForSend = null;
                    for (int i = 0; i < userOnline.Count; ++i)
                    {
                        loginsForSend += userOnline[i];
                        loginsForSend += "/";
                    }

                    loginsForSend += userOnline[userOnline.Count - 1];

                    stream.Send(Encoding.ASCII.GetBytes(loginsForSend));
                    break;

                case "Disconnect":
                    getUserLogin(userMessage, ref userLogin);
                    userOnline.Remove(userLogin);
                    break;
            }
            client.Close();
        }

        static void Main(string[] args)
        {
            try
            {
                listener = new TcpListener(IPAddress.Any, 1024);
                listener.Start();

                while (true)
                {
                    if (listener.Pending())
                    {
                        Thread thread = new Thread(new ThreadStart(HandleClient));
                        thread.Start();
                    }
                }
            }
            catch (SocketException sockEx)
            {
                Console.WriteLine("Socket's error " + sockEx.Message);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error: " + ex.Message);
            }
            finally
            {
                listener.Stop();
            }
        }
    }

І сторона кліента. Вона з інтерфейсом, що має дві форми: одна для логування,

Прихований текст
public partial class Form1 : Form
    {
        string userLogin;
        string userPassword;

        TcpClient client;
        public Form1()
        {
            InitializeComponent();
        }

        private void b_login_Click(object sender, EventArgs e)
        {
            try
            {
                client = new TcpClient();
                client.Connect("192.168.1.8", 1024);
                Socket stream = client.Client;

                if (tb_login.Text != "")
                {
                    userLogin = tb_login.Text;
                }

                if (tb_password.Text != "")
                {
                    userPassword = tb_password.Text;
                }

                string sendMessage = "Login:" + userLogin + "/" + userPassword;
                stream.Send(Encoding.ASCII.GetBytes(sendMessage));

                byte[] buffer = new byte[1024];
                stream.Receive(buffer);
                string serverAnswer = Encoding.ASCII.GetString(buffer).TrimEnd('\0');
                if (serverAnswer == "Good")
                {
                    this.Visible = false;
                    Chat chat = new Chat(this, client, userLogin);
                    chat.ShowDialog();
                    this.Visible = true;
                    tb_login.Text = "";
                    tb_password.Text = "";
                }
                else
                {
                    MessageBox.Show("Wrong! Try again");
                    tb_login.Text = "";
                    tb_password.Text = "";
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error: " + ex.Message);
            }
        }

        private void b_cancel_Click(object sender, EventArgs e)
        {
            this.Close();
        }

    }

друга для чату.

Прихований текст
public partial class Chat : Form
    {
        Form1 parent;
        string userLogin;
        Thread thread;
        TcpClient client;

        public Chat()
        {
            InitializeComponent();
        }

        public Chat(Form1 parent, TcpClient client, string userLogin): this()
        {
            this.parent = parent;
            this.client = client;
            this.userLogin = userLogin;

            thread = new Thread(new ParameterizedThreadStart(getUserOnline));
            thread.IsBackground = true;
            thread.Start(client);
        }

        private void getUserOnline(object obj)
        {
            try
            {
                TcpClient threadClient = (TcpClient)obj;
           
                while (true)
                {
                    Socket threadStream = threadClient.Client;
                    if (lbox_Online.Items.Count > 0)
                        lbox_Online.Invoke(new Action(() => lbox_Online.Items.Clear()));

                    threadStream.Send(Encoding.ASCII.GetBytes("GetListOnline:"));
                    
                    StreamWriter file = File.AppendText("C:\\temp\\client_log.txt");
                    file.WriteLine(111);
                    file.Close();

                    byte[] buffer = new byte[2048];
                    threadStream.Receive(buffer);
                    string serverAnswer = Encoding.ASCII.GetString(buffer).TrimEnd('\0');
                    string[] usersLogins = serverAnswer.Split('/');

                    lbox_Online.Invoke(new Action(() =>
                        {
                            for (int i = 0; i < usersLogins.Length; ++i)
                            {
                                lbox_Online.Items.Add(usersLogins[i]);
                            }
                        }
                    ));
                    Thread.Sleep(2000);
                }
            }
            catch(Exception ex)
            {
                MessageBox.Show("Error: " + ex.Message);
            }
        }

        private void Chat_FormClosing(object sender, FormClosingEventArgs e)
        {
            try
            {
                Socket stream = client.Client;
                string sendMessage = "Disconnect:" + userLogin + "/";
                stream.Send(Encoding.ASCII.GetBytes(sendMessage));
                client.Close();
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error: " + ex.Message);
            }
        }

        private void b_Send_Click(object sender, EventArgs e)
        {
            try
            {
                Socket stream = client.Client;
                string whom = userLogin;
                string sendMessage = "Send:" + userLogin + ":" + textBox2.Text;
                stream.Send(Encoding.ASCII.GetBytes(sendMessage));

                byte[] data = new byte[1024];
                stream.Receive(data);
                string serverAnswer = Encoding.ASCII.GetString(data);
                if (serverAnswer == "good")
                {
                    lbox_Message.Items.Add(sendMessage);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error: " + ex.Message);
            }
        }
    }

І, власне, сама проблема: Коли клієнт надсилає до серевера перше повідомлення (у моєму випадку це при логуванні),
то все ніби працює. Але коли намагаюсь відправляти наступні ( наприклад отримати список корстувачів чи просто відправити повідомлення) виникає виключення:  An established connection was aborted by the software in your host machine.
З чим це може бути пов'язано?

Подякували: 0xDADA11C71

2

Re: Чат з використанням класів TcpClient та TcpListener

ця помилка вилазить на стороні клієнту, чи на стороні сервера?

3

Re: Чат з використанням класів TcpClient та TcpListener

На стороні клієнта

4

Re: Чат з використанням класів TcpClient та TcpListener

в кінці метода

static void HandleClient()

стоїть

client.Close();

може, воно мало б бути в середині switch і мало б викликатись, коли приходить повідомлення Disconnect?

5 Востаннє редагувалося pika1989 (30.01.2017 16:55:10)

Re: Чат з використанням класів TcpClient та TcpListener

Може і так... Взагалі цей рядок я додала недавно
Поставила в Disconnect і тепер маю помилку: an existing connection was forcibly closed by the remote host

6

Re: Чат з використанням класів TcpClient та TcpListener

це після якої дії таке вилізло?

7

Re: Чат з використанням класів TcpClient та TcpListener

Поставила в Disconnect

8

Re: Чат з використанням класів TcpClient та TcpListener

pika1989 написав:

Поставила в Disconnect

а шо лог серверу каже?

9

Re: Чат з використанням класів TcpClient та TcpListener

FakiNyan написав:
pika1989 написав:

Поставила в Disconnect

а шо лог серверу каже?

Що надходить лише один запит на логування

10

Re: Чат з використанням класів TcpClient та TcpListener

І взагалі ситуція не змінилась від початкової, а змінилась лише помилка: an existing connection was forcibly closed by the remote host

11

Re: Чат з використанням класів TcpClient та TcpListener

поставте брейк-поінт на switch, та гляньте, шо там відбувається, коли приходять дані

12 Востаннє редагувалося pika1989 (30.01.2017 17:38:58)

Re: Чат з використанням класів TcpClient та TcpListener

FakiNyan написав:

поставте брейк-поінт на switch, та гляньте, шо там відбувається, коли приходять дані

Коли дані приходять перший раз, все працює норм, але коли  потік( у клієнті) намагається надіслати дані на сервер, то помилка виникає на рядку

threadStream.Receive(buffer);

А на стороні серевера запит від клієнта

threadStream.Send(Encoding.ASCII.GetBytes("GetListOnline:"));

взагалі не приходить
Аналогічно із іншими запитами

13 Востаннє редагувалося pika1989 (30.01.2017 17:38:11)

Re: Чат з використанням класів TcpClient та TcpListener

Я думаю, можливо це зв'язано з передачею даних від однієї форми до іншої (можливо десь втрачаються дані клієнта)

14

Re: Чат з використанням класів TcpClient та TcpListener

pika1989 написав:

Я думаю, можливо це зв'язано з передачею даних від однієї форми до іншої (можливо десь втрачаються дані клієнта)

ото я як раз хтів запропонувати спробувати відправити дані з першої форми, відразу після успішного логіну

15

Re: Чат з використанням класів TcpClient та TcpListener

мені от ще якось не подобається той сервер.
Коли з'являється новий клієнт, то запускається новий потік, в котрому працює

static void HandleClient()

. Але цей метод один раз приймає дані, і один раз відправляє, якщо треба. Після цього метод закінчує роботу, і виходить, що в цьому потоці більше нічого не відбувається. І якщо клієнт знову відправляє якісь дані, то їх ніхто не приймає ж.
І ще є думка, що так, як метод

static void HandleClient()

більше не працює, то всі локальні змінні знищуються, і тому відбувається відключення клієнту

Подякували: pika19891

16

Re: Чат з використанням класів TcpClient та TcpListener

FakiNyan написав:
pika1989 написав:

Я думаю, можливо це зв'язано з передачею даних від однієї форми до іншої (можливо десь втрачаються дані клієнта)

ото я як раз хтів запропонувати спробувати відправити дані з першої форми, відразу після успішного логіну

Я щойно це зробила, і виходить, що коли намагається другий раз надіслати запит, то клієнт уже від'єднаний. Значить проблема дійсно у сервері

17

Re: Чат з використанням класів TcpClient та TcpListener

ну то тре робити вічний цикл в static void HandleClient(), аби після відправлення даних намагався знову прийняти їх

18

Re: Чат з використанням класів TcpClient та TcpListener

Ось що я маю зараз:
сервер:

Прихований текст
...
static void HandleClient()
        {
            string userLogin = null;
            string userPassword = null;
            string userAddress = null;

            //TcpClient client = (TcpClient)obj;
            while (true)
            {
                TcpClient client = listener.AcceptTcpClient();
                Socket stream = client.Client;
                byte[] buffer = new byte[2048];
                stream.Receive(buffer);

                //StreamReader sReader = new StreamReader(client.GetStream(), Encoding.ASCII);
                //StreamWriter sWriter = new StreamWriter(client.GetStream(), Encoding.ASCII);

                userAddress = (client.Client.RemoteEndPoint as IPEndPoint).Address.ToString();

                string userMessage = Encoding.ASCII.GetString(buffer).TrimEnd('\0');
                //string userMessage = sReader.ReadLine();
                string request = userMessage.Substring(0, userMessage.IndexOf(':'));

                StreamWriter file = File.AppendText("log.txt");
                file.WriteLine(request);
                file.Close();

                switch (request)
                {
                    case "Login":
                        parseUserMessage(userMessage, ref userLogin, ref userPassword);

                        if (isExistUser(userLogin))
                        {
                            if (isCorrectUser(userLogin, userPassword))
                            {
                                stream.Send(Encoding.ASCII.GetBytes("Good"));
                                //sWriter.WriteLine("Good");
                                //sWriter.Flush();
                                userOnline.Add(userLogin);
                                // stream.Close();
                            }
                            else
                            {
                                stream.Send(Encoding.ASCII.GetBytes("Wrong"));
                                //sWriter.WriteLine("Wrong");
                                //sWriter.Flush();
                                //stream.Close();
                            }
                        }
                        else
                        {
                            MD5 md5Hash = MD5.Create();
                            string hashPassword = getMd5Hash(md5Hash, userPassword);
                            //sWriter.WriteLine("Good");
                            //sWriter.Flush();
                            stream.Send(Encoding.ASCII.GetBytes("Good"));
                            // stream.Close();

                            logins.Add(userLogin, hashPassword);
                            addresses.Add(userLogin, userAddress);
                            userOnline.Add(userLogin);
                        }
                        break;

                    case "Send":
                        stream.Send(Encoding.ASCII.GetBytes("Good"));
                        //sWriter.WriteLine("Wrong");
                        //sWriter.Flush();
                        break;

                    case "GetListOnline":
                        string loginsForSend = null;
                        for (int i = 0; i < userOnline.Count; ++i)
                        {
                            loginsForSend += userOnline[i];
                            loginsForSend += "/";
                        }

                        loginsForSend += userOnline[userOnline.Count - 1];

                        //StreamWriter file = File.AppendText("log.txt");
                        //file.WriteLine(111);
                        //file.Close();
                        stream.Send(Encoding.ASCII.GetBytes("List gets"));

                        //stream.Send(Encoding.ASCII.GetBytes(loginsForSend));
                        //stream.Close();
                        //sWriter.WriteLine(loginsForSend);
                        //sWriter.Flush();
                        break;

                    case "Disconnect":
                        getUserLogin(userMessage, ref userLogin);
                        userOnline.Remove(userLogin);
                        client.Close();
                        break;
                }
                //client.Close();
            }
        }

        static void Main(string[] args)
        {
            try
            {
                listener = new TcpListener(IPAddress.Any, 1024);
                listener.Start();

                while (true)
                {
                    if (listener.Pending())
                    {
                        Thread thread = new Thread(new ThreadStart(HandleClient));
                        thread.Start();
                    }
                }
            }
            catch (SocketException sockEx)
            {
                Console.WriteLine("Socket's error " + sockEx.Message);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error: " + ex.Message);
            }
            finally
            {
                listener.Stop();
            }
        }

клієнт (перша форма)

Прихований текст
private void b_login_Click(object sender, EventArgs e)
        {
            try
            {
                client = new TcpClient();
                client.Connect("192.168.1.8", 1024);
                Socket stream = client.Client;

                if (tb_login.Text != "")
                {
                    userLogin = tb_login.Text;
                }

                if (tb_password.Text != "")
                {
                    userPassword = tb_password.Text;
                }

                string sendMessage = "Login:" + userLogin + "/" + userPassword;
                stream.Send(Encoding.ASCII.GetBytes(sendMessage));

                byte[] buffer = new byte[1024];
                stream.Receive(buffer);
                string serverAnswer = Encoding.ASCII.GetString(buffer).TrimEnd('\0');
                if (serverAnswer == "Good")
                {
                    //this.Visible = false;
                    //Chat chat = new Chat(this, client, userLogin);
                    //chat.ShowDialog();
                    //this.Visible = true;
                    //tb_login.Text = "";
                    //tb_password.Text = "";
                    //stream = client.Client;
                   
                    stream.Send(Encoding.ASCII.GetBytes("GetListOnline:"));
                    stream.Receive(buffer);
                    serverAnswer = Encoding.ASCII.GetString(buffer).TrimEnd('\0');
                    MessageBox.Show(serverAnswer);
                }
                else
                {
                    MessageBox.Show("Wrong! Try again");
                    tb_login.Text = "";
                    tb_password.Text = "";
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error: " + ex.Message);
            }
        }

Тепер помилку не вибиває, але програма клієнта просто зависає ось тут:

 stream.Send(Encoding.ASCII.GetBytes("GetListOnline:"));
 stream.Receive(buffer);

А логування сервера показує, що сервер знову не отримав запиту:

 stream.Send(Encoding.ASCII.GetBytes("GetListOnline:"));

19

Re: Чат з використанням класів TcpClient та TcpListener

так ви намагаєтесь в циклі отримати клієнта, а вам треба один раз отримати, а потім лише приймати та відправляти дані через нього

Подякували: pika19891

20

Re: Чат з використанням класів TcpClient та TcpListener

Дякую Вам дуже, далі буду допилювати сама
Три дні просиділа над цим чатом і ніяк не могла зрозуміти в чому проблема, як ніби засліпило))
Дякую ще раз

Подякували: FakiNyan, 0xDADA11C72