From 0b6bfaafc9a12c4cf2efa82ed907ede1ed39ce53 Mon Sep 17 00:00:00 2001 From: Alexander Baranov Date: Mon, 23 Feb 2026 20:05:35 +0300 Subject: [PATCH] homework: add power socket mock --- smart-house/README.md | 8 +-- smart-house/src/bin/power_socket_mock.rs | 87 ++++++++++++++++++++++++ smart-house/src/main.rs | 9 ++- smart-house/src/power_socket.rs | 6 +- 4 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 smart-house/src/bin/power_socket_mock.rs diff --git a/smart-house/README.md b/smart-house/README.md index 18fbc7b..d234f54 100644 --- a/smart-house/README.md +++ b/smart-house/README.md @@ -127,10 +127,10 @@ - [x] Розетка может использовать как реальный TCP-обмен, так и имитировать реальную работу (для тестов). Для имитатора умной розетки: -- [ ] Читает адрес для приёма TCP-соединений из аргументов командной строки. -- [ ] Реализован с использованием неблокирующего сетевого взаимодействия. -- [ ] Хранит состояние розетки. -- [ ] Позволяет управлять розеткой множеству клиентов одновременно. +- [x] Читает адрес для приёма TCP-соединений из аргументов командной строки. +- [x] Реализован с использованием неблокирующего сетевого взаимодействия. +- [x] Хранит состояние розетки. +- [x] Позволяет управлять розеткой множеству клиентов одновременно. Для умного термометра: - [ ] Функционал не изменяется: возвращает температуру. diff --git a/smart-house/src/bin/power_socket_mock.rs b/smart-house/src/bin/power_socket_mock.rs new file mode 100644 index 0000000..05bf185 --- /dev/null +++ b/smart-house/src/bin/power_socket_mock.rs @@ -0,0 +1,87 @@ +use std::io; +use std::io::{Read, Write}; +use std::net::{SocketAddr, TcpListener, TcpStream}; +use std::sync::{Arc, RwLock}; +use std::time::Duration; + +fn parse_args() -> SocketAddr { + let mut args = std::env::args(); + args.next(); + args.next() + .expect("server address should be passed as parameter") + .parse() + .expect("server address should be valid") +} + +struct RealPowerSocket { + power_rate: f32, + on: bool, +} + +const CMD_GET_ON: u8 = 1; +const CMD_TURN_ON: u8 = 2; +const CMD_TURN_OFF: u8 = 3; +const CMD_GET_POWER: u8 = 4; + +fn handle_connection(mut stream: TcpStream, real_power_socket: Arc>) { + loop { + let mut buf = [0u8; 1]; + let result = stream.read_exact(&mut buf); + match result { + Err(e) if e.kind() == io::ErrorKind::WouldBlock => { + std::thread::sleep(Duration::from_millis(250)); + continue; + } + Err(e) if [io::ErrorKind::UnexpectedEof, io::ErrorKind::ConnectionReset].contains(&e.kind()) => { + println!("connection is over"); + break; + } + Err(e) => panic!("error on reading socket: {e}"), + Ok(_) => {} + } + match buf { + [CMD_GET_ON] => { + println!("handling CMD_GET_ON"); + let power_socket = real_power_socket.read().expect("power socket lock is poisoned"); + buf = if power_socket.on { [1u8; 1] } else { [0u8; 1] }; + stream.write_all(&buf).expect("response write error"); + } + [CMD_TURN_ON] => { + println!("handling CMD_TURN_ON"); + let mut power_socket = real_power_socket.write().expect("power socket lock is poisoned"); + power_socket.on = true; + stream.write_all(&buf).expect("response write error"); + } + [CMD_TURN_OFF] => { + println!("handling CMD_TURN_OFF"); + let mut power_socket = real_power_socket.write().expect("power socket lock is poisoned"); + power_socket.on = false; + stream.write_all(&buf).expect("response write error"); + } + [CMD_GET_POWER] => { + println!("handling CMD_GET_POWER"); + let power_socket = real_power_socket.write().expect("power socket lock is poisoned"); + let data_buf = power_socket.power_rate.to_le_bytes(); + stream.write_all(&data_buf).expect("response write error"); + } + _ => panic!("unexpected command: {}", buf[0]), + } + } +} + +const TIMEOUT: Duration = Duration::from_secs(5); + +fn main() { + let real_power_socket = Arc::new(RwLock::new(RealPowerSocket { power_rate: 12.0, on: false })); + let listener = TcpListener::bind(parse_args()).expect("address 127.0.0.1:10001 should be available for listening"); + for connection in listener.incoming() { + println!("new connection"); + let real_power_socket = real_power_socket.clone(); + let stream = connection.expect("connection should not fail"); + stream.set_write_timeout(Some(TIMEOUT)).expect("set_write_timeout failed"); + stream.set_nonblocking(true).expect("set_nonblocking call failed"); + std::thread::spawn(move || { + handle_connection(stream, real_power_socket); + }); + } +} diff --git a/smart-house/src/main.rs b/smart-house/src/main.rs index f328e4d..898c7a9 100644 --- a/smart-house/src/main.rs +++ b/smart-house/src/main.rs @@ -1 +1,8 @@ -fn main() {} +use smart_house::PowerSocket; +use std::time::Duration; + +fn main() { + let power_socket = PowerSocket::connect("127.0.0.1:10001"); + println!("{}", power_socket.display()); + std::thread::sleep(Duration::from_secs(30)); +} diff --git a/smart-house/src/power_socket.rs b/smart-house/src/power_socket.rs index e341929..30cd075 100644 --- a/smart-house/src/power_socket.rs +++ b/smart-house/src/power_socket.rs @@ -40,7 +40,7 @@ impl PowerSocket { } } -pub trait PowerSocketHandle: Debug { +trait PowerSocketHandle: Debug { fn is_on(&self) -> bool; fn set_on(&mut self, on: bool); @@ -49,7 +49,7 @@ pub trait PowerSocketHandle: Debug { } #[derive(Debug)] -pub struct PowerSocketStub { +struct PowerSocketStub { power_rate: f32, on: bool, } @@ -77,7 +77,7 @@ impl PowerSocketHandle for PowerSocketStub { const TIMEOUT: Duration = Duration::from_secs(5); #[derive(Debug)] -pub struct PowerSocketClient { +struct PowerSocketClient { stream: RefCell, }