You are on page 1of 20

Đại Học Quốc Gia Thành Phố Hồ Chí Minh

Trường Đại học Công nghệ Thông tin

Lớp K2C6

Hướng dẫn chi tiết làm 1 chương trình Chat đơn giản
Giáo viên hướng dẫn: Phạm Thi Vương

I. Giới thiệu chung về cấu trúc.


- Chat theo mô hình Client-Server, sử dụng giao thức TCP.
- Đơn vị truyền thông tin là Command.
- Sử dụng các namespace chính là:
o System.Net;
o System.Net.Sockets;
o System.CompomentModel;
II. Hướng dẫn chi tiết:
1. Lập trình Server:
Bước 1: vào Visual C# tạo 1 project mới có tên là UITChatServer, có dạng là
Windows Form. Đặt tên cho class chính là frmServer. Tiếp tục trên Solution hiện tại tạo
thêm 2 project có dạng Class Library đặt tên là Command và Server.
Bước 2: vào project Command tạo 2 file có tên là Enum.cs và Command.cs.
Bước 3: mở file Enum.cs nhập vào đoạn code sau đây để định nghĩa các loại
command sẽ dùng, khi muốn update thì cần chú ý nội dụng của file này.
public enum CommandType
{
Message,// Để gửi message
ClientList,//Để gửi danh sách client online
NameExists,// thông báo tên vừa đăng nhập đã có người sử dụng
Login, //thông báo đăng nhập
Logout,//thông báo đăng xuất
}

Bươc 4: Mở file Command.cs và nhập vào đoạn code sau dùng định nghĩa 1 đối
tượng command dùng để truyền trên mạng, gồm có CommandType, SenderName,
SenderIP, TargerIP,
using System.Net;
namespace UITChat

1
{
public class Command
{
private IPAddress senderIP;// địa chỉ IP máy gửi
public IPAddress SenderIP
{
get { return senderIP; }
set { senderIP = value; }
}
private string senderName;//tên người gửi
public string SenderName
{
get { return senderName; }
set { senderName = value; }
}

private CommandType cmdType;//loại Command được gửi


public CommandType CommandType
{
get { return cmdType; }
set { cmdType = value; }
}

private IPAddress target;// địa chỉ IP máy nhận


public IPAddress Target
{
get { return target; }
set { target = value; }
}

private string commandBody;//nội dung cần gửi


public string MetaData
{
get { return commandBody; }
set { commandBody = value; }
}
// 2 contructor
public Command(CommandType type, IPAddress targetMachine, string
metaData)
{
this.cmdType = type;
this.target = targetMachine;
this.commandBody = metaData;
}
public Command(CommandType type, IPAddress targetMachine)
{
this.cmdType = type;
this.target = targetMachine;
this.commandBody = "";
}
}
}
Bươc 5: Chuyển sang project Server. Ta Reference đến project Command. Tạo 3
file là ClientManager.cs , Server.cs và EventArgs.cs

2
Bước 6: Mở file EventArgs.cs, nhập vào nội dung sau, định nghĩa các class thừa kế
từ class EventArg và các delegate để dùng khai báo các Event trong 2 file còn lại.
using System;
using System.Net;
using System.Net.Sockets;
using UITChat;//chú ý phải using đến class command
namespace Server
{
public delegate void UserOnlineHandler(object sender, UserEventArg user);
public delegate void UserLogoutHandler(object sender, UserEventArg user);
public delegate void CommandReceivedEventHandler(object sender,CommandEventArgs e);
public delegate void DisconnectedEventHandler(object sender, ClientEventArgs e);

public class UserEventArg : EventArgs


{
private string _userName;//tên người dùng
private IPAddress _ipaddress;//địa chỉ IP

public string UserName


{
get
{
return _userName;
}
set
{
_userName = value;
}
}
public UserEventArg(string Name)
{
_ipaddress = null;
_userName = Name;
}
public UserEventArg(IPAddress address, string Name)
{
_ipaddress = address;
_userName = Name;
}
}
public class CommandEventArgs : EventArgs
{
private Command command;// đối tượng Command
public Command Command
{
get { return command; }
}
public CommandEventArgs(Command cmd)
{
this.command = cmd;
}
}
public class ClientEventArgs : EventArgs
{
private Socket socket; //Socket của client
public IPAddress IP
{
get { return ((IPEndPoint)this.socket.RemoteEndPoint).Address; }
}

public int Port


{

3
get { return ((IPEndPoint)this.socket.RemoteEndPoint).Port; }
}
public ClientEventArgs(Socket clientManagerSocket)
{
this.socket = clientManagerSocket;
}
}
}

Bước 7: Mở file ClientManager.cs, ta tạo 1 class có tên là ClientManager. Trong


class này ta khai báo 1 số thuộc tính và contructor như sau:
public IPAddress IP
{
get
{
try
{
return ((IPEndPoint)this.socket.RemoteEndPoint).Address;
}
catch
{
return IPAddress.None;
}
}
}
public int Port
{
get
{
if (this.socket != null)
return ((IPEndPoint)this.socket.RemoteEndPoint).Port;
else
return -1;
}
}

private Socket socket;//socket để kết nối tới client


private string clientName = "";//tên của client
public string ClientName
{
get { return this.clientName; }
set { this.clientName = value; }
}

private NetworkStream networkStream;//luồn để ghi và đọc dữ liệu trên socket


private BackgroundWorker bwReceiver;//thread nhận
#region Constructor
public ClientManager(Socket clientSocket)
{
this.socket = clientSocket;
this.networkStream = new NetworkStream(this.socket);
this.bwReceiver = new BackgroundWorker();
this.bwReceiver.DoWork += new DoWorkEventHandler(Receive);
this.bwReceiver.RunWorkerAsync();

4
}
#endregion

Bước 8: Ta viết hàm Receive chạy trên Thread bwReceiver. Chú ý ta dùng
Encoding để chuyển dữ liệu sang kiểu dữ liệu cũ trước khi Encode ở phía máy gửi.
Đây là hàm để chạy Event DoWorkEventHandler trong BackgroudWorker. Một
class kế thừa từ Thread, giúp làm việc trên Thread đơn giản hơn.
private void Receive(object sender, DoWorkEventArgs e)
{
while (this.socket.Connected)
{
try
{
//Đọc commandtype
byte[] buffer = new byte[4];
int readBytes = this.networkStream.Read(buffer, 0, 4);
if (readBytes == 0)
break;
CommandType cmdType = (CommandType)(BitConverter.ToInt32(buffer, 0));
//Đọc kích thước của SenderIP
buffer = new byte[4];
readBytes = this.networkStream.Read(buffer, 0, 4);
if (readBytes == 0)
break;
int senderIPSize = BitConverter.ToInt32(buffer, 0);
//----------------------
//Đọc senderIP
buffer = new byte[senderIPSize];
readBytes = this.networkStream.Read(buffer, 0, senderIPSize);
if (readBytes == 0)
break;
IPAddress senderIP =
IPAddress.Parse(System.Text.Encoding.ASCII.GetString(buffer));

//Đọc kích thước của SenderName


buffer = new byte[4];
readBytes = this.networkStream.Read(buffer, 0, 4);
if (readBytes == 0)
break;
int senderNameSize = BitConverter.ToInt32(buffer, 0);
//Đọc SenderName
buffer = new byte[senderNameSize];
readBytes = this.networkStream.Read(buffer, 0, senderNameSize);
if (readBytes == 0)
break;
string senderName = System.Text.Encoding.Unicode.GetString(buffer);
//--------------------------
//Đọc kích thước của TargetIp
string cmdTarget = "";
buffer = new byte[4];
readBytes = this.networkStream.Read(buffer, 0, 4);
if (readBytes == 0)
break;
int ipSize = BitConverter.ToInt32(buffer, 0);

//Đọc TargerIp
buffer = new byte[ipSize];
readBytes = this.networkStream.Read(buffer, 0, ipSize);

5
if (readBytes == 0)
break;
cmdTarget = System.Text.Encoding.ASCII.GetString(buffer);

//Đọc kích thước của Metadata


string cmdMetaData = "";
buffer = new byte[4];
readBytes = this.networkStream.Read(buffer, 0, 4);
if (readBytes == 0)
break;
int metaDataSize = BitConverter.ToInt32(buffer, 0);

//Đọc metadata
buffer = new byte[metaDataSize];
readBytes = this.networkStream.Read(buffer, 0, metaDataSize);
if (readBytes == 0)
break;
cmdMetaData = System.Text.Encoding.Unicode.GetString(buffer);
//đóng gói thành kiểu command
Command cmd = new Command(cmdType, IPAddress.Parse(cmdTarget), cmdMetaData);
cmd.SenderIP = this.IP;
cmd.SenderName = this.ClientName;
//phát sự kiện đã nhận xong 1 command
this.OnCommandReceived(new CommandEventArgs(cmd));
}
catch
{
break;//gặp bất kỳ lỗi nào sẽ tự thoát
}
}
//phát sự kiện mất kết nối
this.OnDisconnected(new ClientEventArgs(this.socket));
//ngắt kết nối
this.Disconnect();
}

Bước 9: Ta viết hàm SendCommand để gửi 1 command đến Client mà nó quản lý.
Quá trình gửi cũng được thực hiện trên 1 Thread riêng, là 1 đối tượng
BackgroundWorker. Chú ý các hàm xử lý sự kiện cho đối tượng này được trình bày
dưới hàm SendCommand.
public void SendCommand(Command cmd)
{
if (this.socket != null && this.socket.Connected)
{
BackgroundWorker bwSender = new BackgroundWorker();
bwSender.DoWork += new DoWorkEventHandler(bwSender_DoWork);
bwSender.RunWorkerCompleted += new
RunWorkerCompletedEventHandler(bwSender_RunWorkerCompleted);
bwSender.RunWorkerAsync(cmd);
}
}

private void bwSender_RunWorkerCompleted(object sender,


RunWorkerCompletedEventArgs e)
{
//dọn rác sau khi đã gửi xong
((BackgroundWorker)sender).Dispose();

6
GC.Collect();
}

private void bwSender_DoWork(object sender, DoWorkEventArgs e)


{
Command cmd = (Command)e.Argument;
e.Result = this.SendCommandToClient(cmd);
}

//Sử dụng semaphor để tránh xung đột khi gửi


System.Threading.Semaphore semaphor = new System.Threading.Semaphore(1, 1);
private bool SendCommandToClient(Command cmd)
{
try
{
semaphor.WaitOne();
//Type
byte[] buffer = new byte[4];
buffer = BitConverter.GetBytes((int)cmd.CommandType);
this.networkStream.Write(buffer, 0, 4);
this.networkStream.Flush();

//Sender IP
byte[] senderIPBuffer = Encoding.ASCII.GetBytes(cmd.SenderIP.ToString());
buffer = new byte[4];
buffer = BitConverter.GetBytes(senderIPBuffer.Length);
this.networkStream.Write(buffer, 0, 4);
this.networkStream.Flush();
this.networkStream.Write(senderIPBuffer, 0, senderIPBuffer.Length);
this.networkStream.Flush();

//Sender Name
byte[] senderNameBuffer =
Encoding.Unicode.GetBytes(cmd.SenderName.ToString());
buffer = new byte[4];
buffer = BitConverter.GetBytes(senderNameBuffer.Length);
this.networkStream.Write(buffer, 0, 4);
this.networkStream.Flush();
this.networkStream.Write(senderNameBuffer, 0, senderNameBuffer.Length);
this.networkStream.Flush();

//Target
byte[] ipBuffer = Encoding.ASCII.GetBytes(cmd.Target.ToString());
buffer = new byte[4];
buffer = BitConverter.GetBytes(ipBuffer.Length);
this.networkStream.Write(buffer, 0, 4);
this.networkStream.Flush();
this.networkStream.Write(ipBuffer, 0, ipBuffer.Length);
this.networkStream.Flush();

//Meta Data.
if (cmd.MetaData == null || cmd.MetaData == "")
cmd.MetaData = "\n";
byte[] metaBuffer = Encoding.Unicode.GetBytes(cmd.MetaData);
buffer = new byte[4];
buffer = BitConverter.GetBytes(metaBuffer.Length);
this.networkStream.Write(buffer, 0, 4);
this.networkStream.Flush();

7
this.networkStream.Write(metaBuffer, 0, metaBuffer.Length);
this.networkStream.Flush();

semaphor.Release();
return true;
}
catch
{
semaphor.Release();
return false;
}
}
Bước 10: Tiếp tục viết thêm hàm Disconnect để ngắt kết nối, đóng socket và các
Event .
public bool Disconnect()
{
if (this.socket != null && this.socket.Connected)
{
try
{
this.socket.Shutdown(SocketShutdown.Both);
this.socket.Close();
return true;
}
catch
{
return false;
}
}
else
return true;
}

public event CommandReceivedEventHandler CommandReceived;


protected virtual void OnCommandReceived(CommandEventArgs e)
{
if (CommandReceived != null)
CommandReceived(this, e);
}

public event DisconnectedEventHandler Disconnected;


protected virtual void OnDisconnected(ClientEventArgs e)
{
if (Disconnected != null)
Disconnected(this, e);
}

Bước 11: Chuyển qua file Server.cs và lập trình các chức năng cơ bản cho 1 server
chat. Bao gồm forward tin nhắn, quản lý các client, và các hàm hỗ trợ.
Trước tiên là khai báo các thuộc tính như sau:
private List<ClientManager> listClient;//danh sach các client online
private Socket listenerSocket;//socket để nghe các kết nối mới
private int port = 1024;//port cố định để nhận Command

8
private IPAddress serverIp;//địa chỉ IP của server
private BackgroundWorker bwServer;//Thread chính của server

Và Contructor.
public Server()
{
serverIp = Dns.GetHostAddresses(Dns.GetHostName())[0];
listClient = new List<ClientManager>();
}

Bước 12: Ta viết hàm chạy Server. Chú ý server chạy trên Thread độc lập dựa vào
đối tượng BackgroundWorker. Có 1 số hàm private kèm theo để quản lý listClient như
thêm, xóa các client khi mất kết nối, login hay logout.
public void Start()
{
bwServer = new BackgroundWorker();
bwServer.DoWork += new DoWorkEventHandler(bwServer_DoWork);
bwServer.RunWorkerAsync();
}

private void bwServer_DoWork(object sender, DoWorkEventArgs e)


{
listenerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream,
ProtocolType.Tcp);
IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Any, port);
listenerSocket.Bind(serverEndPoint);
listenerSocket.Listen(200);
while (true)
{
//tao 1 ClientManager mới bất kỳ khi nào có kêt nối mới
ClientManager newClient = new ClientManager(listenerSocket.Accept());
newClient.CommandReceived += new CommandReceivedEventHandler(newClient_CommandReceived);
newClient.Disconnected += new DisconnectedEventHandler(newClient_Disconnected);
//xóa client trong list nếu IP của nó đã được lưu trong list
this.RemoveClientManager(newClient.IP);
//thêm Client mới vào list
listClient.Add(newClient);
}
}
//Dọn dẹp, xóa client trong list nếu server mất kêt nối với nó và gửi clientlist mới
private void newClient_Disconnected(object sender, ClientEventArgs e)
{
ClientManager client = (ClientManager)sender;
listClient.Remove(client);
UserEventArg userEvent = new UserEventArg(client.ClientName);
SendUserOnlineList();
}

private void newClient_CommandReceived(object sender, CommandEventArgs e)


{
ClientManager client = (ClientManager)sender;
Command cmd = e.Command;
switch (cmd.CommandType)
{
// khi đang nhập
case CommandType.Login:
{

9
//đặt tên cho Client mới bằng tên đã đăng nhập
string name = SetManagerName(cmd.SenderIP, cmd.MetaData);
//kiểm tra xem tên có trùng với tên đã có không
if (!IsNameExists(cmd.SenderIP, name))
{
//nêu ko thi gửi clientlist mới cho tất cả các máy
SendUserOnlineList();
}
else
{
//nếu có thì gửi thông báo là tên đã có người khác dùng
Command sendcmd = new Command(CommandType.NameExists, cmd.SenderIP);
sendcmd.SenderIP = serverIp;
sendcmd.SenderName = "server";
client.SendCommand(sendcmd);
}
break;
}
//khi đang xuât
case CommandType.Logout:
{
SendUserOnlineList();
break;
}
//forward tin nhắn
case CommandType.Message:
{
string targetname = cmd.MetaData.Split(new char[] { '|' })[0];
SendCommandToTarget(targetname, cmd);
break;
}
}

}
//Đặt tên cho Client
private string SetManagerName(IPAddress remoteClientIP, string nameString)
{
int index = this.IndexOfClient(remoteClientIP);
if (index != -1)
{
string name = nameString.Split(new char[] { ':' })[0];
this.listClient[index].ClientName = name;
return name;
}
return "";
}
//Lấy vị trí của Client cho địa chỉ là IP trong list
private int IndexOfClient(IPAddress ip)
{
int index = -1;
foreach (ClientManager cMngr in listClient)
{
index++;
if (cMngr.IP.Equals(ip))
return index;
}
return -1;
}
//Xóa 1 client có địa chỉ là IP
private bool RemoveClientManager(IPAddress ip)
{
lock (this)
{
int index = this.IndexOfClient(ip);

10
if (index != -1)
{
string name = listClient[index].ClientName;
listClient.RemoveAt(index);
SendUserOnlineList();
return true;
}
return false;
}
}
//kiểm tra xem tên đăng nhập đã có chưa
private bool IsNameExists(IPAddress remoteClientIP, string nameToFind)
{
foreach (ClientManager mngr in this.listClient)
if (mngr.ClientName == nameToFind && !mngr.IP.Equals(remoteClientIP))
return true;
return false;
}

Bước 13: Viết 1 số hàm public quan trọng để điều kiển Server.
//gửi command cho tất cả client trong list
public void BroadCastCommand(Command cmd)
{
foreach (ClientManager mngr in this.listClient)
mngr.SendCommand(cmd);
}
//gửi command cho targetname bằng cách tìm ClientManager trong list
public void SendCommandToTarget(string targetname, Command cmd)
{
foreach (ClientManager mngr in this.listClient)
if (mngr.ClientName.Equals(targetname))
{
cmd.Target = mngr.IP;
mngr.SendCommand(cmd);
return;
}
}
//Gửi danh sách client Online cho tất cả các máy online trên mạng
public void SendUserOnlineList()
{
string userOnline = "";
foreach (ClientManager c in listClient)
{
userOnline += "|" + c.ClientName + ":" + c.IP.ToString();
}
Command cmd = new Command(CommandType.ClientList, this.serverIp, userOnline);
cmd.SenderIP = this.serverIp;
cmd.SenderName = "server";
BroadCastCommand(cmd);
}
//đóng Server
public void Stop()
{
foreach (ClientManager c in listClient)
{
c.Disconnect();
}

11
}
Bước 14: Bước cuối cùng để hoàn tất 1 server chat đơn giản. Mở file frmServer.cs.
Về phần giao diện form ta có thể tự do thích vẽ gì cũng được. Quan trọng là xử lý 2 xự
kiện chính cho form đó là Form_Load và Form_Closing. Tạo 1 đối tượng Server. Gọi
hàm Start() ở Form_Load và hàm stop ở Form_Closing. Ta có thể thêm nút Start và
Stop lên form.

2. Lập trình Client:


Bước 1: Tương tự bên Server, ta tạo 1 project mới có tên là UITChatClient dạng
Windows Form. Trong cùng Solution đó ta tạo thêm 1 project mới có dạng Class
Library tên là ChatClient, đồng thời add thêm project Command của Server vì client và
server phải đồng bộ về Command. Thêm 1 file có tên là EventArgs.cs cho project
ChatClient.

Bước 2: Mở file EventArgs.cs của ChatClient và tiến hành định nghĩa 1 số


EventArg, delegate cho ChatClient.
//Nhận được Command
public delegate void CommandReceivedEventHandler(object sender, CommandEventArgs e);
//Mất kết nối server
public delegate void DisconnectedEventHandler(object sender, EventArgs e);
//Kết nối thành công
public delegate void ConnectingSuccessedEventHandler(object sender, EventArgs e);
//kết nối bị lỗi
public delegate void ConnectingFailedEventHandler(object sender, EventArgs e);

public class CommandEventArgs : EventArgs


{
private Command command;
public Command Command
{
get { return command; }
}

public CommandEventArgs(Command cmd)


{
this.command = cmd;
}
}

Bước 3: Mở file ChatClient.cs, tạo 1 class ChatClient và tiến hành cài đặt cho class
này.

Trước tiên là định nghĩa 1 số thuộc tính và Contructor.


private string username;//tên đăng nhập
private IPAddress localAddress;//địa chỉ IP của máy hiện hành
private IPAddress remoteAddress;//địa chỉ IP của Server
private int port = 1024;//port kết nối với Server

12
private BackgroundWorker bwReceiver;//thread nhận command
private BackgroundWorker bwConnector;//thread kết nối
private NetworkStream networkStream;//Luồn để ghi,đọc dữ liệu
private Socket clientSocket;

public string UserName


{
get
{
return username;
}
set
{
username = value;
}
}
public IPAddress LocalAddress
{
get
{
return localAddress;
}
}
//Contructor
public ChatClient(string Username)
{
username = Username;
localAddress = Dns.GetHostAddresses(Dns.GetHostName())[0];
}
Bước 4: Viết hàm để nhận và gửi Command cho ChatClient tương tự như bên phía
Server đã trình bày ở trên. Có thể tóm tắt lại như sau:

Nhận Command, Client cần phải kết nối với Server, tạo socket kết nối với Server rồi
mới thực hiện nhận Command:
public void ConnectTo(IPAddress address)
{
remoteAddress = address;
bwConnector = new BackgroundWorker();
bwConnector.DoWork += new DoWorkEventHandler(bwConnector_DoWork);
bwConnector.RunWorkerCompleted += new
RunWorkerCompletedEventHandler(bwConnector_RunWorkerCompleted);
bwConnector.RunWorkerAsync();
}
private void bwConnector_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
//kiểm tra xem kết nối có thành công không và phát ra xự kiện tương ứng
if ((bool)e.Result == false)
OnConnectingFailed(new EventArgs());
else
OnConnectingSuccessed(new EventArgs());
((BackgroundWorker)sender).Dispose();
}

private void bwConnector_DoWork(object sender, DoWorkEventArgs e)

13
{
try
{
//tạo socket
this.clientSocket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
IPEndPoint serverEP = new IPEndPoint(remoteAddress, port);
//kết nối tới IP của server
this.clientSocket.Connect(serverEP);
e.Result = true;
//tạo 1 luồn để nhận và ghi dữ liệu lên socket
this.networkStream = new NetworkStream(this.clientSocket);
//tạo 1 thread để nhận Command
this.bwReceiver = new BackgroundWorker();
this.bwReceiver.WorkerSupportsCancellation = true;
this.bwReceiver.DoWork += new DoWorkEventHandler(Receive);
this.bwReceiver.RunWorkerAsync();
}
catch
{
e.Result = false;//kết nối ko thành công
}
}
private void Receive(object sender, DoWorkEventArgs e)
{
Tương tự bên phía Server
}
Gửi Command:
public void SendCommandToDestination(Command cmd)
{
if (this.clientSocket != null && this.clientSocket.Connected)
{
BackgroundWorker bwSender = new BackgroundWorker();
bwSender.DoWork += new DoWorkEventHandler(bwSender_DoWork);
bwSender.RunWorkerCompleted += new
RunWorkerCompletedEventHandler(bwSender_RunWorkerCompleted);
bwSender.RunWorkerAsync(cmd);
}
}
private void bwSender_DoWork(object sender, DoWorkEventArgs e)
{
Command cmd = (Command)e.Argument;
e.Result = this.SendCommand(cmd);
}
private void bwSender_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
((BackgroundWorker)sender).Dispose();
GC.Collect();
}
System.Threading.Semaphore semaphor = new System.Threading.Semaphore(1, 1);
private bool SendCommand(Command cmd)
{
Tương tự như bên Server
}

14
Bước 5: Thiết kế form để Chat.Chuyển qua project UITChatClient. Reference đến 2
project còn lại.

Tạo thêm 1 form có tên là frmLogin. Thiết kế form


Login như hình vẽ. Chú ý:

- Textbox Server IP tên là txtServerIP.


- Textbox User Name tên là txtUsername.
- Button Login có tên là btnLogin.
- Button Cancel có tên là btnCancel.
- Viết code cho frmLogin khá đơn giản như sau:
private string ipString;//chuỗi IP của server
private string username;//tên đăng nhập
private bool isCancel = true;//kiểm tra xem có bấm Cancel không
//các property
public bool IsCancel
{
get
{
return isCancel;
}
}
public string IPString
{
get
{
return ipString;
}
}
public string UserName
{
get
{
return username;
}
}
//contructor
public frmLogin()
{
InitializeComponent();
}

private void btnLogin_Click(object sender, EventArgs e)


{
username = txtUsername.Text.Trim();
ipString = txtServerIP.Text.Trim();
isCancel = false;
this.Close();
}

private void btnCancel_Click(object sender, EventArgs e)


{

15
this.Close();
}

Bước 6: Tạo form frmChat.


Thiết kế giao diện và đặt tên như hình vẽ.
Form này có chức năng hiện thị tên tất cả các
client đang online và có thể chat với bất kỳ client
nào bằng cách click vào tên client đó và bấm vào
nút chat.
- Đầu tiên để tránh lỗi “Cross-thread” ta tạo 2
delegate cho việc update lại list useronline và chèn
Message vào form frmInstantChat tương ứng.

public delegate void insertMessage(string Targetname,string message);


public delegate void updateUserList(string[] UserName);
Và 2 hàm tương ứng như sau:
private void InsertMessage(string Targetname, string Message)
{
frmInstantChat IM=null;
//duyệt qua tất cả các form đang mở
foreach (Form form in Application.OpenForms)
{
//gán cho form đó thành frmIntantChat
IM = form as frmInstantChat;
if (IM == null)
continue;
if (IM.TargetName == Targetname)//kiểm tra targetname đúng khi
form chat đã được mở sẵn
{
break;
}
}
if (IM == null)
{
IM = new frmInstantChat(client.UserName,Targetname, this);
IM.Show();
}
//chèn message vào form
IM.InsertMessage(Targetname,Message);
IM.Focus();
}

private void UpdateUserList(string[] UserName)


{
lsbUserOnline.Items.Clear();
foreach (string s in UserName)

16
if (s.Trim()!="" && s!=client.UserName)
lsbUserOnline.Items.Add(s);
}

- Tiếp theo ta tạo 1 đối tượng ChatClient, trong Form_Load ta sẽ cho hiện
frmLogin để lấy thông tin, khởi tạo đối tượng ChatClient và kết nối với Server như
sau:
formlogin = new frmLogin();
formlogin.ShowDialog();
if (formlogin.IsCancel)
{
this.Close();
return;
}
try
{
//tạo đối tượng ChatClient
client = new ChatClient(formlogin.UserName);
//gán các xự kiện
client.CommandReceived += new
CommandReceivedEventHandler(client_CommandReceived);
client.ConnectingFailed += new
ConnectingFailedEventHandler(client_ConnectingFailed);
client.Disconnected += new DisconnectedEventHandler(client_Disconnected);
client.ConnectingSuccessed += new
ConnectingSuccessedEventHandler(client_ConnectingSuccessed);
//kết nối với server
client.ConnectTo(IPAddress.Parse(formlogin.IPString));
}
catch
{
MessageBox.Show("Erro!!!");//báo lỗi nếu ip server bị sai
Close();
}
- Tiếp theo ta xử lý các tình huống Disconnected, ConnectingSuccessed,
ConnectedFailed như sau:
private void client_ConnectingSuccessed(object sender, EventArgs e)
{
//gửi tên đăng nhập cho server và thông báo đã login
Command cmd = new Command(CommandType.Login, client.RemoteAddress,
formlogin.UserName);
cmd.SenderIP = client.LocalAddress;
cmd.SenderName = client.UserName;
client.SendCommandToDestination(cmd);
}

private void client_Disconnected(object sender, EventArgs e)


{
MessageBox.Show("Disconnected");
this.Close();
}

private void client_ConnectingFailed(object sender, EventArgs e)

17
{
MessageBox.Show("Connecting failed");
this.Close();
}
- Phần quan trọng nhất là xử lý cho tính huống CommandReceive. Đối với mỗi
loại CommandType ta có các cách xử lý khác nhau. Sau đây là cách xử lý cho
3 loại CommandType cơ bản nhất cho chương trình Chat.
Command cmd = e.Command;
string[] metadata = cmd.MetaData.Split(new char[] { '|' });

switch (cmd.CommandType)
{
case CommandType.ClientList:
{
//Cập nhật lại ClientList
if (!this.InvokeRequired)
this.Invoke(new updateUserList(UpdateUserList),new
object[]{metadata});
break;
}

case CommandType.Message:
{
int t = cmd.MetaData.IndexOf('|');
string message = cmd.MetaData.Substring(t+1);
string name = cmd.SenderName;
//gọi hàm InserMessage thông qua delegate
this.Invoke(new insertMessage(InsertMessage), new object[] {
name, message });
break;
}
case CommandType.NameExists:
{
//nếu tên đã tồn tại rùi thì sẽ yêu cầu nhập lại tên khác
MessageBox.Show("Name is Exist");
formlogin = new frmLogin();
formlogin.ShowDialog();
client.UserName = formlogin.UserName;
Command logincmd = new Command(CommandType.Login,
client.RemoteAddress, client.UserName);
logincmd.SenderIP = client.LocalAddress;
logincmd.SenderName = client.UserName;
client.SendCommandToDestination(logincmd);
break;
}
}
- Cuối cùng ta gọi hàm client.Disconnect() trong hàm Form_Closing

18
Bước 7: Tạo thêm 1 form có tên là frmInstantChat.Giao diện và cách đặt tên
tương tự như hình vẽ. Form này
cần có các thuộc tính là
UserName (tên người dùng),
TargetName(tên bạn chat),
MainForm(form chính) và 1
hàm public InsertMessage dùng
để chèn thêm 1 message vào
RichTextBox và có thể gọi từ
form khác.
Cách viết code như sau:

private string targetname;


private string username;
private frmClient mainform;
public string TargetName
{
get
{
return targetname;
}
}
public frmInstantChat(string Username,string Targetname,frmClient
Client)
{
InitializeComponent();
targetname = Targetname;
username = Username;
mainform = Client;
}
//hàm chèn 1 chuỗi vào RichTextBox
public void InsertMessage(string name, string Message)
{
rtbOutPut.AppendText(name + ":" + Message);
rtbOutPut.AppendText(Environment.NewLine);
}

private void btnSend_Click(object sender, EventArgs e)


{
if (txtInput.Text.Trim() != "")
mainform.sendMessage(targetname, txtInput.Text.Trim());
InsertMessage(username, txtInput.Text);
txtInput.Text = "";

19
}

III. Kết luận:


- Để làm 1 chương trình chat theo mô hình Client-Server khá đơn giản.
- Theo mô hình này rất có lợi cho việc nâng cấp các tính năng cao cấp hơn như
Send File, Share Photo, chơi game.
- Những giao tiếp dưới dạng thông điệp nhỏ muốn gửi giữa 2 máy với mô hình
này chỉ cần cập nhập thêm CommandType và xử lý CommandType này sau
khi nhận xong là đươc.

Mong nhận được sự góp ý của bạn.

Mọi thắc mắc hay góp ý xin vui lòng


mail về địa chỉ: namlh@escuit.com

20

You might also like