
Результаты применения программы
Рисунок 1
Рисунок 2
Рисунок 3
Рисунок 4
Рисунок 5
Рисунок 6
Рисунок 7
Заключение
В рамках данной курсовой работы было создано многозадачное приложение с использованием клиент-серверной технологии. В соответствии с требованиями задания были разработаны два серверных процесса и один клиентский процесс.
Созданное клиент-серверное приложение эффективно выполняет все требования, указанные в задании, и полностью обеспечивает пользователей всем необходимым функционалом. В дополнение к основным задачам были реализованы дополнительные задания 1 2 8, такие как режим периодической отправки запросов на сервер, возможность обновления информации по инициативе сервера, а также разработка графического интерфейса.
В ходе выполнения курсовой работы была успешно закреплена обширная теоретическая база знаний о современных операционных системах, а также приобретены ценные практические навыки в разработке клиент-серверных приложений в среде Linux. При этом активно использовались стандартные механизмы межпроцессного взаимодействия, такие как сокеты и каналы, которые играют ключевую роль в организации эффективного обмена данными между клиентом и сервером.
В процессе разработки как серверных, так и клиентских приложений была проведена глубокая и всесторонняя работа по изучению различных механизмов обмена данными, а также средств синхронизации процессов и потоков. Особое внимание было уделено функциям API, которые позволяют взаимодействовать с операционной системой на более высоком уровне, и возможностям получения системной информации о процессах, потоках и состоянии памяти.
Кроме того, в рамках работы были рассмотрены общие методы практического применения всех перечисленных механизмов, что позволило значительно углубить понимание темы и освоить важные аспекты системного программирования. Этот опыт не только укрепил теоретические знания, но и подготовил к будущим вызовам в области разработки программного обеспечения, обеспечив уверенность в использовании изученных технологий на практике.
Список использованных источников
1. Э.Таненбаум, Х.Бос. Современные операционные системы. 4-изд. – СПб.: Питер, 2018
2. В. Столлингс. Операционные системы. Внутренняя структура и принципы проектирования. 9-изд. – Вильямс, 2020
3. Арпачи-Дюссо Р.Х., Арпачи-Дюссо А.К., Операционные системы: Три простых элемента – М.: ДМК Пресс, 2021
4. Колиснеченко Д.Н. Linux. От новичка к перофессионалу. – 7-е изд., перераб. и доп. — СПб.: БХВ-Петербург, 2020
5. Роберт Лав. Linux. Системное программирование. 2-е изд. - – СПб.: Питер, 2014
Приложение
Приложение 1 – Код программы server1.py
from encodings.utf_8 import encode, decode import os import socket import ast import platform import subprocess from time import sleep, time from datetime import datetime import threading import psutil def get_terminal_size(): system = platform.system() if system == 'Linux': terminal_size = subprocess.run(['tput', 'cols', 'lines'], capture_output=True, text=True, check=True).stdout return terminal_size.replace('\n', ' ').strip() else: p = subprocess.Popen(["powershell.exe", '&{(get-host).ui.rawui.MaxPhysicalWindowSize;}'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) return list(filter(lambda x: x, p.communicate()[0].strip().split(' ')))[-2:] def get_memory(): memory_info = psutil.virtual_memory() return memory_info.available / (1024 ** 3) def get_data(): data = {} data['terminal_size'] = get_terminal_size() data['memory'] = get_memory() return data def send_message(client_socket: socket.socket, message: str): ''' Sending message into sock :param client_socket: сокет клиента :param message: сообщение, которое будет передано :return: None ''' message = encode(message + '\n')[0] while True: try: client_socket.send(message) print(f'{datetime.now().strftime("%H:%M:%S")}: << {message}') return except BlockingIOError: sleep(1) def send_state(client_socket: socket.socket, data: dict): new_data = get_data() message_dict = {} for key, item in new_data.items(): if item != data.get(key, None): data[key] = new_data[key] message_dict[key] = item if message_dict: message = repr(message_dict) send_message(client_socket, message) def on_new_client(client_socket: socket.socket): data = {} while True: try: msg = client_socket.recv(1024) except BlockingIOError: pass except ConnectionError: break else: messages = decode(msg)[0] if messages == '': break print(f'{datetime.now().strftime("%H:%M:%S")}: >> {messages}') for message in messages.split('\n'): if message.strip() != '': # Обработка сообщений клиента (например, режим, запрос данных и т.д.) # Здесь можно добавить обработку сообщений pass send_state(client_socket, data) client_socket.close() def main(): sock = socket.socket() port = 1001 try: threads = [] sock.bind(('', port)) sock.listen(10) sock.setblocking(False) while True: try: conn, address = sock.accept() thread = threading.Thread(target=on_new_client, args=(conn,)) threads.append(thread) thread.start() except BlockingIOError: continue except KeyboardInterrupt: sock.close() if __name__ == '__main__': main()
Приложение 2 – код программы server 2
from encodings.utf_8 import encode, decode import os import socket import ast import platform import subprocess from time import sleep from datetime import datetime import threading import psutil def get_process_threads(): """Возвращает количество потоков для указанного PID.""" current_pid = os.getpid() system = platform.system() if system == 'Windows': processes = subprocess.run(['wmic', 'process', 'get', 'ProcessId,ThreadCount'], capture_output=True, text=True, check=True).stdout processes = list(filter(lambda x: x != '', processes.splitlines())) processes.pop(0) for process in processes: process = process.strip().split(' ') pid, threads = int(process[0]), int(process[-1]) if pid == current_pid: return threads return 0 processes = subprocess.run(['ps', '-eLo', 'pid,tid,cmd'], capture_output=True, text=True, check=True).stdout thread_count = 0 for line in processes.splitlines()[1:]: # Пропускаем заголовок if str(current_pid) in line.split(): # Сравниваем PID thread_count += 1 return thread_count # Linux command = ['ps', '-eLo', 'pid,tid,cmd'] # Получаем список процессов и потоков processes = command(command) thread_count = 0 for line in processes.splitlines()[1:]: # Пропускаем заголовок if str(current_pid) in line.split(): # Сравниваем PID thread_count += 1 return thread_count def get_processes(): """Возвращает количество процессов текущего приложения.""" current_pid = os.getpid() system = platform.system() if system == 'Windows': processes = subprocess.run(['wmic', 'process', 'get', 'ProcessId,ParentProcessId'], capture_output=True, text=True, check=True).stdout return sum(1 for line in processes.splitlines() if str(current_pid) in line.split(' ')[0]) else: # Linux processes = subprocess.run(['ps', '-e', '-o', 'pid'], capture_output=True, text=True, check=True).stdout return sum(1 for line in processes.splitlines() if str(current_pid) in line) def get_data(): data = {} data['threads'] = get_process_threads() data['processes'] = get_processes() return data def send_message(client_socket: socket.socket, message: str): ''' Sending message into sock :param client_socket: сокет клиента :param message: сообщение, которое будет передано :return: None ''' message = encode(message + '\n')[0] while True: try: client_socket.send(message) print(f'{datetime.now().strftime("%H:%M:%S")}: << {message}') return except BlockingIOError: sleep(1) def send_state(client_socket: socket.socket, data: dict, mode: int, update_rules: list[bool]): new_data = get_data() message_dict = {} for key, item in new_data.items(): if item != data.get(key, None): data[key] = new_data[key] message_dict[key] = item if message_dict: message = repr(message_dict) send_message(client_socket, message) def on_new_client(client_socket: socket.socket): mode = 0 update_rules = None data = {} while True: try: msg = client_socket.recv(1024) except BlockingIOError: pass except ConnectionError: break else: messages = decode(msg)[0] if messages == '': break print(f'{datetime.now().strftime("%H:%M:%S")}: >> {messages}') for message in messages.split('\n'): eval_message(message.strip(), data, client_socket, mode, update_rules) send_state(client_socket, data, mode, update_rules) client_socket.close() def eval_message(message: str, data: dict, client_socket: socket.socket, mode: int, update_rules: list[bool]): if message: message = ast.literal_eval(message) else: return if message.get('mode', None) is not None: mode = message['mode'] elif message.get('get', False): send_state(client_socket, data, mode, update_rules) elif param := message.get('updates', []): update_rules = param def main(): sock = socket.socket() port = 1002 try: threads = [] sock.bind(('', port)) sock.listen(10) sock.setblocking(False) while True: try: conn, address = sock.accept() thread = threading.Thread(target=on_new_client, args=(conn,)) threads.append(thread) thread.start() except BlockingIOError: continue except KeyboardInterrupt: sock.close() if __name__ == '__main__': main()
Приложение 3 – код программы client
import tkinter as tk import socket from encodings.utf_8 import encode, decode from time import sleep, time from datetime import datetime import ast class Application(tk.Frame): def __init__(self, master): super().__init__() self.master = master self.master.configure(bg='#f0f0f0') self._initialize_interface() self.data_1 = {} self.data_2 = {} self.connected_1 = False self.connected_2 = False self.mode_1 = 0 # 0 - Callback, 1 - Polling self.mode_2 = 0 # 0 - Callback, 1 - Polling self.param_list_1 = [True, True] self.param_list_2 = [True, True] self.periodic_requests_1 = False self.periodic_requests_2 = False self.next_send_1 = 0 self.next_send_2 = 0 self.sock_1 = None self.sock_2 = None def _initialize_interface(self): self.frame_1 = tk.Frame(self.master, bg='#e0e0e0', bd=2, relief=tk.RAISED) self.frame_1.pack(side=tk.LEFT, padx=10, pady=10) self.frame_2 = tk.Frame(self.master, bg='#e0e0e0', bd=2, relief=tk.RAISED) self.frame_2.pack(side=tk.RIGHT, padx=10, pady=10) # Elements for Server 1 self.label_server_1 = tk.Label(self.frame_1, text="Сервер 1", font=("Arial", 16, "bold"), bg='#e0e0e0') self.label_server_1.grid(row=0, column=0, columnspan=2, pady=(10, 5)) self.log_text_field_1 = tk.Text(self.frame_1, height=15, width=60, bg='#ffffff', font=("Arial", 10)) self.log_text_field_1.bind("<Key>", lambda e: "break") self.log_text_field_1.grid(row=1, column=0, columnspan=2, padx=5, pady=5) self.status_label_1 = tk.Label(self.frame_1, bg='#e0e0e0', font=("Arial", 10)) self.status_label_1.grid(row=2, column=0, columnspan=2) self.param_button_1_1 = tk.Button(self.frame_1, text="Отключить Параметр 1", command=self.set_value_1_subscription_server_1, width=25, height=2) self.param_button_1_1.grid(row=3, column=0, pady=(5, 5)) self.param_button_2_1 = tk.Button(self.frame_1, text="Отключить Параметр 2", command=self.set_value_2_subscription_server_1, width=25, height=2) self.param_button_2_1.grid(row=4, column=0, pady=(5, 5)) self.all_params_button_1 = tk.Button(self.frame_1, text="Получить параметры", command=self.get_polling_data_server_1, width=25, height=2) self.all_params_button_1.grid(row=5, column=0, columnspan=2, pady=(5, 10)) self.method_button_1 = tk.Button(self.frame_1, text="Сменить на Polling", command=self.switch_method_server_1, width=25, height=2) self.method_button_1.grid(row=3, column=1, pady=(5, 5)) self.connect_button_1 = tk.Button(self.frame_1, text="Подключиться", command=self.connect_or_disconnect_server_1, width=25, height=2) self.connect_button_1.grid(row=4, column=1, pady=(5, 5)) # Elements for Server 2 self.label_server_2 = tk.Label(self.frame_2, text="Сервер 2", font=("Arial", 16, "bold"), bg='#e0e0e0') self.label_server_2.grid(row=0, column=0, columnspan=2, pady=(10, 5)) self.log_text_field_2 = tk.Text(self.frame_2, height=15, width=60, bg='#ffffff', font=("Arial", 10)) self.log_text_field_2.bind("<Key>", lambda e: "break") self.log_text_field_2.grid(row=1, column=0, columnspan=2, padx=5, pady=5) self.status_label_2 = tk.Label(self.frame_2, bg='#e0e0e0', font=("Arial", 10)) self.status_label_2.grid(row=2, column=0, columnspan=2) self.param_button_1_2 = tk.Button(self.frame_2, text="Отключить Параметр 1", command=self.set_value_1_subscription_server_2, width=25, height=2) self.param_button_1_2.grid(row=3, column=0, pady=(5, 5)) self.param_button_2_2 = tk.Button(self.frame_2, text="Отключить Параметр 2", command=self.set_value_2_subscription_server_2, width=25, height=2) self.param_button_2_2.grid(row=4, column=0, pady=(5, 5)) self.all_params_button_2 = tk.Button(self.frame_2, text="Получить параметры", command=self.get_polling_data_server_2, width=25, height=2) self.all_params_button_2.grid(row=5, column=0, columnspan=2, pady=(5, 10)) self.method_button_2 = tk.Button(self.frame_2, text="Сменить на Polling", command=self.switch_method_server_2, width=25, height=2) self.method_button_2.grid(row=3, column=1, pady=(5, 5)) self.connect_button_2 = tk.Button(self.frame_2, text="Подключиться", command=self.connect_or_disconnect_server_2, width=25, height=2) self.connect_button_2.grid(row=4, column=1, pady=(5, 5)) # Elements for periodic requests self.start_requests_button_1 = tk.Button(self.frame_1, text="Запустить периодические запросы", command=self.toggle_periodic_requests_server_1, width=30, height=3) self.start_requests_button_1.grid(row=6, column=0, pady=(10, 5)) self.interval_spinbox_1 = tk.Spinbox(self.frame_1, from_=1, to=10, increment=1) self.interval_spinbox_1.grid(row=6, column=1, pady=(10, 5)) # Elements for Server 2 self.start_requests_button_2 = tk.Button(self.frame_2, text="Запустить периодические запросы", command=self.toggle_periodic_requests_server_2, width=30, height=3) self.start_requests_button_2.grid(row=6, column=0, pady=(10, 5)) self.interval_spinbox_2 = tk.Spinbox(self.frame_2, from_=1, to=10, increment=1) self.interval_spinbox_2.grid(row=6, column=1, pady=(10, 5)) def connect_or_disconnect_server_1(self): self.connect_or_disconnect_server(1) def connect_or_disconnect_server_2(self): self.connect_or_disconnect_server(2) def connect_or_disconnect_server(self, server_number): if server_number == 1: if self.connected_1: self.disconnect_server(1) else: self.connect_server(1) else: if self.connected_2: self.disconnect_server(2) else: self.connect_server(2) def connect_server(self, server_number): if server_number == 1: self.sock_1 = socket.socket() try: self.sock_1.connect(('localhost', 1001)) self.sock_1.setblocking(False) self.connected_1 = True self.connect_button_1.config(text='Отключиться') self.log(f'Успешно подключено к Серверу 1', 1) if self.mode_1 == 0: # Если режим Callback self.receive_messages_server_1() # Запускаем получение сообщений except ConnectionRefusedError: self.log(f'Подключение к Серверу 1 не удалось', 1) else: self.sock_2 = socket.socket() try: self.sock_2.connect(('localhost', 1002)) self.sock_2.setblocking(False) self.connected_2 = True self.connect_button_2.config(text='Отключиться') self.log(f'Успешно подключено к Серверу 2', 2) if self.mode_2 == 0: # Если режим Callback self.receive_messages_server_2() # Запускаем получение сообщений except ConnectionRefusedError: self.log(f'Подключение к Серверу 2 не удалось', 2) def disconnect_server(self, server_number): if server_number == 1: self.sock_1.close() self.connected_1 = False self.data_1 = {} self.connect_button_1.config(text='Подключиться') self.log(f'Отключено от Серверу 1', 1) else: self.sock_2.close() self.connected_2 = False self.data_2 = {} self.connect_button_2.config(text='Подключиться') self.log(f'Отключено от Серверу 2', 2) def switch_method_server_1(self): self.switch_method(1) def switch_method_server_2(self): self.switch_method(2) def switch_method(self, server_number): if server_number == 1: self.mode_1 = 1 if self.mode_1 == 0 else 0 self.method_button_1.config(text='Сменить на Polling' if self.mode_1 == 0 else 'Сменить на Callback') self.send_mode(server_number) # Отправляем режим на сервер if self.mode_1 == 0: # Если режим Callback self.receive_messages_server_1() # Запускаем получение сообщений else: self.mode_2 = 1 if self.mode_2 == 0 else 0 self.method_button_2.config(text='Сменить на Polling' if self.mode_2 == 0 else 'Сменить на Callback') self.send_mode(server_number) # Отправляем режим на сервер if self.mode_2 == 0: # Если режим Callback self.receive_messages_server_2() # Запускаем получение сообщений def send_mode(self, server_number): mode_message = {'mode': self.mode_1 if server_number == 1 else self.mode_2} self.send_message(server_number, mode_message) def log(self, message, server_number): message = message.strip() if server_number == 1: self.log_text_field_1.insert(self.log_text_field_1.index('end'), f"{datetime.now().strftime('%H:%M:%S')}\t{message}\n") self.log_text_field_1.see(tk.END) else: self.log_text_field_2.insert(self.log_text_field_2.index('end'), f"{datetime.now().strftime('%H:%M:%S')}\t{message}\n") self.log_text_field_2.see(tk.END) def set_value_1_subscription_server_1(self): self.param_list_1[0] = not self.param_list_1[0] self.re_subscribe_params(1) def set_value_1_subscription_server_2(self): self.param_list_2[0] = not self.param_list_2[0] self.re_subscribe_params(2) def set_value_2_subscription_server_1(self): self.param_list_1[1] = not self.param_list_1[1] self.re_subscribe_params(1) def set_value_2_subscription_server_2(self): self.param_list_2[1] = not self.param_list_2[1] self.re_subscribe_params(2) def re_subscribe_params(self, server_number): if server_number == 1: self.send_message(1, {'updates': self.param_list_1}) self.update_param_buttons(1) else: self.send_message(2, {'updates': self.param_list_2}) self.update_param_buttons(2) def update_param_buttons(self, server_number): if server_number == 1: for i, button in enumerate([self.param_button_1_1, self.param_button_2_1]): button.config(text=f'{"Отключить" if self.param_list_1[i] else "Включить"} Параметр {i + 1}') else: for i, button in enumerate([self.param_button_1_2, self.param_button_2_2]): button.config(text=f'{"Отключить" if self.param_list_2[i] else "Включить"} Параметр {i + 1}') def get_polling_data_server_1(self): self.get_polling_data(1) def get_polling_data_server_2(self): self.get_polling_data(2) def get_polling_data(self, server_number): self.send_message(server_number, {'get': True}) def send_message(self, server_number, message): if server_number == 1 and self.connected_1: self.log(repr(message), 1) message = encode(repr(message) + '\n')[0] while True: try: self.sock_1.send(message) return except BlockingIOError: sleep(1) elif server_number == 2 and self.connected_2: self.log(repr(message), 2) message = encode(repr(message) + '\n')[0] while True: try: self.sock_2.send(message) return except BlockingIOError: sleep(1) def receive_messages_server_1(self): if self.connected_1: try: msg = self.sock_1.recv(1024) if msg: self.process_message(1, decode(msg)[0]) except BlockingIOError: pass except ConnectionError: self.log('Соединение потеряно', 1) self.disconnect_server(1) def receive_messages_server_2(self): if self.connected_2: try: msg = self.sock_2.recv(1024) if msg: self.process_message(2, decode(msg)[0]) except BlockingIOError: pass except ConnectionError: self.log('Соединение потеряно', 2) self.disconnect_server(2) def process_message(self, server_number, message): if message: message = ast.literal_eval(message) if server_number == 1: self.data_1.update(message) self.log(f'Сервер 1 >> {message}', 1) self.update_status_label(1) else: self.data_2.update(message) self.log(f'Сервер 2 >> {message}', 2) self.update_status_label(2) def update_status_label(self, server_number): if server_number == 1: formatted_data = "\n".join([f"{key}: {value}" for key, value in self.data_1.items()]) self.status_label_1.config(text=formatted_data) else: formatted_data = "\n".join([f"{key}: {value}" for key, value in self.data_2.items()]) self.status_label_2.config(text=formatted_data) def toggle_periodic_requests_server_1(self): self.toggle_periodic_requests(1) def toggle_periodic_requests_server_2(self): self.toggle_periodic_requests(2) def toggle_periodic_requests(self, server_number): if server_number == 1: self.periodic_requests_1 = not self.periodic_requests_1 self.start_requests_button_1.config(text='Остановить периодические запросы' if self.periodic_requests_1 else 'Запустить периодические запросы') else: self.periodic_requests_2 = not self.periodic_requests_2 self.start_requests_button_2.config(text='Остановить периодические запросы' if self.periodic_requests_2 else 'Запустить периодические запросы') def run(self): root = self.master while True: root.update_idletasks() root.update() # Вызываем методы получения сообщений, если режим Callback if self.mode_1 == 0 and self.connected_1: self.receive_messages_server_1() if self.mode_2 == 0 and self.connected_2: self.receive_messages_server_2() # Обработка периодических запросов self.send_periodic_requests() # Обработка сообщений от серверов в режиме Polling if self.mode_1 == 1 and self.connected_1: self.receive_messages_server_1() if self.mode_2 == 1 and self.connected_2: self.receive_messages_server_2() if not self.connected_1: self.connect_button_1.config(text='Подключиться') self.param_button_1_1.config(text='Отключить Параметр 1') self.param_button_2_1.config(text='Отключить Параметр 2') if not self.connected_2: self.connect_button_2.config(text='Подключиться') self.param_button_1_2.config(text='Отключить Параметр 1') self.param_button_2_2.config(text='Отключить Параметр 2') def send_periodic_requests(self): interval_periodic_1 = int(self.interval_spinbox_1.get()) interval_periodic_2 = int(self.interval_spinbox_2.get()) if self.periodic_requests_1 and self.next_send_1 < time(): self.next_send_1 = time() + interval_periodic_1 self.get_polling_data(1) if self.periodic_requests_2 and self.next_send_2 < time(): self.next_send_2 = time() + interval_periodic_2 self.get_polling_data(2) if __name__ == "__main__": root = tk.Tk() app = Application(root) app.run()