Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 20 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
20
Dung lượng
399,99 KB
Nội dung
1 Đạ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 2 { 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 3 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 { 4 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(); 5 } #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); 6 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(); 7 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(); 8 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 9 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: { 10 //đặ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); [...]... } 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... 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... ((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... 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);... 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:... 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 = 10 24;//port kết nối với Server 12 private private private private... 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);... 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... 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 . 1 Đạ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 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. txtInput.Text); txtInput.Text = ""; 20 } 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