Xây dựng chương trình FTP client đơn giản

    0

    Trong bài học này chúng ta sẽ cùng xây dựng một chương trình FTP client đơn giản sử dụng các kỹ thuật lập trình socket đã biết trong .NET.

    Chương trình chỉ hỗ trợ thực hiện một số lệnh cơ bản sau:

    Kết nối server, đăng nhập, đăng xuất, xem danh sách thư mục, tạo thư mục, xóa thư mục, đổi thư mục hiện hành, xem thư mục hiện hành, lấy danh sách file từ thư mục, tải file về client, tải file lên server.

    Thực hành: xây dựng FTP client

    Bước 1. Tạo project đặt tên là FtpClient thuộc loại ConsoleApp

    Bước 2. Thêm class FtpClient trong file FtpClient.cs

    Cấu trúc thư mục giờ sẽ có dạng như sau:

    Bước 3. Viết code cho FtpClient như sau:

    using System;
    using System.ComponentModel;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    namespace FtpProtocol {
        public class FtpClient {
            #region Properties
            TcpClient _controlSocket;
            StreamWriter _writer;
            StreamReader _reader;
            BindingList<string> _responses = new BindingList<string>();
            BindingList<string> _commands = new BindingList<string>();
            public string Command {
                get => _commands.Last();
                private set {
                    if (!string.IsNullOrEmpty(value)) {
                        _commands.Add(value);
                    }
                }
            }
            public string Response {
                get => _responses.Last();
                private set {
                    if (!string.IsNullOrEmpty(value)) {
                        _responses.Add(value);
                    }
                }
            }
            public IPAddress Address { get; set; }
            public string User { get; set; }
            public string Password { get; set; }
            public bool Connected => _controlSocket.Connected;
            public bool LoggedOn { get; private set; }
            #endregion        
            #region Commands        
            // Các lệnh quản lý kênh điều khiển
            /// <summary>
            /// Kết nối
            /// </summary>
            public void Connect() {
                _responses.Clear();
                _commands.Clear();
                _controlSocket = new TcpClient();
                if (!_controlSocket.Connected) {
                    _controlSocket.Connect(Address, 21);
                    if (_controlSocket.Connected) {
                        _responses.ListChanged -= Responses_ListChanged;
                        _responses.ListChanged += Responses_ListChanged;
                        _commands.ListChanged -= Commands_ListChanged;
                        _commands.ListChanged += Commands_ListChanged;
                        _reader = new StreamReader(_controlSocket.GetStream());
                        _writer = new StreamWriter(_controlSocket.GetStream()) { AutoFlush = true };                    
                        //StringBuilder sb = new StringBuilder();
                        Response = _reader.ReadLine();
                        if (Response.StartsWith("220-")) {
                            while (true) {
                                Response = _reader.ReadLine();
                                if (Response.StartsWith("220 ")) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
            /// <summary>
            /// Đăng nhập
            /// </summary>
            public void Login() {
                Command = string.Format("USER {0}", User);
                _writer.WriteLine(Command);
                Response = _reader.ReadLine();
                if (Response.StartsWith("331 ")) {
                    Command = string.Format("PASS {0}", Password);
                    _writer.WriteLine(Command);
                    Response = _reader.ReadLine();
                    LoggedOn = true;
                }
            }
            /// <summary>
            /// Thoát
            /// </summary>
            public void Logout() {
                if (_controlSocket.Connected && LoggedOn) {
                    Command = "QUIT";
                    _writer.WriteLine(Command);
                    Response = _reader.ReadLine();
                    if (Response.StartsWith("221 ")) {
                        LoggedOn = false;
                        _controlSocket.Close();
                    }
                }
            }
            // các lệnh đơn giản (chỉ dùng kênh điều khiển, không dùng kênh dữ liệu)
            /// <summary>
            /// Xóa thư mục
            /// </summary>
            /// <param name="dir"></param>
            public void RemoveDirectory(string dir) {
                Command = string.Format("RMD {0}", dir);
                _writer.WriteLine(Command);
                Response = _reader.ReadLine();
            }
            /// <summary>
            /// Tạo thư mục
            /// </summary>
            /// <param name="dir"></param>
            public void CreateDirectory(string dir) {
                Command = string.Format("MKD {0}", dir);
                _writer.WriteLine(Command);
                Response = _reader.ReadLine();
            }
            /// <summary>
            /// Đổi thư mục hiện hành
            /// </summary>
            /// <param name="dir"></param>
            public void ChangeDirectory(string dir) {
                Command = string.Format("CWD {0}", dir);
                _writer.WriteLine(Command);
                Response = _reader.ReadLine();
            }
            /// <summary>
            /// Xem thư mục hiện hành
            /// </summary>
            public void CurrentDirectory() {
                Command = "PWD";
                _writer.WriteLine(Command);
                Response = _reader.ReadLine();
            }
            // Các lệnh phức tạp (dùng kênh dữ liệu)
            /// <summary>
            /// Lấy danh sách file
            /// </summary>
            public void List() {
                Command = "PASV";
                _writer.WriteLine(Command);
                Response = _reader.ReadLine();
                if (Response.StartsWith("227 ")) // 227 (a,b,c,d,x,y)
                {
                    IPEndPoint server_data_endpoint = GetServerEndpoint(Response);
                    Command = "LIST";
                    _writer.WriteLine(Command);
                    TcpClient data_channel = new TcpClient();
                    data_channel.Connect(server_data_endpoint);
                    Response = _reader.ReadLine();
                    if (Response.StartsWith("150 ")) {
                        StreamReader sr = new StreamReader(data_channel.GetStream());
                        Response = sr.ReadToEnd();
                        Response = _reader.ReadLine();
                        if (Response.StartsWith("226 ")) {
                            data_channel.Close();
                        }
                    }
                }
            }
            /// <summary>
            /// Tải file lên (thụ động)
            /// </summary>
            /// <param name="filename"></param>
            public void Upload(string filename) {
                Command = "PASV";
                _writer.WriteLine(Command);
                Response = _reader.ReadLine();
                if (Response.StartsWith("227 ")) {
                    IPEndPoint server_data_endpoint = GetServerEndpoint(Response);
                    Command = string.Format("STOR {0}", filename); // storage
                    _writer.WriteLine(Command);
                    TcpClient data_channel = new TcpClient();
                    data_channel.Connect(server_data_endpoint);
                    Response = _reader.ReadLine();
                    if (Response.StartsWith("150 ")) {
                        NetworkStream ns = data_channel.GetStream();
                        int blocksize = 1024;
                        byte[] buffer = new byte[blocksize];
                        int byteread = 0;
                        lock (this) {
                            FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read);
                            while (true) {
                                byteread = fs.Read(buffer, 0, blocksize);
                                ns.Write(buffer, 0, byteread);
                                if (byteread == 0) {
                                    break;
                                }
                            }
                            ns.Flush();
                            ns.Close();
                        }
                        Response = _reader.ReadLine();
                        if (Response.StartsWith("226 ")) {
                            data_channel.Close();
                        }
                    }
                    data_channel.Close();
                }
            }
            /// <summary>
            /// Tải file xuống (thụ động)
            /// </summary>
            /// <param name="filename"></param>
            public void Download(string filename) {
                Command = "PASV";
                _writer.WriteLine(Command);
                Response = _reader.ReadLine();
                if (Response.StartsWith("227 ")) { // 227 (a,b,c,d,x,y)
                    IPEndPoint server_data_endpoint = GetServerEndpoint(Response);
                    Command = string.Format("RETR {0}", filename); // retrieve
                    _writer.WriteLine(Command);
                    TcpClient data_channel = new TcpClient();
                    data_channel.Connect(server_data_endpoint);
                    Response = _reader.ReadLine();
                    if (Response.StartsWith("150 ")) {
                        NetworkStream ns = data_channel.GetStream();
                        int blocksize = 1024;
                        byte[] buffer = new byte[blocksize];
                        int byteread = 0;
                        lock (this) {
                            FileStream fs = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.Write);
                            while (true) {
                                byteread = ns.Read(buffer, 0, blocksize);
                                fs.Write(buffer, 0, byteread);
                                if (byteread == 0) {
                                    break;
                                }
                            }
                            fs.Flush();
                            fs.Close();
                        }
                        Response = _reader.ReadLine();
                        if (Response.StartsWith("226 ")) {
                            data_channel.Close();
                        }
                    }
                }
            }
            #endregion
            #region Helper
            IPEndPoint GetServerEndpoint(string response) {
                // 227 (a,b,c,d,x,y)
                int start = response.IndexOf('(');
                int end = response.IndexOf(')');
                string substr = response.Substring(start + 1, end - start - 1);
                string[] octets = substr.Split(',');
                int port = int.Parse(octets[4]) * 256 + int.Parse(octets[5]);
                IPAddress address = new IPAddress(new byte[] { byte.Parse(octets[0]), byte.Parse(octets[1]), byte.Parse(octets[2]), byte.Parse(octets[3]) });
                return new IPEndPoint(address, port);
            }
            #endregion
            #region Logging
            public Action<string> ResponseListChangedHandler { get; set; }
            public Action<string> CommandListChangedHandler { get; set; }
            private void Responses_ListChanged(object sender, ListChangedEventArgs e) {
                if (e.ListChangedType == ListChangedType.ItemAdded) {
                    string response = _responses[e.NewIndex];
                    ResponseListChangedHandler?.Invoke(response);
                }
            }
            private void Commands_ListChanged(object sender, ListChangedEventArgs e) {
                if (e.ListChangedType == ListChangedType.ItemAdded) {
                    string command = _commands[e.NewIndex];
                    CommandListChangedHandler?.Invoke(command);
                }
            }
            #endregion
        }
    }

    Bước 4. Viết code cho Program.cs như sau:

    using System;
    using System.Net;
    namespace FtpProtocol {
        class Program {
            static void Help() {
                Console.WriteLine("Supported commands");
                Console.WriteLine("Account\t\tConnect\t\tLogin\t\tLogout\t\tQuit");
                Console.WriteLine("Dir\t\tMkDir\t\tRmDir\t\tCd\t\tCr");
                Console.WriteLine("Upload\t\tDownload");
                Console.WriteLine("--------------------------------------------");
            }
            static void Reset(ref FtpClient client) {
                client = new FtpClient {
                    ResponseListChangedHandler = s => { Console.WriteLine("S> {0}", s); },
                    CommandListChangedHandler = s => Console.WriteLine("C> {0}", s)
                };
                Console.Write("User: ");
                client.User = Console.ReadLine();
                Console.Write("Password: ");
                client.Password = Console.ReadLine();
                Console.Write("IP: ");
                client.Address = IPAddress.Parse(Console.ReadLine());
            }
            static void Main(string[] args) {
                Console.OutputEncoding = System.Text.Encoding.UTF8;
                Console.InputEncoding = System.Text.Encoding.UTF8;
                Console.WriteLine("Simple FTP client by MYPE. Type Help to get supported commands.");
                FtpClient client = new FtpClient();
                bool quit = false;
                string file, folder;
                while (!quit) {
                    Console.Write("fpt> ");
                    string cmd = Console.ReadLine();
                    try {
                        switch (cmd.ToUpper()) {
                            case "ACCOUNT":
                                Reset(ref client);
                                break;
                            case "QUIT":
                                quit = true;
                                break;
                            case "HELP":
                                Help();
                                break;
                            case "CONNECT":
                                client.Connect();
                                break;
                            case "LOGIN":
                                client.Login();
                                break;
                            case "LOGOUT":
                                client.Logout();
                                break;
                            case "DIR":
                                client.List();
                                break;
                            case "CR":
                                client.CurrentDirectory();
                                break;
                            case "CD":
                                Console.Write(">Go to folder: ");
                                folder = Console.ReadLine();
                                client.ChangeDirectory(folder);
                                break;
                            case "MKDIR":
                                Console.Write(">New folder name: ");
                                folder = Console.ReadLine();
                                client.CreateDirectory(folder);
                                break;
                            case "RMDIR":
                                Console.Write(">Folder to remove: ");
                                folder = Console.ReadLine();
                                client.RemoveDirectory(folder);
                                break;
                            case "DOWNLOAD":
                                Console.Write(">File name: ");
                                file = Console.ReadLine();
                                client.Download(file);
                                break;
                            case "UPLOAD":
                                Console.Write(">File name: ");
                                file = Console.ReadLine();
                                client.Upload(file);
                                break;
                            default:
                                Console.WriteLine("Unknown command");
                                break;
                        }
                    } catch (Exception e) {
                        Console.WriteLine(e.Message);
                    }
                }
            }
        }
    }

    Chạy thử nghiệm chương trình

    Giờ bạn có thể chạy thử chương trình ở chế độ debug. Kết quả chạy chương trình như sau:

    Bạn có thể chạy lệnh Help để xem các lệnh FTP được hỗ trợ:

    Cấu hình FTP server Filezilla

    Để sử dụng client vừa viết, bạn cần chạy một FTP server. Đơn giản nhất là cài đặt bộ server XAMPP và sử dụng luôn FTP Server cài đặt sẵn trong đó.

    Sau khi chạy FileZilla server, bạn click vào nút Admin để mở chương trình quản trị server:

    Ấn OK để kết nối với FTP server đang hoạt động. Bạn sẽ chuyển sang giao diện quản trị server:

    Chọn menu Edit -> Users để mở giao diện quản lý người dùng:

    Tại giao diện này, bạn hãy tạo mới một tài khoản và đặt mật khẩu cho nó.

    Tiếp theo chuyển sang node Shared folders:

    Ở đây bạn thêm vào một thư mục trên máy cục bộ và cấp các quyền tương ứng.

    Ấn OK để hoàn tất cấu hình.

    Từ đây, client có thể sử dụng tài khoản vừa tạo để quản lý thư mục bạn chia sẻ.

    Chạy client tự tạo với FileZilla server

    Đến đây bạn đã có thể chạy thử client vừa viết với FileZilla server vừa cấu hình.

    Bước 1. Chạy lệnh Account để đặt tài khoản vào client

    Bước 2. Chạy lệnh connect để kết nối với server

    Bước 3. Chạy lệnh login để đăng nhập vào server

    Bước 4. Chạy lệnh Dir để xem các file và thư mục được chia sẻ

    Đến đây, bạn có thể thực hiện các lệnh làm việc với thư mục khác.

    Lưu ý rằng, các lệnh làm việc với thư mục là lệnh của người dùng. Nó không phải là lệnh của FTP. Mỗi lệnh của người dùng có thể tương ứng với 1 lệnh FTP, nhưng cũng có thể tương ứng với một chuỗi lệnh FTP phức tạp.

    Các lệnh người dùng được chúng ta tạo ra gần giống với lệnh của DOS / Shell.

    Nếu muốn kết thúc phiên làm việc và ngắt kết nối, bạn có thể sử dụng lệnh Logout. Nếu client không làm gì thì sau một khoảng thời gian, server cũng chủ động ngắt kết nối của kênh điều khiển.

    Theo dõi
    Thông báo của
    guest

    0 Thảo luận
    Phản hồi nội tuyến
    Xem tất cả bình luận