Merge branch 'ReactHosting' of file://k4fs3201n/k4bpartcenter$/Repository/Common/GroupWare into ReactHosting
This commit is contained in:
Submodule SubProject/AmkorRestfulService deleted from cd4e1379bc
27
SubProject/ChatServer/App.config
Normal file
27
SubProject/ChatServer/App.config
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<configSections>
|
||||
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
|
||||
<section name="ChatServer.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
|
||||
</sectionGroup>
|
||||
</configSections>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
|
||||
</startup>
|
||||
<appSettings>
|
||||
<!-- TCP 채팅 서버 포트 -->
|
||||
<add key="TcpPort" value="5000" />
|
||||
<!-- 최대 동시 접속자 수 -->
|
||||
<add key="MaxClients" value="100" />
|
||||
</appSettings>
|
||||
<userSettings>
|
||||
<ChatServer.Properties.Settings>
|
||||
<setting name="TcpPort" serializeAs="String">
|
||||
<value>5000</value>
|
||||
</setting>
|
||||
<setting name="MaxClients" serializeAs="String">
|
||||
<value>1000</value>
|
||||
</setting>
|
||||
</ChatServer.Properties.Settings>
|
||||
</userSettings>
|
||||
</configuration>
|
||||
174
SubProject/ChatServer/ChatClient.cs
Normal file
174
SubProject/ChatServer/ChatClient.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
using System;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NoticeServer
|
||||
{
|
||||
/// <summary>
|
||||
/// 연결된 채팅 클라이언트 정보 및 통신 관리
|
||||
/// </summary>
|
||||
public class ChatClient
|
||||
{
|
||||
private TcpClient tcpClient;
|
||||
private NetworkStream stream;
|
||||
private Thread receiveThread;
|
||||
private bool isConnected;
|
||||
|
||||
/// <summary>클라이언트 고유 ID</summary>
|
||||
public string ClientId { get; private set; }
|
||||
|
||||
/// <summary>닉네임</summary>
|
||||
public string NickName { get; set; }
|
||||
|
||||
/// <summary>사원번호 (Employee ID)</summary>
|
||||
public string EmployeeId { get; set; }
|
||||
|
||||
/// <summary>사용자 그룹</summary>
|
||||
public string UserGroup { get; set; }
|
||||
|
||||
/// <summary>IP 주소</summary>
|
||||
public string IpAddress { get; private set; }
|
||||
|
||||
/// <summary>호스트명</summary>
|
||||
public string HostName { get; set; }
|
||||
|
||||
/// <summary>연결 시간</summary>
|
||||
public DateTime ConnectedTime { get; private set; }
|
||||
|
||||
/// <summary>마지막 활동 시간</summary>
|
||||
public DateTime LastActivity { get; set; }
|
||||
|
||||
/// <summary>메시지 수신 이벤트</summary>
|
||||
public event EventHandler<ChatMessage> MessageReceived;
|
||||
|
||||
/// <summary>연결 해제 이벤트</summary>
|
||||
public event EventHandler<string> Disconnected;
|
||||
|
||||
public ChatClient(TcpClient client)
|
||||
{
|
||||
tcpClient = client;
|
||||
stream = client.GetStream();
|
||||
isConnected = true;
|
||||
ClientId = Guid.NewGuid().ToString();
|
||||
ConnectedTime = DateTime.Now;
|
||||
LastActivity = DateTime.Now;
|
||||
|
||||
var endpoint = client.Client.RemoteEndPoint.ToString();
|
||||
IpAddress = endpoint.Split(':')[0];
|
||||
|
||||
StartReceiving();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 메시지 수신 스레드 시작
|
||||
/// </summary>
|
||||
private void StartReceiving()
|
||||
{
|
||||
receiveThread = new Thread(ReceiveLoop)
|
||||
{
|
||||
IsBackground = true,
|
||||
Name = $"ChatClient-{ClientId}"
|
||||
};
|
||||
receiveThread.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 메시지 수신 루프
|
||||
/// </summary>
|
||||
private void ReceiveLoop()
|
||||
{
|
||||
byte[] buffer = new byte[4096];
|
||||
|
||||
try
|
||||
{
|
||||
while (isConnected && tcpClient.Connected)
|
||||
{
|
||||
int bytesRead = stream.Read(buffer, 0, buffer.Length);
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
// Connection closed
|
||||
break;
|
||||
}
|
||||
|
||||
string jsonData = Encoding.UTF8.GetString(buffer, 0, bytesRead);
|
||||
var message = JsonConvert.DeserializeObject<ChatMessage>(jsonData);
|
||||
|
||||
if (message != null)
|
||||
{
|
||||
LastActivity = DateTime.Now;
|
||||
|
||||
// Set nickname, employeeId, userGroup from Connect message
|
||||
if (message.Type == MessageType.Connect && !string.IsNullOrEmpty(message.NickName))
|
||||
{
|
||||
NickName = message.NickName;
|
||||
EmployeeId = message.EmployeeId;
|
||||
UserGroup = message.UserGroup;
|
||||
HostName = message.HostName;
|
||||
}
|
||||
|
||||
MessageReceived?.Invoke(this, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[ERROR] Client {NickName ?? ClientId} receive error: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send message
|
||||
/// </summary>
|
||||
public bool SendMessage(ChatMessage message)
|
||||
{
|
||||
if (!isConnected || !tcpClient.Connected)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
string jsonData = JsonConvert.SerializeObject(message);
|
||||
byte[] data = Encoding.UTF8.GetBytes(jsonData);
|
||||
stream.Write(data, 0, data.Length);
|
||||
stream.Flush();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[ERROR] Client {NickName ?? ClientId} send error: {ex.Message}");
|
||||
Disconnect();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 연결 해제
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
if (!isConnected)
|
||||
return;
|
||||
|
||||
isConnected = false;
|
||||
|
||||
try
|
||||
{
|
||||
stream?.Close();
|
||||
tcpClient?.Close();
|
||||
}
|
||||
catch { }
|
||||
|
||||
Disconnected?.Invoke(this, ClientId);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{NickName ?? "Unknown"} ({IpAddress})";
|
||||
}
|
||||
}
|
||||
}
|
||||
76
SubProject/ChatServer/ChatMessage.cs
Normal file
76
SubProject/ChatServer/ChatMessage.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
|
||||
namespace NoticeServer
|
||||
{
|
||||
/// <summary>
|
||||
/// 채팅 메시지 타입
|
||||
/// </summary>
|
||||
public enum MessageType
|
||||
{
|
||||
/// <summary>클라이언트 연결</summary>
|
||||
Connect,
|
||||
/// <summary>클라이언트 연결 해제</summary>
|
||||
Disconnect,
|
||||
/// <summary>일반 채팅 메시지</summary>
|
||||
Chat,
|
||||
/// <summary>서버 공지</summary>
|
||||
Notice,
|
||||
/// <summary>귓속말</summary>
|
||||
Whisper,
|
||||
/// <summary>사용자 목록 요청</summary>
|
||||
UserListRequest,
|
||||
/// <summary>사용자 목록 응답</summary>
|
||||
UserListResponse,
|
||||
/// <summary>핑 (연결 유지)</summary>
|
||||
Ping,
|
||||
/// <summary>퐁 (핑 응답)</summary>
|
||||
Pong
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 채팅 메시지 프로토콜
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class ChatMessage
|
||||
{
|
||||
/// <summary>메시지 타입</summary>
|
||||
public MessageType Type { get; set; }
|
||||
|
||||
/// <summary>발신자 닉네임</summary>
|
||||
public string NickName { get; set; }
|
||||
|
||||
/// <summary>발신자 사원번호 (Employee ID)</summary>
|
||||
public string EmployeeId { get; set; }
|
||||
|
||||
/// <summary>발신자 IP</summary>
|
||||
public string IpAddress { get; set; }
|
||||
|
||||
/// <summary>발신자 호스트명</summary>
|
||||
public string HostName { get; set; }
|
||||
|
||||
/// <summary>메시지 내용</summary>
|
||||
public string Content { get; set; }
|
||||
|
||||
/// <summary>수신자 사원번호 (1:1 채팅용)</summary>
|
||||
public string TargetEmployeeId { get; set; }
|
||||
|
||||
/// <summary>수신자 닉네임 (귓속말용, null이면 전체)</summary>
|
||||
public string TargetNickName { get; set; }
|
||||
|
||||
/// <summary>전송 시간</summary>
|
||||
public DateTime Timestamp { get; set; }
|
||||
|
||||
/// <summary>사용자 그룹</summary>
|
||||
public string UserGroup { get; set; }
|
||||
|
||||
public ChatMessage()
|
||||
{
|
||||
Timestamp = DateTime.Now;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[{Timestamp:HH:mm:ss}] {NickName}: {Content}";
|
||||
}
|
||||
}
|
||||
}
|
||||
73
SubProject/ChatServer/NoticeServer.csproj
Normal file
73
SubProject/ChatServer/NoticeServer.csproj
Normal file
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{8E9A4B1C-6D5F-4E2A-9F3B-1C8D7E6A5B4F}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>NoticeServer</RootNamespace>
|
||||
<AssemblyName>NoticeServer</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<Deterministic>true</Deterministic>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.ServiceProcess" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="NoticeService.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Properties\Settings.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="TcpChatServer.cs" />
|
||||
<Compile Include="ChatClient.cs" />
|
||||
<Compile Include="ChatMessage.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
<None Include="packages.config" />
|
||||
<None Include="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
116
SubProject/ChatServer/NoticeService.cs
Normal file
116
SubProject/ChatServer/NoticeService.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using System.ServiceProcess;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace NoticeServer
|
||||
{
|
||||
public partial class NoticeService : ServiceBase
|
||||
{
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private Task _serviceTask;
|
||||
private static TcpChatServer _tcpServer;
|
||||
|
||||
public NoticeService()
|
||||
{
|
||||
InitializeComponent();
|
||||
ServiceName = "EETGWNoticeService";
|
||||
CanStop = true;
|
||||
CanPauseAndContinue = false;
|
||||
AutoLog = true;
|
||||
}
|
||||
|
||||
protected override void OnStart(string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
EventLog.WriteEntry(ServiceName, "Notice 서비스가 시작됩니다.", EventLogEntryType.Information);
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_serviceTask = Task.Run(() => DoWork(_cancellationTokenSource.Token));
|
||||
|
||||
EventLog.WriteEntry(ServiceName, "Notice 서비스가 성공적으로 시작되었습니다.", EventLogEntryType.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
EventLog.WriteEntry(ServiceName, $"서비스 시작 중 오류 발생: {ex.Message}", EventLogEntryType.Error);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnStop()
|
||||
{
|
||||
try
|
||||
{
|
||||
EventLog.WriteEntry(ServiceName, "Notice 서비스를 중지합니다.", EventLogEntryType.Information);
|
||||
|
||||
_cancellationTokenSource?.Cancel();
|
||||
|
||||
// Stop server
|
||||
_tcpServer?.Stop();
|
||||
|
||||
if (_serviceTask != null)
|
||||
{
|
||||
// 최대 30초 대기
|
||||
if (!_serviceTask.Wait(TimeSpan.FromSeconds(30)))
|
||||
{
|
||||
EventLog.WriteEntry(ServiceName, "서비스 중지 시간 초과", EventLogEntryType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
EventLog.WriteEntry(ServiceName, "Notice 서비스가 중지되었습니다.", EventLogEntryType.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
EventLog.WriteEntry(ServiceName, $"서비스 중지 중 오류 발생: {ex.Message}", EventLogEntryType.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_serviceTask?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void DoWork(CancellationToken cancellationToken)
|
||||
{
|
||||
EventLog.WriteEntry(ServiceName, "Notice 서비스 작업을 시작합니다.", EventLogEntryType.Information);
|
||||
|
||||
try
|
||||
{
|
||||
// Read settings
|
||||
int tcpPort = Properties.Settings.Default.TcpPort;
|
||||
int maxClients = Properties.Settings.Default.MaxClients;
|
||||
|
||||
// Start server
|
||||
_tcpServer = new TcpChatServer(tcpPort, maxClients);
|
||||
_tcpServer.Start();
|
||||
|
||||
EventLog.WriteEntry(ServiceName, $"Notice 서버가 포트 {tcpPort}에서 시작되었습니다.", EventLogEntryType.Information);
|
||||
|
||||
// Wait for cancellation
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
if (cancellationToken.WaitHandle.WaitOne(1000))
|
||||
{
|
||||
break; // 취소 요청 시 종료
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
EventLog.WriteEntry(ServiceName, $"서버 실행 중 오류 발생: {ex.Message}\n{ex.StackTrace}", EventLogEntryType.Error);
|
||||
}
|
||||
|
||||
EventLog.WriteEntry(ServiceName, "Notice 서비스 작업이 종료되었습니다.", EventLogEntryType.Information);
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
//
|
||||
// NoticeService
|
||||
//
|
||||
this.ServiceName = "EETGWNoticeService";
|
||||
}
|
||||
}
|
||||
}
|
||||
288
SubProject/ChatServer/Program.cs
Normal file
288
SubProject/ChatServer/Program.cs
Normal file
@@ -0,0 +1,288 @@
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.ServiceProcess;
|
||||
using System.Threading;
|
||||
|
||||
namespace NoticeServer
|
||||
{
|
||||
partial class Program
|
||||
{
|
||||
static TcpChatServer tcpServer;
|
||||
static bool isRunning = true;
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// 명령행 인수 처리
|
||||
if (args.Length > 0)
|
||||
{
|
||||
string command = args[0].ToLower();
|
||||
switch (command)
|
||||
{
|
||||
case "-install":
|
||||
case "/install":
|
||||
InstallService();
|
||||
return;
|
||||
case "-uninstall":
|
||||
case "/uninstall":
|
||||
UninstallService();
|
||||
return;
|
||||
case "-console":
|
||||
case "/console":
|
||||
RunAsConsole();
|
||||
return;
|
||||
case "-help":
|
||||
case "/help":
|
||||
case "/?":
|
||||
ShowHelp();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 서비스로 실행
|
||||
if (Environment.UserInteractive)
|
||||
{
|
||||
// 대화형 모드에서는 콘솔로 실행
|
||||
RunAsConsole();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 서비스로 실행
|
||||
ServiceBase[] ServicesToRun;
|
||||
ServicesToRun = new ServiceBase[]
|
||||
{
|
||||
new NoticeService()
|
||||
};
|
||||
ServiceBase.Run(ServicesToRun);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ShowHelp()
|
||||
{
|
||||
Console.WriteLine("EETGW Notice Server (Chat Server)");
|
||||
Console.WriteLine("사용법:");
|
||||
Console.WriteLine(" NoticeServer.exe - 서비스로 실행 (또는 대화형 모드에서 콘솔 실행)");
|
||||
Console.WriteLine(" NoticeServer.exe -console - 콘솔 모드로 실행");
|
||||
Console.WriteLine(" NoticeServer.exe -install - 서비스 설치");
|
||||
Console.WriteLine(" NoticeServer.exe -uninstall - 서비스 제거");
|
||||
Console.WriteLine(" NoticeServer.exe -help - 도움말 표시");
|
||||
}
|
||||
|
||||
private static void InstallService()
|
||||
{
|
||||
try
|
||||
{
|
||||
string servicePath = $"\"{System.Reflection.Assembly.GetExecutingAssembly().Location}\"";
|
||||
ProcessStartInfo startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "sc.exe",
|
||||
Arguments = $"create EETGWNoticeService binPath= \"{servicePath}\" DisplayName= \"EETGW Notice Service\" start= auto",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using (Process process = Process.Start(startInfo))
|
||||
{
|
||||
process.WaitForExit();
|
||||
if (process.ExitCode == 0)
|
||||
{
|
||||
Console.WriteLine("서비스가 성공적으로 설치되었습니다.");
|
||||
Console.WriteLine("서비스를 시작하려면 다음 명령을 실행하세요:");
|
||||
Console.WriteLine("net start EETGWNoticeService");
|
||||
}
|
||||
else
|
||||
{
|
||||
string error = process.StandardError.ReadToEnd();
|
||||
Console.WriteLine($"서비스 설치 실패: {error}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"서비스 설치 중 오류 발생: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void UninstallService()
|
||||
{
|
||||
try
|
||||
{
|
||||
ProcessStartInfo startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "sc.exe",
|
||||
Arguments = "delete EETGWNoticeService",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using (Process process = Process.Start(startInfo))
|
||||
{
|
||||
process.WaitForExit();
|
||||
if (process.ExitCode == 0)
|
||||
{
|
||||
Console.WriteLine("서비스가 성공적으로 제거되었습니다.");
|
||||
}
|
||||
else
|
||||
{
|
||||
string error = process.StandardError.ReadToEnd();
|
||||
Console.WriteLine($"서비스 제거 실패: {error}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"서비스 제거 중 오류 발생: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void RunAsConsole()
|
||||
{
|
||||
// 중복실행 방지 체크
|
||||
if (!CheckSingleInstance())
|
||||
{
|
||||
return; // 프로그램 종료
|
||||
}
|
||||
|
||||
Console.OutputEncoding = System.Text.Encoding.UTF8;
|
||||
Console.Title = "EETGW Notice Server (Chat Server)";
|
||||
Console.WriteLine("========================================");
|
||||
Console.WriteLine(" EETGW Notice Server v1.0 (콘솔 모드)");
|
||||
Console.WriteLine("========================================\n");
|
||||
|
||||
// Read settings
|
||||
int tcpPort = Properties.Settings.Default.TcpPort;
|
||||
int maxClients = Properties.Settings.Default.MaxClients;
|
||||
|
||||
// Get local IP address
|
||||
string localIP = GetLocalIPAddress();
|
||||
string hostName = Dns.GetHostName();
|
||||
|
||||
Console.WriteLine($"Server IP: {localIP}");
|
||||
Console.WriteLine($"Host Name: {hostName}");
|
||||
Console.WriteLine($"TCP Port: {tcpPort}");
|
||||
Console.WriteLine($"Max Clients: {maxClients}\n");
|
||||
|
||||
// Start server
|
||||
tcpServer = new TcpChatServer(tcpPort, maxClients);
|
||||
tcpServer.Start();
|
||||
|
||||
Console.WriteLine("\nServer started successfully.");
|
||||
Console.WriteLine("Commands: status, clear, quit, help");
|
||||
Console.WriteLine("종료하려면 Ctrl+C를 누르거나 'quit'를 입력하세요.\n");
|
||||
|
||||
// Ctrl+C handler
|
||||
Console.CancelKeyPress += (sender, e) =>
|
||||
{
|
||||
Console.WriteLine("\n서버를 종료합니다...");
|
||||
e.Cancel = true;
|
||||
isRunning = false;
|
||||
};
|
||||
|
||||
// Command processing loop
|
||||
while (isRunning)
|
||||
{
|
||||
string command = Console.ReadLine()?.Trim().ToLower();
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case "status":
|
||||
case "s":
|
||||
tcpServer.PrintStatus();
|
||||
break;
|
||||
|
||||
case "clear":
|
||||
case "cls":
|
||||
Console.Clear();
|
||||
Console.WriteLine("EETGW Notice Server - Commands: status, clear, quit\n");
|
||||
break;
|
||||
|
||||
case "quit":
|
||||
case "exit":
|
||||
case "q":
|
||||
isRunning = false;
|
||||
break;
|
||||
|
||||
case "help":
|
||||
case "h":
|
||||
case "?":
|
||||
PrintHelp();
|
||||
break;
|
||||
|
||||
case "":
|
||||
break;
|
||||
|
||||
default:
|
||||
Console.WriteLine($"Unknown command: {command}");
|
||||
Console.WriteLine("Available commands: status, clear, quit, help");
|
||||
break;
|
||||
}
|
||||
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
// Server shutdown
|
||||
Console.WriteLine("\nShutting down server...");
|
||||
tcpServer.Stop();
|
||||
|
||||
Console.WriteLine("Server stopped.");
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 중복실행 방지 체크
|
||||
/// </summary>
|
||||
/// <returns>단일 인스턴스인 경우 true, 중복실행인 경우 false</returns>
|
||||
static bool CheckSingleInstance()
|
||||
{
|
||||
string processName = Process.GetCurrentProcess().ProcessName;
|
||||
Process[] processes = Process.GetProcessesByName(processName);
|
||||
|
||||
if (processes.Length > 1)
|
||||
{
|
||||
// 중복실행 감지
|
||||
string message = $"⚠️ 프로그램이 이미 실행 중입니다!\n\n" +
|
||||
"동시에 여러 개의 프로그램을 실행할 수 없습니다.\n";
|
||||
|
||||
Console.WriteLine(message);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true; // 단일 인스턴스
|
||||
}
|
||||
|
||||
static string GetLocalIPAddress()
|
||||
{
|
||||
try
|
||||
{
|
||||
var host = Dns.GetHostEntry(Dns.GetHostName());
|
||||
var ipAddress = host.AddressList
|
||||
.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork);
|
||||
|
||||
if (ipAddress != null)
|
||||
return ipAddress.ToString();
|
||||
}
|
||||
catch { }
|
||||
|
||||
return "127.0.0.1";
|
||||
}
|
||||
|
||||
static void PrintHelp()
|
||||
{
|
||||
Console.WriteLine("\n========================================");
|
||||
Console.WriteLine("Available Commands:");
|
||||
Console.WriteLine("========================================");
|
||||
Console.WriteLine("status (s) - Show server status and client list");
|
||||
Console.WriteLine("clear (cls) - Clear screen");
|
||||
Console.WriteLine("quit (q) - Stop server");
|
||||
Console.WriteLine("help (h) - Show this help");
|
||||
Console.WriteLine("========================================\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
33
SubProject/ChatServer/Properties/AssemblyInfo.cs
Normal file
33
SubProject/ChatServer/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// 어셈블리에 대한 일반 정보는 다음 특성 집합을 통해
|
||||
// 제어됩니다. 어셈블리와 관련된 정보를 수정하려면
|
||||
// 이러한 특성 값을 변경하세요.
|
||||
[assembly: AssemblyTitle("ChatServer")]
|
||||
[assembly: AssemblyDescription("GroupWare Chat Server - TCP/UDP 기반 다중 사용자 채팅 서버")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("ChatServer")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2025")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// ComVisible을 false로 설정하면 이 어셈블리의 형식이 COM 구성 요소에
|
||||
// 표시되지 않습니다. COM에서 이 어셈블리의 형식에 액세스하려면
|
||||
// 해당 형식에 대해 ComVisible 특성을 true로 설정하세요.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// 이 프로젝트가 COM에 노출되는 경우 다음 GUID는 typelib의 ID를 나타냅니다.
|
||||
[assembly: Guid("8e9a4b1c-6d5f-4e2a-9f3b-1c8d7e6a5b4f")]
|
||||
|
||||
// 어셈블리의 버전 정보는 다음 네 가지 값으로 구성됩니다.
|
||||
//
|
||||
// 주 버전
|
||||
// 부 버전
|
||||
// 빌드 번호
|
||||
// 수정 버전
|
||||
//
|
||||
[assembly: AssemblyVersion("25.11.12.1300")]
|
||||
[assembly: AssemblyFileVersion("25.11.12.1300")]
|
||||
50
SubProject/ChatServer/Properties/Settings.Designer.cs
generated
Normal file
50
SubProject/ChatServer/Properties/Settings.Designer.cs
generated
Normal file
@@ -0,0 +1,50 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// 이 코드는 도구를 사용하여 생성되었습니다.
|
||||
// 런타임 버전:4.0.30319.42000
|
||||
//
|
||||
// 파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면
|
||||
// 이러한 변경 내용이 손실됩니다.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ChatServer.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("5000")]
|
||||
public int TcpPort {
|
||||
get {
|
||||
return ((int)(this["TcpPort"]));
|
||||
}
|
||||
set {
|
||||
this["TcpPort"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("1000")]
|
||||
public int MaxClients {
|
||||
get {
|
||||
return ((int)(this["MaxClients"]));
|
||||
}
|
||||
set {
|
||||
this["MaxClients"] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
SubProject/ChatServer/Properties/Settings.settings
Normal file
12
SubProject/ChatServer/Properties/Settings.settings
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="ChatServer.Properties" GeneratedClassName="Settings">
|
||||
<Profiles />
|
||||
<Settings>
|
||||
<Setting Name="TcpPort" Type="System.Int32" Scope="User">
|
||||
<Value Profile="(Default)">5000</Value>
|
||||
</Setting>
|
||||
<Setting Name="MaxClients" Type="System.Int32" Scope="User">
|
||||
<Value Profile="(Default)">1000</Value>
|
||||
</Setting>
|
||||
</Settings>
|
||||
</SettingsFile>
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
4
SubProject/ChatServer/packages.config
Normal file
4
SubProject/ChatServer/packages.config
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Newtonsoft.Json" version="13.0.3" targetFramework="net48" />
|
||||
</packages>
|
||||
Reference in New Issue
Block a user