using System.Drawing.Drawing2D; using System.Net; using System.Net.Sockets; using System.Text; namespace ControlServer { public partial class Form1 : Form { private List terminalList = new List(); // 리스트 생성 private Dictionary _connectedAgents = new Dictionary(); 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) { } } }