C# dasturlash tilida oddiy chat dasturi

Salom. Hozirgi kunda C# ni tanimaydigan dasturchi bo'lmasa kerak (taniydigan ishliydigan demadim :) ). Keling shu tilda oddiy chat dasturini tuzaylik. Ya'ni bitta local tarmoqda ishliydigan SERVER va CLIET li chat dasturini yuzamiz.

Man bu dasturni tuzish uchun Visual Studio 2010 va Framework 4 dan foydalanaman.

VS2010 ni yuklagandan keyin, File >> New >> Project… (CTRL + SHIFT + N) ga kirib bitta «Windows Form Application» ni tanlab proyekt yaratamiz. Proyekt nomi «ChatServer» deb nomlang va OK tugmasini bosing.

Endi yana CTRL + SHIFT + N tugmasini bosib, yana «Windows Form Application» tanlang va proyekt nomini «ChatClient» deb, Solution ni «Add to solution» ga tanlab, OK ni bosing. Keyin Solution Explorer dan Solition ni tanlab, mishkani o'ng tugmasini bosing va Properties ga kiring. Undan Multiple startup projects ni tanlab, Action ustuniga START ni tanlang. Shunda birdaniga ikkita proyekt ham ishga tushadigan bo'ladi.

Endi maqsadga o'tamiz :). Bu chat dasturini yaratishda asosan System.Net, System.Net.Sockets va System.Threading namespacelaridan foydalanamiz.

ChatServer dasturi
Bu dasturda server bo'lib, bitta portni tinglab turadi va ChetClient lar shu portga bog'lanib bir-biri bilan o'zaro gaplashishadi.

Server qismidagi formada hozirda ONLINE bo'lgan userlarni ro'yxatini ko'rsatib turadigan ListBox (listBox1) bo'ladi.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace ChatServer
{
    public partial class Form1 : Form
    {
        //Thread holatini aniqlaydi
        bool isRunning = true;

        //Thread o'zgaruvchisi
        Thread threadListener, threadGateway;

        //Bog'langan clientlarni saqlaydigan o'zgaruvchi
        List<ChatClientData> connectedClients = new List<ChatClientData>();

        //Nicklar ro'yxati, tekshirish uchun kerak
        List<string> nickNameList = new List<string>();

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //CLient larni bog'lanishini kutadigan patok
            threadListener = new Thread(ListenerThread);
            threadListener.Start();

            //clientlar orasidagi xabarni bir biriga o'tkazish uchun ishlatiladi
            threadGateway = new Thread(GatewayThread);
            threadGateway.Start();
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            //Patolarni to'xtatish
            isRunning = false;

            //Patoklar yopilishini kutish (5 sekundgacha)
            threadListener.Join(5000);
            threadGateway.Join(5000);

            //Bog'langan kanallarni uzamiz
            while(connectedClients.Count > 0)
            {
                ChatClientData client = connectedClients[0];
                try
                {
                    client.tcpClient.Close();
                }
                catch (Exception ee) { }

                connectedClients.RemoveAt(0);
            }
        }

        private void ListenerThread()
        {
            //Barcha interfeysdagi 3366 porni tinglaydigan TcpListener obyektini yaratish
            TcpListener listener = new TcpListener(IPAddress.Any, 3366);

            try
            {
                //3366 portni tinglashni boshlash
                listener.Start();

                //bir marotaba refresh qilish uchun kerak
                //chunki while juda tez bo'lib, 1 sekund ichida 10 marta ham 
                //takrorlanib (aylanib) qolishi mumkin
                bool refreshList = true;

                while (isRunning)
                {
                    //Barcha ONLINE userlarni listbox da ko'rsatish, har 5 sekundda REFRESH qilamiz
                    if (DateTime.Now.Second % 5 == 0)
                    {
                        if (refreshList)
                        {
                            //Patokni ichida bo'lganligi uchun, delegate ishlatamiz
                            listBox1.Invoke((MethodInvoker)delegate()
                            {
                                listBox1.Items.Clear();
                                listBox1.Items.AddRange(connectedClients.ToArray());
                            });

                            refreshList = false;
                        }
                    }
                    else
                        refreshList = true;

                    //Yangi client bog'lanishga harakat qilyaptimi?
                    if (!listener.Pending())
                    {
                        //Yo'q bo'lsa, yana tekshirishda davom etamiz
                        Thread.Sleep(1);
                        continue;
                    }

                    //Yangi client bog'landi
                    connectedClients.Add(new ChatClientData(listener.AcceptTcpClient()));

                    Thread.Sleep(1);
                }

                //Tinglashni to'xtatish
                listener.Stop();
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message);
            }
        }

        private void GatewayThread()
        {
            while (isRunning)
            {
                foreach (ChatClientData client in connectedClients)
                {
                    //Async holda ma'lumot o'qish
                    //Bu bir vaqtda bir nechta clientda darrov javob berish uchun ishlatiladi
                    try
                    {
                        client.tcpClient.GetStream().BeginRead(client.Buffer, 0, client.Buffer.Length, new AsyncCallback(ReadDataCallback), client);
                    }
                    catch (Exception e) { }
                }

                Thread.Sleep(1);
            }
        }

        private void ReadDataCallback(IAsyncResult result)
        {
            try
            {
                //Obyektni olish
                ChatClientData client = result.AsyncState as ChatClientData;

                //Ma'lumotni o'qish, agar bo'lsa n > 0, ya'ni nechta belgi borligini ko'rsatadi
                int n = client.tcpClient.GetStream().EndRead(result);
                if (n > 0)
                {
                    //Kelgan qatorni STRING ga o'tkazamiz
                    string str = ASCIIEncoding.UTF8.GetString(client.Buffer, 0, n);

                    //kelganlarni bitta joyga yig'ish
                    client.sb.Append(str);

                    //ma'lumot yakunlanganligini aniqlash
                    //agar ma'lumot oxirida \n bo'lsa demak bitta xabar yakunlangan
                    if (str.EndsWith("\n"))
                    {
                        //xabarni jo'natamiz
                        string data = client.sb.ToString();

                        //Client birinchi barotaba o'zini NICK ni jo'natadi 
                        //shuning uchun agar nick bo'sh bo'lsa yozib qo'yamiz
                        if (client.Nick == null)
                        {
                            //nickni borligini tekshirish
                            //agar bo'lsa nick_1, nick_2 kabilar tekshiriladi
                            int counter = 1;
                            string nick = data.TrimEnd("\n".ToCharArray());
                            while (nickNameList.IndexOf(nick) >= 0)
                            {
                                nick = data.TrimEnd("\n".ToCharArray()) + "_" + counter;
                                counter++;
                            }

                            nickNameList.Add(nick);
                            client.Nick = nick;

                            //yangi nickni va online userlarni clientni o'ziga jo'natamiz
                            byte[] sendData = ASCIIEncoding.UTF8.GetBytes(nick + "\n");
                            client.tcpClient.GetStream().BeginWrite(sendData, 0, sendData.Length, new AsyncCallback(WriteDataCallback), client);

                            //online userlarni jo'natish
                            sendData = ASCIIEncoding.UTF8.GetBytes("\r\t" + String.Join("\r", nickNameList.ToArray()) + "\n");
                            foreach (ChatClientData c in connectedClients)
                            {
                                c.tcpClient.GetStream().BeginWrite(sendData, 0, sendData.Length, new AsyncCallback(WriteDataCallback), c);
                            }
                        }
                        else
                        {
                            //Nick kiritilgan bo'lsa kimga jo'natish kerakligini aniqlaymiz

                            string[] arr = data.Split("\t".ToCharArray());

                            //nick bo'yicha clientni qidirish
                            foreach (ChatClientData f in connectedClients)
                            {
                                if (f.Nick.EndsWith(arr[0]))
                                {
                                    /**
                                     * Agar BeginWrite ni o'rniga Write ishlatsangiz
                                     * Patokni yopayotganingizda yoki client.tcpClient 
                                     * ni Close qilsangiz, TIMEOUT berib qotib qoladi
                                     * to TIMEOUT bo'lguncha
                                     * 
                                     * BeginWrite qilsangiz, Xatolik bo'lsada yopilib ketadi
                                     */

                                    //yangi client nomidan jo'natish
                                    data = client.Nick + "\t" + arr[1];
                                    byte[] sendData = ASCIIEncoding.UTF8.GetBytes(data);
                                    f.tcpClient.GetStream().BeginWrite(sendData, 0, sendData.Length, new AsyncCallback(WriteDataCallback), f);
                                }
                            }
                        }

                        //builderni tozalaymiz
                        client.sb = new StringBuilder();
                    }
                }
            }
            catch (Exception e) { }
        }

        private void WriteDataCallback(IAsyncResult result)
        {
            try
            {
                ChatClientData client = result.AsyncState as ChatClientData;
                client.tcpClient.GetStream().EndWrite(result);
            }
            catch (Exception e) { }
        }
    }

    //Bog'langan client haqida ma'lumotni saqlaydigan klass
    public class ChatClientData
    {
        public TcpClient tcpClient;
        public string Nick = null;
        public byte[] Buffer = new byte[1024];
        public StringBuilder sb = new StringBuilder();

        public ChatClientData(TcpClient client)
        {
            this.tcpClient = client;
        }

        public override string ToString()
        {
            return Nick == null ? "[new client]" : Nick;
        }
    }
}


Endi client tarafi. Bunda 2 ta ListBox (listBox1 — online userlarni ro'yxati, listBox2 — yozilgan xabarlar), TextBox (textBox1 — xabar va nickni kiritish uchun), Button (button1 — dastlab nickni kiritish va keyin xatni jo'natish uchun)

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

namespace ChatClient
{
    public partial class Form1 : Form
    {
        //Har bir userga nisbatan oxirgi 100 ta xabarni saqlaydi
        Dictionary<string, List<string>> messageList = new Dictionary<string, List<string>>();

        //Asosiy patok
        Thread threadClient;

        //patok ishlab turganligini bildiruvchi o'zgaruvchi
        bool isRunning = true;

        //nick 
        string nick;

        //listBox1 dan tanlangan nick nomi
        string selectedNick = null;

        //Jo'natilishi lozim bo'lgan xabar
        string message = "";

        //Lock obyekt
        object lockObject = new object();

        //Kelgan xabarlarni to '\n' gacha yig'adigan o'zgaruvchi
        StringBuilder messageBuilder = new StringBuilder();

        //Buffer
        byte[] messageBuffer = new byte[1024];

        //online userlar
        List<string> onlineUsers = new List<string>();

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
           
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            isRunning = false;
            threadClient.Join(5000);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (nick == null)
            {
                if (textBox1.Text.Length == 0)
                {
                    MessageBox.Show("Nick kiritilmagan");
                    textBox1.Focus();
                    return;
                }

                //nickni saqlash
                nick = textBox1.Text;

                textBox1.Text = "";
                button1.Text = "Jo'natish";

                //birinchi nickni jo'natamiz serverga
                lock (lockObject)
                {
                    message = nick + "\n";
                    nick = "";
                }

                //serverga bog'lanish patoki
                threadClient = new Thread(ClientThread);
                threadClient.Start();

                return;
            }

            //nick kiritilgan bo'lsa
            
            //bir vaqtda o'qish va yozishni oldini olish uchun
            //message o'zgaruvchisiga
            lock (lockObject)
            {
                if (!textBox1.Text.Equals("") && selectedNick != null)
                {
                    message = selectedNick + "\t" + textBox1.Text.Replace("\t", "") + "\n";
                    if (!messageList.ContainsKey(selectedNick))
                        messageList[selectedNick] = new List<string>();

                    messageList[selectedNick].Add(nick + " >> " + textBox1.Text);

                    ShowMessages(2);

                    textBox1.Text = "";
                }
            }
        }

        private void ClientThread()
        {
            try
            {
                // localhost:3366 ga bog'lanadigan TcpClient obyekti
                TcpClient client = new TcpClient("127.0.0.1", 3366);

                while (isRunning)
                {
                    lock (lockObject)
                    {
                        if (message.EndsWith("\n"))
                        {
                            //string dan byte arrayga o'tkazadi
                            byte[] data = ASCIIEncoding.UTF8.GetBytes(message);
                            message = "";
                            client.GetStream().BeginWrite(data, 0, data.Length, new AsyncCallback(WriteDataCallback), client);
                        }
                    }

                    client.GetStream().BeginRead(messageBuffer, 0, messageBuffer.Length, new AsyncCallback(ReadDataCallback), client);

                    Thread.Sleep(1000);
                }
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message);
            }
        }

        private void WriteDataCallback(IAsyncResult result)
        {
            TcpClient client = result.AsyncState as TcpClient;
            client.GetStream().EndWrite(result);
        }

        private void ReadDataCallback(IAsyncResult result)
        {
            //ketma ketlikni ushlash uchun
            lock (messageBuilder)
            {
                TcpClient client = result.AsyncState as TcpClient;
                int n = client.GetStream().EndRead(result);

                if (n > 0)
                {
                    //byte arraydan stringga o'tkazish
                    string msg = ASCIIEncoding.UTF8.GetString(messageBuffer, 0, n);

                    //kelganlarni yig'ish
                    messageBuilder.Append(msg);

                    if (msg.EndsWith("\n"))
                    {
                        string message = messageBuilder.ToString().TrimEnd("\n".ToCharArray());

                        //yangi nick 
                        if (nick.Equals(""))
                        {
                            nick = message;
                        }
                        else
                        {
                            string[] arr = message.Split("\t".ToCharArray());

                            //online userlar o'yxati
                            if (arr[0].EndsWith("\r"))
                            {
                                onlineUsers = new List<string>();
                                string[] _users = arr[1].Split("\r".ToCharArray());
                                onlineUsers.AddRange(_users);

                                ShowMessages(1);
                            }
                            else
                            {

                                //agar nick mavjud bo'lmasa
                                if (!messageList.ContainsKey(arr[0]))
                                    messageList[arr[0]] = new List<string>();

                                //xatni qo'shish
                                messageList[arr[0]].Add(arr[0] + " >> " + arr[1]);

                                //yangi xabarni ko'rsatish
                                ShowMessages(2);
                            }
                        }

                        //builderni tozalash
                        messageBuilder = new StringBuilder();
                    }
                }
            }
        }

        private void ShowMessages(int i)
        {
            if (listBox1.InvokeRequired)
            {
                listBox1.Invoke((MethodInvoker)delegate()
                {
                    ShowMessages(i);
                });
            }
            else
            {
                string currentSelected = null;
                if (listBox1.SelectedIndex >= 0)
                    currentSelected = listBox1.SelectedItem.ToString();

                //online userlarni ro'yxatini chiqarish
                if (i == 1 || i == 3)
                {
                    listBox1.Items.Clear();
                    listBox1.Items.AddRange(onlineUsers.ToArray());
                }

                //tanlangan userga nisbatan xabarlarni chiqarish
                if (i == 2 || i == 3)
                {
                    listBox2.Items.Clear();
                    if (currentSelected != null && messageList.ContainsKey(currentSelected))
                    {
                        int c = messageList[currentSelected].Count;
                        for (int k = 0; k < c; k++)
                        {
                            listBox2.Items.Add(messageList[currentSelected][k]);
                        }
                    }
                }
            }
        }

        private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (listBox1.SelectedIndex == -1)
            {
                selectedNick = null;
                return;
            }

            selectedNick = listBox1.SelectedItem.ToString();

            ShowMessages(2);
        }
    }
}


Umuman kod ichida izohlar keltirilgan, agar tushunarsiz bo'lsa shu yerda kutib qolaman.

Oddiy chat qilmoqchi edim, lekin oxirida o'zim qiynalib ketdim. Kod sal kasha bo'p qoldi lekin ishlidi. Ha yana protokol haqida

Client serverga bog'langanda o'zini nickini jo'natadi. O'z navbatida server hamma clientlarga barcha online userlar nicklarini jo'natadi. Har qanday xabar \n belgisi bilan tugaydi va nickka jo'natilgan xabar esa
[nick nomi]\t[xabar]\n

ko'rinishida bo'ladi. Online userlar esa
\r\t[nick name]\r[nick name]\r[nick name]...[nick name]\n

ko'rinishida jo'natiladi.

Bu kod oxirigacha yetmagan, ya'ni client chiqib ketsa ONLINE da qolib ketadi va yana shu aloqa bilan bog'liq bo'lgan ayrim Exception lar e'tiborga olinmagan.

Oxirgi natija esa:
Chat natijasi

Shuning bilan chatimiz ham tugadi.

Dasturni kodini shu yerdan, exe fayllarni esa shu yerdan ko'chirishingiz mumkin.

1 комментарий

saidolim
Assalomu alaykum,

Bitta fikr. Shu chat masalasi mana necha yillardan beri Hello World darajasida yozilib kelmoqda. WhatsApp yoki WeChat darajasida o`zimiz StartUp qila olarmikanmi? Nechta programmist shu ishga qo`shila oladi? Nima deb o`ylaysiz?

Saidolim
0