초기 커밋.

This commit is contained in:
chainsecure
2026-05-28 14:48:48 +09:00
parent 377498fae8
commit 5d6ee943f6
31 changed files with 3182 additions and 0 deletions

503
ControlServer/Form1.cs Normal file
View File

@@ -0,0 +1,503 @@
using System.Drawing.Drawing2D;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace ControlServer
{
public partial class Form1 : Form
{
private List<TerminalInfo> terminalList = new List<TerminalInfo>(); // 리스트 생성
private Dictionary<string, TcpClient> _connectedAgents = new Dictionary<string, TcpClient>();
private TcpListener _listener;
private bool _isRunning = false;
private string _connectedIP = "";
private System.Windows.Forms.Timer _statusTimer;
private System.Windows.Forms.Timer _dataSyncTimer;
public Form1()
{
InitializeComponent();
this.Load += Form1_Load;
pannGray.BackColor = Color.Transparent; // 배경색 초기화
pannBlue.BackColor = Color.Transparent; // 배경색 초기화
grdTotList.RowHeadersVisible = false; // 그리드 자동으로 생성되는 첫 번째 열 제거
grdTotList.AllowUserToAddRows = false; // 새 행 입력용 기본 로우 제거
grdTotList.EnableHeadersVisualStyles = false; // 헤더 기본 테마 사용 안 함
grdTotList.ColumnHeadersDefaultCellStyle.BackColor = Color.LightGray; // 헤더 배경색
grdTotList.ColumnHeadersHeight = 40; // 헤더 높이
grdTotList.AllowUserToResizeRows = false; // 행 높이 조절 금지
grdTotList.AllowUserToResizeColumns = false; // 열 너비 조절 금지
grdTotList.ReadOnly = true; // 읽기 전용
grdTotList.ClearSelection();
txtSearch.PlaceholderText = "검색(단말기명, IP)";
}
// 가짜 데이터 로드용 함수 (테스트용 예시이며 필요 시 구현체 연결)
private void LoadSavedTerminals()
{
lblTotCnt.Text = grdTotList.RowCount.ToString() + "대";
}
// 폼이 화면에 나타날 때 서버를 별도의 통로(Task)로 실행
private void Form1_Load(object sender, EventArgs e)
{
LoadSavedTerminals(); // 기존에 저장된 단말기 데이터 리스트 로드
InitStatusTimer(); // 에이전트pc 상태 여부 확인
if (grdTotList.Rows.Count > 0)
{
// 등록된 단말기가 있다면 상태 체크 스레드 및 서버 리스너 시작
_ = Task.Run(() => StartServer());
_ = Task.Run(() => CheckAllAlliveStatus()); // 상시 또는 최초 상태 체크
}
else
{
// 등록된 단말기가 하나도 없다면 리스너를 구동하지 않음
MessageBox.Show("등록된 단말기가 없어 서버를 대기 상태로 유지합니다. 단말기를 등록해주세요.");
}
}
private void InitStatusTimer()
{
_statusTimer = new System.Windows.Forms.Timer();
_statusTimer.Interval = 5000; // 5초마다 체크
_statusTimer.Tick += (s, e) => {
CheckAllAlliveStatus();
};
_statusTimer.Start();
}
// 기존 등록된 단말기들의 현재 On/Off 상태를 체크하는 함수
private void CheckAllAlliveStatus()
{
foreach (DataGridViewRow row in grdTotList.Rows)
{
if (row.IsNewRow) continue;
string ip = row.Cells[2].Value?.ToString();
if (string.IsNullOrEmpty(ip)) continue;
// 현재 서버에 소켓이 활성화되어 연결되어 있는지 확인
bool isAlive = false;
lock (_connectedAgents)
{
if (_connectedAgents.ContainsKey(ip))
{
TcpClient client = _connectedAgents[ip];
// 소켓이 연결되어 있고, 실제 연결 상태가 양호한지 체크
if (client != null && client.Connected)
{
isAlive = true;
}
}
}
// 만약 소켓이 연결 안 되어 있다면 보조 수단으로 원래의 PingTest를 수행해볼 수 있음
if (!isAlive)
{
isAlive = PingTest(ip);
}
this.Invoke(new Action(() => {
if (isAlive)
{
row.Cells[4].Value = "Start";
row.Cells[4].Style.BackColor = Color.Green;
row.Cells[4].Style.ForeColor = Color.White;
}
else
{
row.Cells[4].Value = "Stop";
row.Cells[4].Style.BackColor = Color.Red;
row.Cells[4].Style.ForeColor = Color.White;
}
row.Cells[4].Style.Alignment = DataGridViewContentAlignment.MiddleCenter;
}));
}
this.Invoke(new Action(() => UpdateRunningCount()));
}
private bool PingTest(string ip)
{
if (string.IsNullOrEmpty(ip)) return false;
try
{
using (System.Net.NetworkInformation.Ping ping = new System.Net.NetworkInformation.Ping())
{
var reply = ping.Send(ip, 1000); // 1초 타임아웃
return reply.Status == System.Net.NetworkInformation.IPStatus.Success;
}
}
catch { return false; }
}
// 서버 시작 함수
private async Task StartServer()
{
try
{
if (_isRunning) return; // 이미 실행 중이면 중복 방지
_listener = new TcpListener(IPAddress.Any, 9999);
_listener.Start();
MessageBox.Show("서버 연결 중...");
_isRunning = true;
while (_isRunning)
{
// 에이전트의 접속을 비동기로 대기
TcpClient client = await _listener.AcceptTcpClientAsync();
// 에이전트 접속 처리 핸들러 실행
_ = Task.Run(() => HandleAgent(client));
}
}
catch (Exception ex)
{
// 리스너가 중단되었을 때의 예외 처리 제외 처리
if (_isRunning)
{
MessageBox.Show("서버 구동 오류: " + ex.Message);
}
}
}
// 에이전트에서 데이터 받기
private void HandleAgent(TcpClient client)
{
try
{
string clientIP = ((IPEndPoint)client.Client.RemoteEndPoint).Address.ToString();
// using을 쓰지 않고 직접 스트림과 리더를 생성(함수가 끝나도 닫히지 않게)
NetworkStream stream = client.GetStream();
System.IO.StreamReader reader = new System.IO.StreamReader(stream, Encoding.UTF8);
// 에이전트가 보낸 첫 줄(MAC 주소)
string receivedMac = reader.ReadLine();
if (!string.IsNullOrEmpty(receivedMac))
{
this.Invoke(new Action(() => {
// 기존 연결이 있다면 안전하게 닫고 교체
if (_connectedAgents.ContainsKey(clientIP))
{
try { _connectedAgents[clientIP].Close(); } catch { }
}
_connectedAgents[clientIP] = client; // 소켓 유지
UpdateAgentStatusByMac(receivedMac, clientIP);
}));
// 서버 소켓이 닫히거나 에이전트가 끊어질 때까지
// 이 스레드가 종료되지 않고 백그라운드에서 계속 대기하도록 루프를 돌려줌
// (에이전트로부터 추가적인 메시지를 받을 때도 여기서 처리 가능)
while (client.Connected)
{
// 에이전트가 보낸 데이터가 없다면 여기서 대기
// 에이전트가 연결을 끊으면 null이 들어와 루프를 빠져나감
string agentMsg = reader.ReadLine();
if (agentMsg == null) break;
}
}
}
catch (Exception ex)
{
Console.WriteLine("서버 핸들러 오류: " + ex.Message);
}
finally
{
// 연결이 최종적으로 끊기면 딕셔너리에서 제거 및 리소스 정리
try
{
string clientIP = ((IPEndPoint)client.Client.RemoteEndPoint).Address.ToString();
lock (_connectedAgents)
{
if (_connectedAgents.ContainsKey(clientIP))
{
_connectedAgents.Remove(clientIP);
}
}
}
catch { }
client.Close();
}
}
// MAC 주소를 기반으로 에이전트 매칭 및 상태 업데이트
private void UpdateAgentStatusByMac(string clientMac, string clientIP)
{
// 수신된 MAC에서 불필요한 문자 제거
string cleanClientMac = clientMac.Replace("-", "").Replace(":", "").Replace(" ", "").ToUpper().Trim();
bool isMatched = false;
this.Invoke(new Action(() => {
foreach (DataGridViewRow row in grdTotList.Rows)
{
string gridIp = row.Cells[2].Value.ToString();
if (gridIp == clientIP)
{
row.Cells[1].Value = cleanClientMac;
row.Cells[4].Value = "Start";
row.Cells[4].Style.BackColor = Color.Green;
row.Cells[4].Style.ForeColor = Color.White;
row.Cells[5].Value = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
isMatched = true;
break;
}
}
if (!isMatched)
{
Console.WriteLine($"매칭 실패! 수신된 MAC: {cleanClientMac}");
}
UpdateRunningCount();
}));
}
// 상단의 프로그램 실행 대수 갱신용 공통 메서드
private void UpdateRunningCount()
{
int startCount = 0;
foreach (DataGridViewRow row in grdTotList.Rows)
{
if (row.IsNewRow) continue;
if (row.Cells[4].Value?.ToString() == "Start")
{
startCount++;
}
}
lblRunningCnt.Text = startCount.ToString() + "대";
}
private void btnRegister_MouseClick(object sender, MouseEventArgs e)
{
// 단말기 등록 팝업 호출
Form2 form = new Form2();
if (form.ShowDialog() == DialogResult.OK)
{
TerminalInfo data = form.TerminalData;
grdTotList.Rows.Add(
data.TerminalName, // 단말기명
data.Mac, // MAC 주소
data.IP, // IP
"설정", // 통신설정 버튼
"Stop", // 기본 상태
DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
data.Port // 포트번호
);
int rowIndex = grdTotList.Rows.Count - 1;
// 프로그램 열 스타일 지정
DataGridViewCell cell4 = grdTotList.Rows[rowIndex].Cells[4];
cell4.Style.BackColor = Color.Red;
cell4.Style.ForeColor = Color.White;
cell4.Style.Alignment = DataGridViewContentAlignment.MiddleCenter;
// 통신설정 열 스타일 지정
DataGridViewCell cell3 = grdTotList.Rows[rowIndex].Cells[3];
cell3.Style.BackColor = Color.LightGray;
cell3.Style.ForeColor = Color.Black;
cell3.Style.Alignment = DataGridViewContentAlignment.MiddleCenter;
// 현재 단말기 개수 업데이트
lblTotCnt.Text = grdTotList.RowCount.ToString() + "대";
// 단말 등록 완료 후 리스너가 구동 중이 아니었다면 리스너 최초 구동 개시
if (!_isRunning)
{
_ = Task.Run(() => StartServer());
}
// 기접속된 IP가 있는 장치라면 바로 연동 매칭
if (!string.IsNullOrEmpty(_connectedIP) && _connectedIP == data.IP)
{
UpdateAgentStatusByMac(data.Mac, data.IP);
}
}
}
private void btnDataUpdate_MouseClick(object sender, MouseEventArgs e)
{
// 1. 메시지 전송
SendDataToAllAgents("데이터 전송 테스트(수동)");
// 2. 기존 로직
Popup popup = new Popup("전체 단말기에 데이터 전송을 시도했습니다.");
popup.ShowDialog();
CheckAllAlliveStatus();
}
private void btnLog_MouseClick(object sender, MouseEventArgs e)
{
Form4 form = new Form4();
form.Show();
}
private void grdTotList_CellMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
grdTotList.ClearSelection();
// 헤더 클릭 방지
if (e.RowIndex < 0) return;
// 통신설정 버튼 열 클릭 시
if (e.ColumnIndex == 3)
{
var row = grdTotList.Rows[e.RowIndex];
string TerminalName = row.Cells[0].Value?.ToString() ?? "";
string TerminalIp = row.Cells[2].Value?.ToString() ?? "";
Form3 form = new Form3(TerminalName, TerminalIp);
form.ShowDialog();
}
// 프로그램 작동 제어 제어 열 클릭 시
else if (e.ColumnIndex == 4)
{
DataGridViewRow row = grdTotList.Rows[e.RowIndex];
DataGridViewCell cell4 = row.Cells[4];
string status = cell4.Value?.ToString() ?? "";
string targetIP = row.Cells[2].Value?.ToString() ?? "";
string targetMac = row.Cells[1].Value?.ToString() ?? "";
if (status == "Start")
{
// 에이전트 PC로 원격 강제 종료 패킷 전달
SendStopCommandToAgent(targetIP);
cell4.Value = "Stop";
cell4.Style.BackColor = Color.Red;
cell4.Style.ForeColor = Color.White;
}
else
{
// WOL 매직 패킷 송신
if (!string.IsNullOrEmpty(targetMac))
{
SendWakeOnLan(targetMac);
cell4.Value = "Start";
cell4.Style.BackColor = Color.Green;
cell4.Style.ForeColor = Color.White;
}
else
{
MessageBox.Show("등록된 MAC 주소가 없습니다.");
}
}
cell4.Style.Alignment = DataGridViewContentAlignment.MiddleCenter;
UpdateRunningCount();
}
}
// [원격 끄기 구현] 소켓 스트림 연동을 위한 에이전트 대상 종료 전송 함수
private void SendStopCommandToAgent(string ip)
{
try
{
if (_connectedAgents.ContainsKey(ip))
{
TcpClient client = _connectedAgents[ip];
if (client.Connected)
{
// 중요: 뒤에 "\n"을 추가하여 에이전트의 ReadLine()이 끝을 인식하게 합니다.
byte[] msg = Encoding.UTF8.GetBytes("STOP\n");
client.GetStream().Write(msg, 0, msg.Length);
client.GetStream().Flush();
}
}
else
{
MessageBox.Show("에이전트가 서버에 연결되어 있지 않습니다.");
}
}
catch (Exception ex)
{
MessageBox.Show("종료 명령 전송 실패: " + ex.Message);
}
}
// WOL 매직 패킷 전송 함수
private void SendWakeOnLan(string macAddress)
{
try
{
string cleanMac = macAddress.Replace("-", "").Replace(":", "").Replace(" ", "").Trim();
if (cleanMac.Length != 12)
{
MessageBox.Show("올바른 MAC 주소 형식이 아닙니다.");
return;
}
byte[] bytes = new byte[102];
for (int i = 0; i < 6; i++)
{
bytes[i] = 0xFF;
}
byte[] macBytes = new byte[6];
for (int i = 0; i < 6; i++)
{
macBytes[i] = Convert.ToByte(cleanMac.Substring(i * 2, 2), 16);
}
for (int i = 1; i <= 16; i++)
{
Array.Copy(macBytes, 0, bytes, i * 6, 6);
}
using (UdpClient client = new UdpClient())
{
client.Connect(IPAddress.Parse("255.255.255.255"), 9);
client.Send(bytes, bytes.Length);
}
}
catch (Exception ex)
{
MessageBox.Show("WOL 전송 실패: " + ex.Message);
}
}
private void SendDataToAllAgents(string message)
{
// 메시지 끝에 \n을 붙여서 에이전트가 ReadLine()으로 잘 읽게함
byte[] data = Encoding.UTF8.GetBytes(message + "\n");
lock (_connectedAgents) // 여러 스레드에서 접근할 수 있으므로 lock 사용
{
foreach (var kvp in _connectedAgents)
{
try
{
TcpClient client = kvp.Value;
if (client != null && client.Connected)
{
NetworkStream stream = client.GetStream();
stream.Write(data, 0, data.Length);
stream.Flush();
}
}
catch (Exception ex)
{
Console.WriteLine($"전송 실패 ({kvp.Key}): {ex.Message}");
}
}
}
}
private void pannGray_Paint(object sender, PaintEventArgs e) { }
private void pannBlue_Paint(object sender, PaintEventArgs e) { }
}
}