feat: 增强连接状态。

This commit is contained in:
Ivan Li 2023-04-30 22:30:24 +08:00
parent 6c90a5e655
commit 82d4adfe0f
4 changed files with 71 additions and 28 deletions

View File

@ -1,15 +1,23 @@
use std::{net::Ipv4Addr, time::Duration}; use std::{net::Ipv4Addr, time::Duration};
use paris::warn; use paris::{warn, info};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::{net::UdpSocket, time::timeout}; use tokio::{net::UdpSocket, time::timeout};
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
pub enum BoardConnectStatus {
Connected,
Connecting(u8),
Disconnected,
Unknown,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct BoardInfo { pub struct BoardInfo {
pub host: String, pub host: String,
pub address: Ipv4Addr, pub address: Ipv4Addr,
pub port: u16, pub port: u16,
pub is_online: bool, pub connect_status: BoardConnectStatus,
pub checked_at: Option<std::time::SystemTime>, pub checked_at: Option<std::time::SystemTime>,
pub ttl: Option<u128>, pub ttl: Option<u128>,
} }
@ -20,7 +28,7 @@ impl BoardInfo {
host, host,
address, address,
port, port,
is_online: false, connect_status: BoardConnectStatus::Unknown,
checked_at: None, checked_at: None,
ttl: None, ttl: None,
} }
@ -36,20 +44,39 @@ impl BoardInfo {
let mut buf = [0u8; 1]; let mut buf = [0u8; 1];
let recv_future = socket.recv(&mut buf); let recv_future = socket.recv(&mut buf);
match timeout(Duration::from_secs(5), recv_future).await { match timeout(Duration::from_secs(1), recv_future).await {
Ok(_) => { Ok(_) => {
let ttl = instant.elapsed(); let ttl = instant.elapsed();
log::info!("buf: {:?}", buf);
if buf == [1] { if buf == [1] {
self.is_online = true; self.connect_status = BoardConnectStatus::Connected;
} else { } else {
self.is_online = false; if let BoardConnectStatus::Connecting(retry) = self.connect_status {
if retry < 10 {
self.connect_status = BoardConnectStatus::Connecting(retry + 1);
info!("reconnect: {}", retry + 1);
} else {
self.connect_status = BoardConnectStatus::Disconnected;
warn!("board Disconnected: bad pong.");
}
} else if self.connect_status != BoardConnectStatus::Disconnected {
self.connect_status = BoardConnectStatus::Connecting(1);
}
} }
self.ttl = Some(ttl.as_millis()); self.ttl = Some(ttl.as_millis());
} }
Err(_) => { Err(_) => {
warn!("timeout"); if let BoardConnectStatus::Connecting(retry) = self.connect_status {
self.is_online = false; if retry < 10 {
self.connect_status = BoardConnectStatus::Connecting(retry + 1);
info!("reconnect: {}", retry + 1);
} else {
self.connect_status = BoardConnectStatus::Disconnected;
warn!("board Disconnected: timeout");
}
} else if self.connect_status != BoardConnectStatus::Disconnected {
self.connect_status = BoardConnectStatus::Connecting(1);
}
self.ttl = None; self.ttl = None;
} }
} }

View File

@ -1,4 +1,9 @@
use std::{collections::HashSet, sync::Arc, time::Duration}; use std::{
collections::{HashMap, HashSet},
net::Ipv4Addr,
sync::Arc,
time::Duration,
};
use futures::future::join_all; use futures::future::join_all;
use mdns_sd::{ServiceDaemon, ServiceEvent}; use mdns_sd::{ServiceDaemon, ServiceEvent};
@ -8,12 +13,12 @@ use tokio::{
sync::{watch, OnceCell, RwLock}, sync::{watch, OnceCell, RwLock},
}; };
use super::BoardInfo; use super::{BoardConnectStatus, BoardInfo};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct UdpRpc { pub struct UdpRpc {
socket: Arc<UdpSocket>, socket: Arc<UdpSocket>,
boards: Arc<RwLock<HashSet<BoardInfo>>>, boards: Arc<RwLock<HashMap<Ipv4Addr, BoardInfo>>>,
boards_change_sender: Arc<watch::Sender<Vec<BoardInfo>>>, boards_change_sender: Arc<watch::Sender<Vec<BoardInfo>>>,
} }
@ -33,7 +38,7 @@ impl UdpRpc {
async fn new() -> anyhow::Result<Self> { async fn new() -> anyhow::Result<Self> {
let socket = UdpSocket::bind("0.0.0.0:0").await?; let socket = UdpSocket::bind("0.0.0.0:0").await?;
let socket = Arc::new(socket); let socket = Arc::new(socket);
let boards = Arc::new(RwLock::new(HashSet::new())); let boards = Arc::new(RwLock::new(HashMap::new()));
let (boards_change_sender, _) = watch::channel(Vec::new()); let (boards_change_sender, _) = watch::channel(Vec::new());
let boards_change_sender = Arc::new(boards_change_sender); let boards_change_sender = Arc::new(boards_change_sender);
@ -109,11 +114,11 @@ impl UdpRpc {
info.get_port(), info.get_port(),
); );
if boards.insert(board.clone()) { if boards.insert(board.address, board.clone()).is_some() {
info!("added board {:?}", board); info!("added board {:?}", board);
} }
let tx_boards = boards.iter().cloned().collect(); let tx_boards = boards.values().cloned().collect();
drop(boards); drop(boards);
sender.send(tx_boards)?; sender.send(tx_boards)?;
@ -133,8 +138,8 @@ impl UdpRpc {
} }
pub async fn get_boards(&self) -> Vec<BoardInfo> { pub async fn get_boards(&self) -> Vec<BoardInfo> {
let boards: tokio::sync::RwLockReadGuard<HashSet<BoardInfo>> = self.boards.read().await; let boards = self.boards.read().await;
boards.iter().cloned().collect() boards.values().cloned().collect()
} }
pub async fn send_to_all(&self, buff: &Vec<u8>) -> anyhow::Result<()> { pub async fn send_to_all(&self, buff: &Vec<u8>) -> anyhow::Result<()> {
@ -160,29 +165,30 @@ impl UdpRpc {
} }
pub async fn check_boards(&self) { pub async fn check_boards(&self) {
let mut interval = tokio::time::interval(Duration::from_secs(1));
loop { loop {
tokio::time::sleep(Duration::from_secs(1)).await; let mut boards = self.boards.clone().write_owned().await;
let mut boards = self.get_boards().await;
if boards.is_empty() { if boards.is_empty() {
info!("no boards found"); info!("no boards found");
continue; continue;
} }
for board in &mut boards { for (_, board) in boards.iter_mut() {
if let Err(err) = board.check().await { if let Err(err) = board.check().await {
error!("failed to check board {}: {:?}", board.host, err); error!("failed to check board {}: {:?}", board.host, err);
} }
} }
let board_vec = boards.values().cloned().collect::<Vec<_>>();
drop(boards);
let board_change_sender = self.boards_change_sender.clone(); let board_change_sender = self.boards_change_sender.clone();
if let Err(err) = board_change_sender.send(boards) { if let Err(err) = board_change_sender.send(board_vec) {
error!("failed to send board change: {:?}", err); error!("failed to send board change: {:?}", err);
} else {
info!("send");
} }
drop(board_change_sender); drop(board_change_sender);
interval.tick().await;
} }
} }
} }

View File

@ -16,7 +16,7 @@ const Item: ParentComponent<ItemProps> = (props) => {
export const BoardInfoPanel: Component<{ board: BoardInfo }> = (props) => { export const BoardInfoPanel: Component<{ board: BoardInfo }> = (props) => {
const ttl = createMemo(() => { const ttl = createMemo(() => {
if (!props.board.is_online) { if (props.board.connect_status !== 'Connected') {
return '--'; return '--';
} }
@ -31,6 +31,16 @@ export const BoardInfoPanel: Component<{ board: BoardInfo }> = (props) => {
); );
}); });
const connectStatus = createMemo(() => {
if (typeof props.board.connect_status === 'string') {
return props.board.connect_status;
}
if ('Connecting' in props.board.connect_status) {
return `Connecting (${props.board.connect_status.Connecting.toFixed(0)})`;
}
});
return ( return (
<section class="p-2 rounded shadow"> <section class="p-2 rounded shadow">
<Item label="Host">{props.board.host}</Item> <Item label="Host">{props.board.host}</Item>
@ -41,7 +51,7 @@ export const BoardInfoPanel: Component<{ board: BoardInfo }> = (props) => {
<span class="font-mono">{props.board.port}</span> <span class="font-mono">{props.board.port}</span>
</Item> </Item>
<Item label="Status"> <Item label="Status">
<span class="font-mono">{props.board.is_online ? 'Online' : 'Offline'}</span> <span class="font-mono">{connectStatus()}</span>
</Item> </Item>
<Item label="TTL">{ttl()}</Item> <Item label="TTL">{ttl()}</Item>
</section> </section>

View File

@ -3,6 +3,6 @@ export type BoardInfo = {
address: string; address: string;
port: number; port: number;
ttl: number; ttl: number;
is_online: boolean; connect_status: 'Connected' | 'Disconnected' | { Connecting: number };
checked_at: Date; checked_at: Date;
}; };