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.
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)
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
ko'rinishida bo'ladi. Online userlar esa
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:
Shuning bilan chatimiz ham tugadi.
Dasturni kodini shu yerdan, exe fayllarni esa shu yerdan ko'chirishingiz mumkin.
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:
Shuning bilan chatimiz ham tugadi.
Dasturni kodini shu yerdan, exe fayllarni esa shu yerdan ko'chirishingiz mumkin.
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