ㅇㅇ
김반장의 블로그
it블로그
2026년 7월 10일 금요일
2026년 7월 4일 토요일
ini-parser 만들기.. C#
윈도우에 있는 ini파일이 개행이 \r\n이거니와.
리눅스쪽으로 복사가 되면서 더미 데이터가 들어가는것 같다..
2026년 6월 28일 일요일
모바일 삼성인터넷 브라우저에서 웹앱만들기
나도.. 자바나 코틀린으로 안드로이드 스튜디오에서 웹뷰만 가지고 처리하도록
하는 앱으로 실행해서 특정웹페이지 URL을 지정해서 apk로 빌드하곤했다.
매니페스트(아이콘과 앱이름)정보와 부가파일만 있으면 처리되고 실행시 스플레쉬 이미지를
"name": "마이어플",
"short_name": "마이어플",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#ffffff",
"icons": [
{
"src": "/myicon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/myicon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
self.skipWaiting();
});
self.addEventListener("activate", e=>{
self.clients.claim();
});
<link rel="manifest" href="/manifest.webmanifest">
라고 기제하고
스크립트 테그에서
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
그 바로가기는 핸드폰 어플처럼 동작한다.
2026년 6월 18일 목요일
닷넷의 R2R
빌드가 안되어 버린다.
잘라내도 된다고 판단해서 코드가 잘라내어 지면(Trimmed) 실행중에 실제로
난독화가 되는것이 아니고 중간언어가 함께 들어가기 때문에 보안이 좋다는
착각을 해서는 안된다.
2026년 6월 17일 수요일
베컴박스(Best Compact Box) 만들기
SBC 싱글보드 컴퓨터가 arm 64비트가 나오고 스펙이 좋아지고 있다.
그리고 요즘에 SK하이닉스에서 SSD 128기가 짜리 2230 벌크가
sftp://아이피.. 그리고 22번 포트로 접속해서 파일 송수신이 가능하다.)
베컴박스 만들기
기기 인프라 확인
초기 하드웨어 점검: (쿨링패드장착, IO핀, 하이닉스M.2 SSD설치)
최초에 우분투서버 이미지를 SD카드에 삽입
Etcher나 Rufus를 이용하여 SD카드에 플레싱후 부팅
맥주소를 확인하여 ipv4주소로 접속한다.
명령어 (nmap -p 22 --open 192.168.0.1/24)
SSH 포트(22) 활성화 확인후 putty로 접속확인
sftp://[기기아이피]로 파일질라를 통해 포트(22) 접속확인
사용자명(U): orangepi
비밀번호(P): orangepi
(선택사항) SSD등의 부가 스토리지로 부팅하고자 하는 경우
SD카드와 SSD스토리지를 동시에 장착하고 부팅
#아래의 명령으로 플레싱툴 설치
sudo apt update
sudo apt install -y bmap-tools
# 처음 부팅된 SD카드에 플레싱할 img파일을 복사한후
# 터미널에서 다음명령어로 SSD 장착여부 확인
lsblk
# 위결과가 nvme0n1 이나 sda로 나옴
# bmaptool으로 img를 ssd에 카피
sudo bmaptool copy /home/orangepi/ubuntu.img /dev/nvme0n1 --nobmap
# 이후 orangepi-config 등에서 부팅 시퀀스 잡아주기
System → Install to/update boot loader 메뉴로 진입
Install/Update bootloader on SPI Flash 옵션을 선택하면
얼마 시간이 지난후 다시 메뉴로 돌아감 이후 재부팅하면 칩셋에 SSD를 1순위로 부팅
기기 Config 세팅
명령어(sudo orangepi-config)로 → 타임존, 로케일 설정
Personal settings 하위메뉴에서
Timezone: Asia / Seoul 설정
Locales: en_US.UTF-8, ko_KR.EUC-KR, ko_KR.UTF-8 설정
기기 로고 세팅
전원 인가시 표시되는 텍스트들을 없에고 이미지 한장으로 표시
#부팅로고 활성화
sudo nano /boot/orangepiEnv.txt
#내용중에 위 2줄을 다음처럼 변경
verbosity=0
bootlogo=true
#이후 테마 디렉터리 생성
sudo mkdir -p /usr/share/plymouth/themes/mylogo
#/usr/share/plymouth/themes/mylogo 폴더로 logo.png 파일을 복사한다.
#mylog.plymouth 생성
sudo nano /usr/share/plymouth/themes/mylogo/mylogo.plymouth
#내용은 다음으로
[Plymouth Theme]
Name=MyLogo
Description=Simple Logo Boot Screen
ModuleName=script
[script]
ImageDir=/usr/share/plymouth/themes/mylogo
ScriptFile=/usr/share/plymouth/themes/mylogo/mylogo.script
#mylogo.script 생성
sudo nano /usr/share/plymouth/themes/mylogo/mylogo.script
#내용은 다음으로
logo = Image("logo.png");
sprite = Sprite(logo);
sprite.SetX(
Window.GetWidth()/2 -
logo.GetWidth()/2
);
sprite.SetY(
Window.GetHeight()/2 -
logo.GetHeight()/2
);
#테마 활성화
sudo ln -sf \
/usr/share/plymouth/themes/mylogo/mylogo.plymouth \
/etc/alternatives/default.plymouth
#initramfs 갱신
sudo update-initramfs -u
sudo reboot
초반 OS업데이트 처리
기기 업데이트 및 폴더생성과 바로가기 만들기
sudo apt update (저장소 업데이트)
sudo apt upgrade (업그레이드 시간오래걸림)
sudo apt update (업데이트 다시)
mkdir temp (temp폴더 생성)
mkdir MainApp (MainApp폴더 생성)
mkdir SubApp (SubApp폴더 생성)
mkdir WatchDog (SubApp폴더 생성)
touch ~/MainApp/start_main.sh (MainApp 바로가기 만들기)
touch ~/SubApp/start_sub.sh (SubApp 바로가기 만들기)
touch ~/WatchDog/toggle_watch_signal.sh (WatchDog Toggle 바로가기 만들기)
chmod +x ~/MainApp/start_main.sh (MainApp 바로가기 실행가능 하도록 변경)
chmod +x ~/SubApp/start_sub.sh (SubApp 바로가기 실행가능 하도록 변경)
chmod +x ~/WatchDog/toggle_watch_signal.sh (워치독 시그널 바로가기 실행가능 하도록 변경)
기초 그래픽 스택 및 Openbox 윈도우 매니저 설치
X-Server 그래픽 엔진과 경량 윈도우매니저 OpenBox 설치
# 패키지 저장소 새로고침
sudo apt update
# X11, 디스플레이 매니저, 오픈박스 설치
sudo apt install xorg lightdm openbox -y
폰트 설치, 한글 입력기, 한글 환경변수 설정
한글폰트 및 한글입력기(fcitx5) 설치 및 환경변수
# 한글 폰트 및 fcitx5 입력기 패키지 설치
sudo apt install fonts-nanum fonts-noto-cjk im-config fcitx5 fcitx5-hangul fcitx5-config-qt fcitx5-frontend-gtk2 fcitx5-frontend-gtk3 fcitx5-frontend-qt5 -y
# 입력기 프레임워크를 fcitx5로 지정
im-config -n fcitx5
# 리눅스 시스템 인코딩 환경에 한국어 언어 팩 생성 및 고정
sudo locale-gen ko_KR.UTF-8
sudo update-locale LANG=ko_KR.UTF-8 LANGUAGE=ko_KR:ko LC_ALL=ko_KR.UTF-8
# LightDM 로그인 세션 환경 변수 설정
nano ~/.xprofile 에 아래 내용 추가 (없으면 새로 생성)
export LANG=ko_KR.UTF-8
export LC_ALL=ko_KR.UTF-8
export XMODIFIERS=@im=fcitx
export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx
# 환경변수 파일 수정처리
sudo nano /etc/environment
# 제일 하단에 다음을 입력후 저장
LANG=ko_KR.UTF-8
LANGUAGE=ko_KR:ko
LC_ALL=ko_KR.UTF-8
한글 입력기 프로파일 구성
한글입력기(fcitx5) 프로파일을 구성한다.
# 프로파일 내용진입 하여 다음 내용으로 수정
mkdir ~/.config/fcitx5
sudo nano ~/.config/fcitx5/profile
[Groups/0]
# Group Name
Name=Default
# Layout
Default Layout=us
# Default Input Method
DefaultIM=keyboard-us
[Groups/0/Items/0]
# Name
Name=keyboard-us
# Layout
Layout=
[Groups/0/Items/1]
# Name
Name=hangul
# Layout
Layout=
[GroupOrder]
0=Default
VNC 서버 설치 및 보안 계정 패스워드 생성
원격지 화면 통제를 위해 VNC 서버를 설치하고 비밀번호를 고정
# x11vnc 패키지 엔진 설치
sudo apt install x11vnc -y
# 계정 홈 디렉토리 내부에 보안 인증서 폴더 생성
mkdir -p ~/.vnc
# 비밀번호 생성 및 저장 (패스워드 2회입력후, 저장여부 Y)
x11vnc -storepasswd ~/.vnc/passwd
LightDM 사용자 자동 로그인 처리
자동 로그인 orangepi로 로그인을 락인 시킴
# 구성 파일 에디터 열기
sudo nano /etc/lightdm/lightdm.conf
# 아래 내용으로 설정한후 저장
[Seat:*]
autologin-user=orangepi
autologin-user-timeout=0
user-session=openbox
(선택사항) HDMI 미장착시 가상 DISPLAY 활성화 설정
콘솔 기반 구동 시 가상 모니터를 생성하여 Headless 상태에서도 해상도 및 VNC 유지
# 가상 그래픽 드라이버 패키지 설치
sudo apt install xserver-xorg-video-dummy -y
# X11 가상 디스플레이 구성 파일 생성
sudo nano /etc/X11/xorg.conf.d/99-dummy.conf
# 아래 설정 내용 복사 후 저장 (1280x720 HD 지정)
Section "Device"
Identifier "DummyDevice"
Driver "dummy"
EndSection
Section "Monitor"
Identifier "DummyMonitor"
HorizSync 28.0-80.0
VertRefresh 48.0-75.0
EndSection
Section "Screen"
Identifier "DummyScreen"
Device "DummyDevice"
Monitor "DummyMonitor"
DefaultDepth 24
SubSection "Display"
Depth 24
Modes "1280x720"
EndSubSection
EndSection
오픈박스 환경 AutoStart 프로필 작성 및 메인앱 자동실행
오픈박스가 로드될때 순차적으로 자동 실행 (이후 재부팅: sudo reboot)
# 오픈박스 스타트업 구성 파일 폴더 및 파일 생성
mkdir -p ~/.config/openbox
sudo nano ~/.config/openbox/autostart
[ 아래 내용으로 설정한 후 저장 ]
# 1. 마우스 커서 기본 모양 설정
xsetroot -cursor_name left_ptr &
# 2. VNC 원격 서버 백그라운드 실행
x11vnc -auth guess -forever -loop -noxdamage -repeat -rfbauth /home/orangepi/.vnc/passwd -rfbport 5900 -shared &
# 3. 절전모드 사용안함
xset s off &
xset -dpms &
xset s noblank &
setterm -blank 0 -powersave off -powerdown 0 &
# 4. fcitx5 한글 입력기 데몬 실행
fcitx5 -d &
# 5. 시스템 안정화 후 메인 앱 자동 구동 (1초 대기 후 실행)
# 워치독을 데몬 서비스를 실행하면서 워치독을 통해 메인앱을 실행하고자 하는 경우 아래내용은 기재하지 않음
sleep 1
/home/orangepi/MainApp/start_main.sh &
오픈박스 디스플레이구성 작성
오픈박스가 로드될때 마우스와 키보드로 처리할 내용
(Ctrl+Shift+M:메인앱, Ctrl+Shift+S:보조앱, Ctrl+Shift+T:워치독토글, Ctrl+Alt+Delete:작업관리자, 기타등등..)
# 시스템 공용 폴더에서 원본파일을 유저 폴더로 복사
cp /etc/xdg/openbox/rc.xml ~/.config/openbox/rc.xml
# 설정파일을 아래의 xml로 변경처리
# 타이핑 보다는 파일질라로 복사처리 권장
[ 아래의 사항으로 변경 ]
<?xml version="1.0" encoding="UTF-8"?>
<openbox_config xmlns="http://openbox.org/3.4/rc" xmlns:xi="http://www.w3.org/2001/XInclude">
<resistance><strength>10</strength><screen_edge_strength>20</screen_edge_strength></resistance>
<focus><focusNew>yes</focusNew><followMouse>no</followMouse><focusLast>yes</focusLast><underMouse>no</underMouse><focusDelay>200</focusDelay><raiseOnFocus>no</raiseOnFocus></focus>
<placement><policy>Smart</policy><center>yes</center><monitor>Primary</monitor><primaryMonitor>1</primaryMonitor></placement>
<theme>
<name>Clearlooks</name><titleLayout>NLIMC</titleLayout><keepBorder>yes</keepBorder><animateIconify>yes</animateIconify>
<font place="ActiveWindow"><name>sans</name><size>8</size><weight>bold</weight><slant>normal</slant></font>
<font place="InactiveWindow"><name>sans</name><size>8</size><weight>bold</weight><slant>normal</slant></font>
<font place="MenuHeader"><name>sans</name><size>9</size><weight>normal</weight><slant>normal</slant></font>
<font place="MenuItem"><name>sans</name><size>9</size><weight>normal</weight><slant>normal</slant></font>
</theme>
<desktops>
<number>1</number><firstdesk>1</firstdesk><names></names><popupTime>0</popupTime>
</desktops>
<resize><drawContents>yes</drawContents><popupShow>Nonpixel</popupShow><popupPosition>Center</popupPosition></resize>
<margins><top>0</top><bottom>0</bottom><left>0</left><right>0</right></margins>
<keyboard>
<chainQuitKey>C-g</chainQuitKey>
<keybind key="W-d"><action name="ToggleShowDesktop"/></keybind>
<keybind key="A-F4"><action name="Close"/></keybind>
<keybind key="A-Escape"><action name="Lower"/><action name="FocusToBottom"/><action name="Unfocus"/></keybind>
<keybind key="A-space"><action name="ShowMenu"><menu>client-menu</menu></action></keybind>
<keybind key="Print"><action name="Execute"><command>scrot</command></action></keybind>
<keybind key="C-S-m">
<action name="Execute">
<command>/home/orangepi/MainApp/start_main.sh</command>
</action>
<action name="Focus"/>
<action name="Raise"/>
</keybind>
<keybind key="C-S-s">
<action name="Execute">
<command>/home/orangepi/SubApp/start_sub.sh</command>
</action>
<action name="Focus"/>
<action name="Raise"/>
</keybind>
<keybind key="C-S-t">
<action name="Execute">
<command>/home/orangepi/WatchDog/toggle_watch_signal.sh</command>
</action>
<action name="Focus"/>
<action name="Raise"/>
</keybind>
<keybind key="C-A-Delete">
<action name="Execute">
<command>lxterminal --title="Task Manager" -e htop</command>
</action>
<action name="Focus"/>
<action name="Raise"/>
</keybind>
</keyboard>
<mouse>
<dragThreshold>1</dragThreshold><doubleClickTime>500</doubleClickTime><screenEdgeWarpTime>0</screenEdgeWarpTime><screenEdgeWarpMouse>false</screenEdgeWarpMouse>
<context name="Frame">
<mousebind button="A-Left" action="Press"><action name="Focus"/><action name="Raise"/></mousebind>
<mousebind button="A-Left" action="Drag"><action name="Move"/></mousebind>
<mousebind button="A-Right" action="Drag"><action name="Resize"/></mousebind>
</context>
<context name="Titlebar">
<mousebind button="Left" action="Drag"><action name="Move"/></mousebind>
<mousebind button="Left" action="DoubleClick"><action name="ToggleMaximize"/></mousebind>
</context>
<context name="Client">
<mousebind button="Left" action="Press"><action name="Focus"/><action name="Raise"/></mousebind>
<mousebind button="Middle" action="Press"><action name="Focus"/><action name="Raise"/></mousebind>
<mousebind button="Right" action="Press"><action name="Focus"/><action name="Raise"/></mousebind>
</context>
<context name="Desktop">
<mousebind button="Left" action="Press"><action name="Focus"/><action name="Raise"/></mousebind>
<mousebind button="Right" action="Press"><action name="Focus"/><action name="Raise"/></mousebind>
</context>
<context name="Root">
<mousebind button="Right" action="Press"><action name="ShowMenu"><menu>root-menu</menu></action></mousebind>
</context>
</mouse>
<menu><file>menu.xml</file><hideDelay>200</hideDelay><middle>no</middle><submenuShowDelay>100</submenuShowDelay><submenuHideDelay>400</submenuHideDelay><showIcons>yes</showIcons><manageDesktops>no</manageDesktops></menu>
</openbox_config>
NTP 시간 동기화 서버 구축 (Chrony)
장비의 물리적 시간 오차방지용 타임데몬 Chrony 설치 및 락인
# 1. 크로니 패키지 설치 및 활성화
sudo apt update
sudo apt install chrony -y
sudo systemctl enable chrony
sudo systemctl start chrony
# 2. 타임 서버 설정 수정 (인터넷 가능 환경 또는 현장 NTP 서버 지정)
sudo nano /etc/chrony/chrony.conf
# [기존 pool 대신에 한국 표준 타임 서버 등록 (지우고 아래로 변경)]
pool time.bora.net iburst
pool time.kornet.net iburst
pool ntp.gwnn.rm.kr iburst
# 3. 크로니 데몬 재시작 및 동기화 상태 현장 검증
sudo systemctl restart chrony
chronyc tracking (현재 시간 동기화 추적 상태 확인)
chronyc sources -v (연결된 외부 타임 서버 리스트 및 상태 확인)
유틸리티 설치
보조 유틸리티 설치
# 정비용 유틸리티 (메모장, 터미널, 탐색기, 파일질라, 이미지뷰어) 설치
sudo apt update
sudo apt install -y mousepad lxterminal pcmanfm filezilla viewnior scrot network-manager-gnome
오픈박스 마우스 우클릭 메뉴설정
오픈박스 바탕화면에 나올 메뉴 구성 및 sh 스크립트 연동
# 메뉴판 구성 파일 에디터 열기
nano ~/.config/openbox/menu.xml
# [아래의 내용으로 변경]
<?xml version="1.0" encoding="utf-8"?>
<openbox_menu xmlns="http://openbox.org/3.4/menu">
<menu id="root-menu" label="EQUIPMENT CONTROL">
<separator label="[ RUN PROGRAM ]" />
<item label="메인 App">
<action name="Execute"><command>/home/orangepi/MainApp/start_main.sh</command></action>
</item>
<item label="보조 App">
<action name="Execute"><command>/home/orangepi/SubApp/start_sub.sh</command></action>
</item>
<separator label="[ REPAIR TOOL ]" />
<item label="메모장">
<action name="Execute"><command>mousepad</command></action>
</item>
<item label="터미널">
<action name="Execute"><command>lxterminal</command></action>
</item>
<item label="파일 탐색기">
<action name="Execute"><command>pcmanfm</command></action>
</item>
<separator />
<item label="장비 재부팅 (Reboot)">
<action name="Execute"><command>systemctl reboot</command></action>
</item>
</menu>
</openbox_menu>
한국어 입력 자판 GUI 설정
한국어 입력 자판 GUI 설정 (configtool 이용)
# 바탕화면 터미널 메뉴로 키보드 설정진입
fcitx5-configtool
# 좌측 입력기에 다음 순서로 배치처리
키보드 - Korean - Korean (101/104)
한글(Hangul)
# 이렇게 설정하고 Apply
메인 App 구동 쉘 스크립트 작성 (start_main.sh)
메인 앱 실행 스크립트 작성
# 메인 앱 디렉토리로 이동 및 스크립트 생성
cd ~/MainApp
nano start_main.sh
# [아래의 내용으로 변경]
#!/bin/bash
export DISPLAY=:0
export XMODIFIERS=@im=fcitx
export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx
# 프로그램 실행 경로로 이동
cd /home/orangepi/MainApp
# 메인 애플리케이션 실행 (실제 구동할 바이너리나 파이썬 파일 지정)
# 예: python3 main_gui.py 또는 ./.NET바이너리 등
/usr/bin/python3 main_gui.py
# 작성 완료 후 저장(Ctrl+O, Enter, Ctrl+X)
서브 App 구동 쉘 스크립트 작성 (start_sub.sh)
서브 앱 실행 스크립트 작성
# 서브 앱 디렉토리로 이동 및 스크립트 생성
cd ~/SubApp
nano start_sub.sh
# [아래의 내용으로 변경]
#!/bin/bash
export DISPLAY=:0
export XMODIFIERS=@im=fcitx
export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx
# 프로그램 실행 경로로 이동
cd /home/orangepi/SubApp
# 메인 애플리케이션 실행 (실제 구동할 바이너리나 파이썬 파일 지정)
# 예: python3 main_gui.py 또는 ./.NET바이너리 등
/usr/bin/python3 sub_gui.py
# 작성 완료 후 저장(Ctrl+O, Enter, Ctrl+X)
와치독앱 데몬서비스 등록
와치독앱을 데몬서비스로 등록하여 죽지않도록 설정
# 서비스 파일 생성
sudo nano /etc/systemd/system/mywatchdog.service
# [아래의 내용으로 변경]
[Unit]
Description=System WatchDog Service
After=graphical.target
[Service]
Type=simple
User=orangepi
WorkingDirectory=/home/orangepi/WatchDog
ExecStart=/usr/bin/python3 /home/orangepi/WatchDog/myWatchDog.py
Restart=always
RestartSec=3
Environment=DISPLAY=:0
[Install]
WantedBy=graphical.target
#서비스 활성화 및 가동
sudo systemctl daemon-reload
sudo systemctl enable mywatchdog.service
sudo systemctl start mywatchdog.service
파이썬 모듈설치와 와치독 스크립트 작성
파이썬을 이용하여 워치독을 처리함 (화면에 상관없이 Ctrl+Shift+T)
# 파이썬 pip 모듈 업데이트
sudo apt update
sudo apt install -y python3-pip python3-setuptools
# 파이썬 기본 GUI TKinter 설치
sudo apt install -y python3-tk
# 워치독 스크립트 파일 생성
(WatchAppName.txt 파일에는 '프로세스명|파라미터명' 으로 기입)
touch ~/WatchDog/WatchAppName.txt
touch ~/WatchDog/myWatchDog.py
touch ~/WatchDog/toggle_watch_signal.py
# 초기에 생성했던 toggle_watch_signal.sh 파일에 내용넣기
sudo nano ~/WatchDog/toggle_watch_signal.sh
# [ 파일내용은 아래의 내용으로 기재 ]
#!/bin/bash
/usr/bin/python3 /home/orangepi/WatchDog/toggle_watch_signal.py
# [toggle_watch_signal.py] 파일내용 (복사추천)
import os
import sys
import signal
import subprocess
#
TARGET_SCRIPT_SIGNATURE = "myWatchDog.py"
#
try:
output = subprocess.check_output(["ps", "-ef"]).decode('utf-8')
daemon_pid = None
#
for line in output.splitlines():
if TARGET_SCRIPT_SIGNATURE in line and "toggle_watch_signal" not in line:
parts = line.split()
if len(parts) > 1:
daemon_pid = int(parts[1])
break
#
if daemon_pid:
os.kill(daemon_pid, signal.SIGUSR1)
print(f"(PID: {daemon_pid}에 시그널 전송 완료", flush=True)
else:
print(f"실행중인 {TARGET_SCRIPT_SIGNATURE} 스크립트를 찾을 수 없습니다.", flush=True)
except Exception as e:
print(f"시그널 전송 실패: {e}", file=sys.stderr, flush=True)
sys.exit(1)
# [myWatchDog.py] 파일내용 (복사추천)
import os
import sys
import time
import signal
import subprocess
import tkinter as tk
# 설정 파일 경로
CONFIG_FILE = "/home/orangepi/WatchDog/WatchAppName.txt"
WATCH_APP_CMD = "bash /home/orangepi/MainApp/start_main.sh"
# 초기 실행 시 감시할 프로세스 이름 읽기
if not os.path.exists(CONFIG_FILE):
print(f"에러: {CONFIG_FILE} 파일이 존재하지 않습니다.")
sys.exit(1)
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
config_content = f.read().strip()
if not config_content:
print("에러: WatchAppName.txt 파일이 비어 있습니다.")
sys.exit(1)
# '|' 기호로 쪼갠 후 필터링 처리
WATCH_KEYWORDS = [kw.strip() for kw in config_content.split('|') if kw.strip()]
if not WATCH_KEYWORDS:
print("유효한 감시 키워드가 없습니다.")
sys.exit(1)
# 감시 활성화 상태 플래그
is_watching = True
is_osd_running = False
# 해당 키워드의 프로세스가 돌고 있는지 확인
def is_process_running(keywords):
try:
output = subprocess.check_output(["ps", "-ef"]).decode('utf-8')
for line in output.splitlines():
# 자기 자신이나 좀비 프로세스는 필터링에서 예외 처리
if "myWatchDog" in line or "defunct" in line:
continue
#
match_all = True
for kw in keywords:
if kw not in line:
match_all = False
break
#
if match_all:
return True
except Exception:
pass
return False
# 화면 중앙에 잠깐 떴다 사라지는 OSD 레이어
def show_osd_message(message, color):
global is_osd_running
is_osd_running = True
root = tk.Tk()
root.overrideredirect(True)
root.attributes("-topmost", True)
w, h = 420, 80
x = (root.winfo_screenwidth() - w) // 2
y = (root.winfo_screenheight() - h) // 2
root.geometry(f"{w}x{h}+{x}+{y}")
#
label = tk.Label(root, text=message, font=("NanumGothic", 15, "bold"), bg="#1e1e1e", fg=color, bd=2, relief="solid")
label.pack(fill="both", expand=True)
#
def on_destory():
global is_osd_running
root.destroy()
is_osd_running = False
root.after(1200, on_destory)
root.mainloop()
# 시그널 수신 시 처리 핸들러
def handle_toggle_sign(signum, frame):
global is_watching, is_osd_running
#
if is_osd_running:
print("OSD 연타 시그널 무시")
return
is_watching = not is_watching
if is_watching:
show_osd_message("WatchDog 감시 재개", "#00ffcc")
else:
show_osd_message("WatchDog 일시정지", "#ff3333")
# 메인 루프 가동부
if __name__ == "__main__":
signal.signal(signal.SIGUSR1, handle_toggle_sign)
print(f"WatchDog 감시 가동 시작: {WATCH_KEYWORDS}")
#
while True:
if is_watching:
if not is_process_running(WATCH_KEYWORDS):
print("메인앱 다운 감지! 스크립트 실행...")
subprocess.Popen(WATCH_APP_CMD, shell=True, env=os.environ.copy())
# time.sleep으로 체킹 인터벌 부여 (5초)
time.sleep(5)
재부팅하여 결과를 확인한다
2026년 6월 12일 금요일
닷넷의 시그널링 기법
.NET에서 async, await, Task가 등장한 이후 비동기 프로그래밍 방식은 크게 변화하였다.
과거에는 스레드 동기화 객체를 사용하여 작업 완료를 기다렸지만, 현재는 Task 기반 비동기 모델을 통해 보다 효율적인 구현이 가능하다.
이번 글에서는 비동기 환경에서 자주 사용되는 시그널링(Signaling) 패턴과 TaskCompletionSource를 활용한 현대적인 구현 방법을 소개한다.
문제 상황
다음과 같은 구조를 생각해보자.
- F1()은 int 값을 반환한다.
- 호출자는 await F1()으로 결과를 기다린다.
- 실제 결과는 F1()이 아니라 F2()에서 생성된다.
- F2()는 결과 생성 시점에 F1()의 대기를 해제해야 한다.
즉, 결과를 기다리는 주체와 결과를 생성하는 주체가 서로 다른 상황이다.
기존 방식 : ManualResetEvent
private int _result;
private readonly ManualResetEvent _event = new(false);
private async Task<int> F1()
{
_event.Reset();
await Task.Run(() => _event.WaitOne());
return _result;
}
private void F2(int result)
{
_result = result;
_event.Set();
}
동작 과정
문제점
- 대기 중인 스레드를 실제로 점유한다.
- ThreadPool 자원을 낭비한다.
- async/await 기반 설계와 잘 맞지 않는다.
현대적인 방식 : TaskCompletionSource
private TaskCompletionSource<int>? _resultTcs;
private Task<int> F1()
{
_resultTcs = new TaskCompletionSource<int>();
return _resultTcs.Task;
}
private void F2(int result)
{
_resultTcs?.TrySetResult(result);
}
호출부
int result = await F1();
동작 과정
실무 권장 패턴
실제 서비스 코드에서는 RunContinuationsAsynchronously 옵션을 함께 사용하는 것을 권장한다.
private TaskCompletionSource<int>? _resultTcs;
public Task<int> F1()
{
_resultTcs =
new TaskCompletionSource<int>(
TaskCreationOptions.RunContinuationsAsynchronously);
return _resultTcs.Task;
}
왜 필요한가?
기본 TaskCompletionSource는 TrySetResult() 호출 시 await 이후 코드가 같은 스레드에서 즉시 실행될 수 있다.
int result = await F1(); DoSomething();
즉, 결과를 전달하려던 코드가 소비자 측 후속 로직까지 실행하게 될 수 있다.
RunContinuationsAsynchronously 적용 후
- 예측 가능한 실행 흐름
- 데드락 위험 감소
- UI 응답성 향상
- 생산자와 소비자 역할 분리
결론
ManualResetEvent는 스레드를 점유하는 블로킹 방식이다.
TaskCompletionSource는 스레드 점유 없이 결과 완료 시점을 외부에서 제어할 수 있으며, 현대적인 .NET 비동기 프로그래밍에 가장 적합한 시그널링 기법이다.

