add chatserver & client
This commit is contained in:
421
SubProject/ChatServer/TcpChatServer.cs
Normal file
421
SubProject/ChatServer/TcpChatServer.cs
Normal file
@@ -0,0 +1,421 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
|
||||
namespace NoticeServer
|
||||
{
|
||||
/// <summary>
|
||||
/// TCP 채팅 서버
|
||||
/// </summary>
|
||||
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<ChatClient> clients = new List<ChatClient>();
|
||||
|
||||
public TcpChatServer(int tcpPort, int maxClients = 100)
|
||||
{
|
||||
this.tcpPort = tcpPort;
|
||||
this.maxClients = maxClients;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TCP 채팅 서버 시작
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 클라이언트 연결 수락 루프
|
||||
/// </summary>
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 클라이언트 메시지 수신 이벤트 핸들러
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 연결 처리
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 채팅 메시지 처리 (1:1 messaging)
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whisper message handler
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 사용자 목록 요청 처리
|
||||
/// </summary>
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ping 처리
|
||||
/// </summary>
|
||||
private void HandlePing(ChatClient client)
|
||||
{
|
||||
var pongMsg = new ChatMessage
|
||||
{
|
||||
Type = MessageType.Pong,
|
||||
Timestamp = DateTime.Now
|
||||
};
|
||||
client.SendMessage(pongMsg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 클라이언트 연결 해제 이벤트 핸들러
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모든 클라이언트에게 메시지 전송
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 서버 상태 출력
|
||||
/// </summary>
|
||||
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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TCP 서버 중지
|
||||
/// </summary>
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user