Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

отчет лаба 3 Гаранин

.docx
Скачиваний:
0
Добавлен:
12.02.2026
Размер:
933.11 Кб
Скачать

ФЕДЕРАЛЬНОЕ АГЕНСТВО ВОЗДУШНОГО ТРАНСПОРТА

(РОСАВИАЦИЯ)

ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ БЮДЖЕТНОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ОБРАЗОВАНИЯ

«МОСКОВСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ ГРАЖДАНСКОЙ АВИАЦИИ» (МГТУ ГА)

Кафедра вычислительных машин, комплексов, сетей и систем.

Лабораторная работа защищена с оценкой ____________________

____________________

(подпись преподавателя, дата)

ЛАБОРАТОРНАЯ РАБОТА №3

по дисциплине «Программирование сетевых приложений».

Тема: «Реализация FTP-клиента.»

Выполнила студентка группы ИС221

Магальник Екатерина Борисовна

Руководитель: Гаранин Сергей Александрович

МОСКВА – 2025

Цель работы.

Приобретение практических навыков работы с протоколом прикладного уровня FTP путем разработки упрощенного FTP-клиента. Изучение моделей взаимодействия "клиент-сервер" и управления соединением (управляющее и соединение для данных).

Задание на выполнение.

Необходимо разработать консольное приложение (FTP-клиент) на любом языке программирования (без использования сторонних библиотек типа ftplib, pyftpdlib), которое поддерживает следующий набор команд:

  1. connect [порт] - установка соединения с FTP-сервером (порт по умолчанию - 21).

  2. login - аутентификация на сервере. Если пароль не указан, запросить его интерактивно.

  3. list - получение и вывод списка файлов и директорий в текущей удаленной директории.

  4. pwd - вывод имени текущей удаленной рабочей директории.

  5. cwd - смена текущей удаленной директории.

  6. retr - скачивание файла с сервера.

  7. stor - загрузка файла на сервер.

  8. quit - корректное завершение сессии и выход из программы.

Листинг сервера.

# ftp_server_simple.py import socket import threading import os from pathlib import Path # Настройки HOST = "0.0.0.0" CTRL_PORT = 2121 DATA_PORT = 2122 # упрощённый пассивный режим: фиксированный порт ROOT_DIR = Path("./ftp_root").resolve() os.makedirs(ROOT_DIR, exist_ok=True) AUTH = {"user": "user", "admin": "admin"} class FTPHandler: def __init__(self, ctrl_conn, addr): self.ctrl_conn = ctrl_conn self.addr = addr self.cwd = ROOT_DIR self.authenticated = False self.data_conn = None self.data_sock = None def send(self, msg): self.ctrl_conn.sendall((msg + "\r\n").encode()) def recv(self): return self.ctrl_conn.recv(1024).decode().strip() def run(self): self.send("220 Simple FTP Server Ready") while True: try: cmd_raw = self.recv() if not cmd_raw: break parts = cmd_raw.split(maxsplit=1) cmd = parts[0].upper() arg = parts[1] if len(parts) > 1 else "" print(f"[{self.addr}] {cmd} {arg}") if cmd == "USER": self.handle_user(arg) elif cmd == "PASS": self.handle_pass(arg) elif cmd == "PWD": self.handle_pwd() elif cmd == "CWD": self.handle_cwd(arg) elif cmd == "LIST": self.handle_list() elif cmd == "RETR": self.handle_retr(arg) elif cmd == "STOR": self.handle_stor(arg) elif cmd == "QUIT": self.send("221 Goodbye") break else: self.send("502 Command not implemented") except Exception as e: print(f"Error: {e}") break self.cleanup() def cleanup(self): if self.data_conn: self.data_conn.close() if self.data_sock: self.data_sock.close() self.ctrl_conn.close() def handle_user(self, user): if user in AUTH: self.user = user self.send("331 User OK, need password") else: self.send("530 User not found") def handle_pass(self, passwd): if self.user in AUTH and AUTH[self.user] == passwd: self.authenticated = True self.send("230 Login successful") else: self.send("530 Authentication failed") def require_auth(f): def wrapper(self, *args, **kwargs): if not self.authenticated: self.send("530 Login with USER and PASS first") return return f(self, *args, **kwargs) return wrapper @require_auth def handle_pwd(self): rel = self.cwd.relative_to(ROOT_DIR) if self.cwd != ROOT_DIR else "/" self.send(f'257 "{rel}"') @require_auth def handle_cwd(self, path): new_path = (self.cwd / path).resolve() if not new_path.is_relative_to(ROOT_DIR): self.send("550 Access denied") elif not new_path.exists(): self.send("550 Directory not found") else: self.cwd = new_path self.send("250 Directory changed") @require_auth def open_data_conn(self): self.data_sock = socket.socket() self.data_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.data_sock.bind((HOST, DATA_PORT)) self.data_sock.listen(1) ip = self.ctrl_conn.getsockname()[0] p1, p2 = divmod(DATA_PORT, 256) self.send(f"227 Entering Passive Mode ({ip.replace('.',',')},{p1},{p2})") self.data_conn, _ = self.data_sock.accept() @require_auth def handle_list(self): self.open_data_conn() try: for item in sorted(self.cwd.iterdir()): self.data_conn.sendall(f"{item.name}\n".encode()) finally: self.data_conn.close() self.data_sock.close() self.data_conn = None self.data_sock = None self.send("226 Directory send OK") @require_auth def handle_retr(self, filename): path = (self.cwd / filename).resolve() if not path.is_relative_to(ROOT_DIR) or not path.is_file(): self.send("550 File not found") return self.open_data_conn() try: with open(path, "rb") as f: while chunk := f.read(1024): self.data_conn.sendall(chunk) finally: self.data_conn.close() self.data_sock.close() self.data_conn = None self.data_sock = None self.send("226 Transfer complete") @require_auth def handle_stor(self, filename): path = (self.cwd / filename).resolve() if not path.is_relative_to(ROOT_DIR): self.send("550 Access denied") return self.open_data_conn() try: with open(path, "wb") as f: while True: data = self.data_conn.recv(1024) if not data: break f.write(data) finally: self.data_conn.close() self.data_sock.close() self.data_conn = None self.data_sock = None self.send("226 Transfer complete") # Запуск сервера def main(): ctrl_sock = socket.socket() ctrl_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) ctrl_sock.bind((HOST, CTRL_PORT)) ctrl_sock.listen(5) print("=" * 50) print("Учебный FTP-сервер запущен") print(f"Порт управления: {CTRL_PORT}") print(f"Порт данных (PASV): {DATA_PORT}") print(f"Корневая папка: {ROOT_DIR}") print("Пользователи: user/user, admin/admin") print("=" * 50) try: while True: conn, addr = ctrl_sock.accept() thread = threading.Thread(target=FTPHandler(conn, addr).run) thread.daemon = True thread.start() except KeyboardInterrupt: print("\nСервер остановлен") finally: ctrl_sock.close() if __name__ == "__main__": main()

Листинг клиента.

# ftp_client.py import socket import sys import os import getpass from pathlib import Path class FTPClient: def __init__(self): self.ctrl_sock = None self.host = None self.port = None def connect(self, host, port=2121): try: self.ctrl_sock = socket.socket() self.ctrl_sock.connect((host, port)) self.host = host self.port = port print(self.recv()) except Exception as e: print(f"Ошибка подключения: {e}") return False return True def send(self, msg): self.ctrl_sock.sendall((msg + "\r\n").encode()) def recv(self): return self.ctrl_sock.recv(1024).decode().strip() def login(self, user, passwd=None): if passwd is None: passwd = getpass.getpass() self.send(f"USER {user}") resp = self.recv() print(resp) if "331" in resp: self.send(f"PASS {passwd}") resp = self.recv() print(resp) return "230" in resp def pwd(self): self.send("PWD") print(self.recv()) def cwd(self, path): self.send(f"CWD {path}") print(self.recv()) def parse_pasv(self, resp): # 227 Entering Passive Mode (127,0,0,1,93,165) prefix = "227" if not resp.startswith(prefix): raise Exception("Не PASV ответ") p = resp[resp.find("(") + 1 : resp.find(")")].split(",") ip = ".".join(p[:4]) port = int(p[4]) * 256 + int(p[5]) return ip, port def list(self): self.send("PASV") pasv_resp = self.recv() print(pasv_resp) ip, port = self.parse_pasv(pasv_resp) data_sock = socket.socket() data_sock.connect((ip, port)) self.send("LIST") print(self.recv()) # 150 while True: data = data_sock.recv(1024) if not data: break print(data.decode(), end="") data_sock.close() print(self.recv()) # 226 def retr(self, filename): self.send("PASV") pasv_resp = self.recv() print(pasv_resp) ip, port = self.parse_pasv(pasv_resp) data_sock = socket.socket() data_sock.connect((ip, port)) self.send(f"RETR {filename}") status = self.recv() print(status) if "550" in status: data_sock.close() return with open(filename, "wb") as f: while True: data = data_sock.recv(1024) if not data: break f.write(data) data_sock.close() print(self.recv()) # 226 def stor(self, filename): if not os.path.isfile(filename): print("Файл не найден") return self.send("PASV") pasv_resp = self.recv() print(pasv_resp) ip, port = self.parse_pasv(pasv_resp) data_sock = socket.socket() data_sock.connect((ip, port)) self.send(f"STOR {filename}") print(self.recv()) # 150 with open(filename, "rb") as f: while chunk := f.read(1024): data_sock.sendall(chunk) data_sock.close() print(self.recv()) # 226 def quit(self): self.send("QUIT") print(self.recv()) self.ctrl_sock.close() def run(self): print("FTP клиент готов. Введите команду:") while True: try: line = input("ftp> ").strip() if not line: continue parts = line.split(maxsplit=1) cmd = parts[0].lower() arg = parts[1] if len(parts) > 1 else "" if cmd == "connect": args = arg.split() host = args[0] port = int(args[1]) if len(args) > 1 else 2121 self.connect(host, port) elif cmd == "login": args = arg.split(maxsplit=1) user = args[0] passwd = args[1] if len(args) > 1 else None self.login(user, passwd) elif cmd == "pwd": self.pwd() elif cmd == "cwd": self.cwd(arg) elif cmd == "list": self.list() elif cmd == "retr": self.retr(arg) elif cmd == "stor": self.stor(arg) elif cmd == "quit": self.quit() break else: print("Неизвестная команда") except KeyboardInterrupt: print("\nВыход...") break except Exception as e: print(f"Ошибка: {e}") if __name__ == "__main__": client = FTPClient() client.run()

Результат работы программы.

Скриншот со стороны сервера.

Полученные файлы.

Скриншот со стороны клиента.

Вывод.

В ходе выполнения лабораторной работы были изучены принципы работы протокола передачи файлов FTP, включая его архитектуру на основе модели «клиент—сервер» и использование двух независимых TCP-соединений: управляющего (control connection) и соединения для передачи данных (data connection). Особое внимание было уделено пассивному режиму (PASV), который является предпочтительным в современных сетях из-за совместимости с NAT и межсетевыми экранами.

Было разработано консольное приложение — упрощённый FTP-клиент на языке Python, реализующий основные команды протокола: подключение к серверу (`connect`), аутентификация (`login`), навигация по файловой системе (`pwd`, `cwd`), просмотр содержимого каталога (`list`), а также передача файлов в обоих направлениях (`retr`, `stor`). Клиент корректно обрабатывает ответы сервера, устанавливает управляющее и соединение для данных, разбирает ответ на команду `PASV` и обеспечивает передачу данных в соответствии со спецификацией RFC 959.

Реализация выполнена без использования сторонних библиотек (таких как `ftplib`), что позволило глубже понять низкоуровневые механизмы взаимодействия по протоколу FTP. Кроме того, была осознана важность корректной обработки текстовых команд и кодов ответов, а также необходимость разделения управляющего и данных потоков.

Таким образом, цель работы — приобретение практических навыков работы с FTP на уровне сокетов — была успешно достигнута. Полученные знания и опыт могут быть использованы при разработке сетевых приложений, требующих надёжной и контролируемой передачи файлов.