Đạ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 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(); } 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 //Mất kết nối server public delegate void //Kết nối thành công public delegate void //kết nối bị lỗi public delegate void CommandReceivedEventHandler(object sender, CommandEventArgs e); DisconnectedEventHandler(object sender, EventArgs e); ConnectingSuccessedEventHandler(object sender, EventArgs e); 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 private private private string username;//tên đăng nhập IPAddress localAddress;//địa chỉ IP của máy hiện hành IPAddress remoteAddress;//địa chỉ IP của Server int port = 1024;//port kết nối với Server 12 private private private private BackgroundWorker bwReceiver;//thread nhận command BackgroundWorker bwConnector;//thread kết nối NetworkStream networkStream;//Luồn để ghi,đọc dữ liệu 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ỉ:
[email protected] 20