using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; using System.Threading; namespace NoticeServer { /// /// TCP 채팅 서버 /// public class TcpChatServer { private TcpListener tcpListener; private Thread acceptThread; private bool isRunning; private int tcpPort; private int maxClients; private readonly object clientsLock = new object(); private List clients = new List(); public TcpChatServer(int tcpPort, int maxClients = 100) { this.tcpPort = tcpPort; this.maxClients = maxClients; } /// /// TCP 채팅 서버 시작 /// public void Start() { if (isRunning) return; try { tcpListener = new TcpListener(IPAddress.Any, tcpPort); tcpListener.Start(); isRunning = true; acceptThread = new Thread(AcceptLoop) { IsBackground = true, Name = "TcpAcceptThread" }; acceptThread.Start(); Console.WriteLine($"[TCP] Chat server started (Port: {tcpPort})"); Console.WriteLine($"[TCP] Max clients: {maxClients}"); } catch (Exception ex) { Console.WriteLine($"[ERROR] Failed to start TCP server: {ex.Message}"); isRunning = false; } } /// /// 클라이언트 연결 수락 루프 /// private void AcceptLoop() { while (isRunning) { try { TcpClient tcpClient = tcpListener.AcceptTcpClient(); lock (clientsLock) { if (clients.Count >= maxClients) { Console.WriteLine("[REJECTED] Maximum clients exceeded"); tcpClient.Close(); continue; } } var client = new ChatClient(tcpClient); client.MessageReceived += OnClientMessageReceived; client.Disconnected += OnClientDisconnected; lock (clientsLock) { clients.Add(client); } Console.WriteLine($"[CONNECTED] New client: {client.IpAddress} (ID: {client.ClientId})"); } catch (SocketException) { // Server shutdown break; } catch (Exception ex) { Console.WriteLine($"[ERROR] Client accept error: {ex.Message}"); } } } /// /// 클라이언트 메시지 수신 이벤트 핸들러 /// private void OnClientMessageReceived(object sender, ChatMessage message) { var client = sender as ChatClient; if (client == null) return; Console.WriteLine($"[RECEIVED] {client.NickName ?? client.IpAddress}: {message.Type} - {message.Content}"); switch (message.Type) { case MessageType.Connect: HandleConnect(client, message); break; case MessageType.Chat: HandleChat(client, message); break; case MessageType.Whisper: HandleWhisper(client, message); break; case MessageType.UserListRequest: HandleUserListRequest(client); break; case MessageType.Ping: HandlePing(client); break; case MessageType.Disconnect: client.Disconnect(); break; } } /// /// 연결 처리 /// private void HandleConnect(ChatClient client, ChatMessage message) { // Check employee ID duplication lock (clientsLock) { if (!string.IsNullOrEmpty(message.EmployeeId) && clients.Any(c => c.ClientId != client.ClientId && c.EmployeeId == message.EmployeeId)) { var errorMsg = new ChatMessage { Type = MessageType.Notice, Content = "Employee ID already connected.", Timestamp = DateTime.Now }; client.SendMessage(errorMsg); client.Disconnect(); return; } } Console.WriteLine($"[LOGIN] {message.NickName} ({message.EmployeeId}, {message.UserGroup}) from {client.IpAddress}, {message.HostName}"); // Welcome message var welcomeMsg = new ChatMessage { Type = MessageType.Notice, Content = $"Welcome to the chat server, {message.NickName}!", Timestamp = DateTime.Now }; client.SendMessage(welcomeMsg); // Notify other users var noticeMsg = new ChatMessage { Type = MessageType.Notice, Content = $"{message.NickName} ({message.EmployeeId}) has joined the chat.", Timestamp = DateTime.Now }; BroadcastMessage(noticeMsg, client.ClientId); } /// /// 채팅 메시지 처리 (1:1 messaging) /// private void HandleChat(ChatClient client, ChatMessage message) { // Set sender information message.NickName = client.NickName; message.EmployeeId = client.EmployeeId; message.UserGroup = client.UserGroup; message.IpAddress = client.IpAddress; message.HostName = client.HostName; message.Timestamp = DateTime.Now; // Send to target recipient (1:1 messaging) if (!string.IsNullOrEmpty(message.TargetEmployeeId)) { lock (clientsLock) { // Find target by employee ID var targetClient = clients.FirstOrDefault(c => c.EmployeeId == message.TargetEmployeeId); if (targetClient != null) { // Send to target targetClient.SendMessage(message); // Echo back to sender client.SendMessage(message); Console.WriteLine($"[CHAT] {client.EmployeeId} -> {message.TargetEmployeeId}: {message.Content}"); } else { var errorMsg = new ChatMessage { Type = MessageType.Notice, Content = $"User '{message.TargetEmployeeId}' not found or offline.", Timestamp = DateTime.Now }; client.SendMessage(errorMsg); } } } else { // No target specified, send error var errorMsg = new ChatMessage { Type = MessageType.Notice, Content = "Please select a recipient.", Timestamp = DateTime.Now }; client.SendMessage(errorMsg); } } /// /// Whisper message handler /// private void HandleWhisper(ChatClient client, ChatMessage message) { message.NickName = client.NickName; message.IpAddress = client.IpAddress; message.Timestamp = DateTime.Now; lock (clientsLock) { var targetClient = clients.FirstOrDefault(c => c.NickName == message.TargetNickName); if (targetClient != null) { targetClient.SendMessage(message); Console.WriteLine($"[WHISPER] {client.NickName} -> {message.TargetNickName}: {message.Content}"); } else { var errorMsg = new ChatMessage { Type = MessageType.Notice, Content = $"User '{message.TargetNickName}' not found.", Timestamp = DateTime.Now }; client.SendMessage(errorMsg); } } } /// /// 사용자 목록 요청 처리 /// private void HandleUserListRequest(ChatClient client) { lock (clientsLock) { // Format: "employeeId1:nickName1:userGroup1,employeeId2:nickName2:userGroup2,..." // Filter: dev can see all, others can only see same group var filteredClients = clients .Where(c => !string.IsNullOrEmpty(c.NickName) && !string.IsNullOrEmpty(c.EmployeeId)) .Where(c => client.UserGroup == "dev" || c.UserGroup == client.UserGroup); var userListStr = string.Join(",", filteredClients .Select(c => $"{c.EmployeeId}:{c.NickName}:{c.UserGroup ?? "default"}")); var response = new ChatMessage { Type = MessageType.UserListResponse, Content = userListStr, Timestamp = DateTime.Now }; client.SendMessage(response); Console.WriteLine($"[USER_LIST] Sent to {client.EmployeeId} ({client.UserGroup}): {userListStr}"); } } /// /// Ping 처리 /// private void HandlePing(ChatClient client) { var pongMsg = new ChatMessage { Type = MessageType.Pong, Timestamp = DateTime.Now }; client.SendMessage(pongMsg); } /// /// 클라이언트 연결 해제 이벤트 핸들러 /// private void OnClientDisconnected(object sender, string clientId) { ChatClient disconnectedClient = null; lock (clientsLock) { disconnectedClient = clients.FirstOrDefault(c => c.ClientId == clientId); if (disconnectedClient != null) { clients.Remove(disconnectedClient); } } if (disconnectedClient != null) { Console.WriteLine($"[DISCONNECTED] {disconnectedClient.NickName ?? disconnectedClient.IpAddress} (ID: {clientId})"); if (!string.IsNullOrEmpty(disconnectedClient.NickName)) { var noticeMsg = new ChatMessage { Type = MessageType.Notice, Content = $"{disconnectedClient.NickName} has left the chat.", Timestamp = DateTime.Now }; BroadcastMessage(noticeMsg); } } } /// /// 모든 클라이언트에게 메시지 전송 /// private void BroadcastMessage(ChatMessage message, string excludeClientId = null) { lock (clientsLock) { foreach (var client in clients.ToList()) { if (excludeClientId != null && client.ClientId == excludeClientId) continue; if (!string.IsNullOrEmpty(client.NickName)) { client.SendMessage(message); } } } } /// /// 서버 상태 출력 /// public void PrintStatus() { Console.WriteLine("\n========================================"); Console.WriteLine($"Current Clients: {clients.Count}/{maxClients}"); Console.WriteLine("========================================"); lock (clientsLock) { foreach (var client in clients) { if (!string.IsNullOrEmpty(client.NickName)) { Console.WriteLine($"- {client.NickName} ({client.EmployeeId}, {client.UserGroup})"); Console.WriteLine($" IP: {client.IpAddress}, Host: {client.HostName}"); Console.WriteLine($" Connected: {client.ConnectedTime:yyyy-MM-dd HH:mm:ss}"); Console.WriteLine($" Last Activity: {client.LastActivity:HH:mm:ss}"); } } } Console.WriteLine("========================================\n"); } /// /// TCP 서버 중지 /// public void Stop() { if (!isRunning) return; isRunning = false; // Disconnect all clients lock (clientsLock) { foreach (var client in clients.ToList()) { client.Disconnect(); } clients.Clear(); } try { tcpListener?.Stop(); } catch { } Console.WriteLine("[TCP] Chat server stopped"); } } }