아래의 소스코드를 작성한 후, 빌드시켜 에러가 없게 합니다.
종전에 작성한 Server 측 소스코드와 거의 유사하지만 가상으로 서비스를 실행하기 위해,
버튼으로 작동되는 부분이 제거되었습니다.
1) 모두 작성한 후, Visual Studio 명령 프롬프트를 실행합니다.
2) 빌드가 끝난 Chatting Server의 실행파일을 아래의 명령어를 입력해서 서비스에 등록합니다.
참고로, 여기서는
----- Web ChattingService (Service1.CS) -----
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.ServiceProcess;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections;
namespace ChattingService
{
public partial class Service1 : ServiceBase
{
// 기본 포트
public static int _svrPort = 2005;
// 서버 TCP 리스너
private TcpListener _server = null;
// 쓰레드를 중지하고자 할때 true 입니다.
private bool _isStop = false;
// 클라이언트를 삭제 쓰레드를 멈출것인지를 결정합니다.
private bool _isStopRemove = false;
// 클라이언트의 접속을 받아들일 쓰레드 입니다.
private Thread _serverThread = null;
// 접속이 끊긴 사용자를 제거하는 쓰레드
private Thread _removeThread = null;
// 접속된 클라이언트를 담아 놓을 리스트
private ArrayList _arrClientLister = null;
// 생성한 폼 어플리케이션에 로그를 찍기위해 선언한 delegate
public delegate void LogWriteDelegate(string msg);
// 서버의 TCP 리스너를 초기화 합니다.
private void Init()
{
try
{
// 서버를 실행하는 컴퓨터의 아이피를 찾아 종점을 생성합니다.
IPHostEntry localHostEntry = Dns.GetHostEntry(Dns.GetHostName());
// TcpListener 로 서버 객체를 생성합니다.
_server = new TcpListener(localHostEntry.AddressList[0], _svrPort);
}
catch
{
_server = null;
}
}
//// 어플리 케이션의 쓰레드에 포함되기 위해 델리게이트 이용
//public void LogWrite(string msg)
//{
// // 소켓에 관련된 쓰레드가 돌고 있으므로 application 과의 충돌을 피하기위해 델리게이트를 이용합니다.
// LogWriteDelegate deleLogWirte = new LogWriteDelegate(AppendLog);
// // 생성한 델리게이트를 이용하여 Invoke 를 실행합니다.
// this.Invoke(deleLogWirte, new object[] { msg });
//}
//// 로그를 찍고 스크롤된 위치에 위치하도록 합니다.
//public void AppendLog(string msg)
//{
// try
// {
// // 메세지를 추가하고 개행을 합니다.
// txtLog.AppendText(msg + "\r\n");
// // 로그상제에 포커스를 설정합니다.
// txtLog.Focus();
// // 추가로 인해 늘어난 라인까지 보여지도록 합니다.
// txtLog.ScrollToCaret();
// }
// catch (Exception ex)
// {
// Console.WriteLine(ex.ToString());
// }
//}
public void StartServer()
{
// 서버가 제대로 생성이 되었다면
if (_server != null)
{
// 리스너의 목록을 갖는 ArrayList
_arrClientLister = new ArrayList();
// 클라이언트의 요청을 받는 쓰레드를 시작합니다.
_server.Start();
// 클라이언트의 접속을 받아들일수 있는 쓰레드를 생성
_serverThread = new Thread(new ThreadStart(ServerThreadStart));
// 쓰레드를 백그라운드로 설정합니다.
_serverThread.IsBackground = true;
// 쓰레드를 가동시킵니다.
_serverThread.Start();
// 가동할때 약간의 여유를 줍니다.
Thread.Sleep(300);
//접속이 끊긴 클라이언트 소켓을 삭제하는 쓰레드
_removeThread = new Thread(new ThreadStart(RemoveThreadStart));
// 쓰레드를 백그라운드로 설정합니다.
_removeThread.IsBackground = true;
// 쓰레드를 가동시킵니다.
_removeThread.Start();
// 가동할때 약간의 여유를 줍니다.
Thread.Sleep(300);
}
}
public void StopServer()
{
// 서버가 정상적이라면
if (_server != null)
{
// 서버가 정지임을 알립니다.
_isStop = true;
// 리스닝을 중지합니다.
_server.Stop();
// 쓰레드가 멈출때까지 1초정도 기다립니다.
_serverThread.Join(1000);
// 쓰레드가 살아 있다면 중지 시킵니다.
if (_serverThread.IsAlive)
{
// 쓰레드가 종료되도록 합니다.
_serverThread.Abort();
}
// 쓰레드를 완전히 해제합니다.
_serverThread = null;
// 제거 쓰레드를 중지를 표시하기위해 _isStopRemove을 true로
_isStopRemove = true;
// 쓰레드 중지를 위한 1초
_removeThread.Join(1000);
// 쓰레드가 살아 있다면 중지 시킴
if (_removeThread.IsAlive)
{
// 쓰레드가 종료되도록 합니다.
_removeThread.Abort();
}
// 쓰레드를 완전히 해제합니다.
_removeThread = null;
// Stop All clients.
StopAllSocketListers();
}
}
private void StopAllSocketListers()
{
// 클라이언트와 접속하고 있는 모든 쓰레드를 중지시킵니다.
foreach (TCPSocketListener socketListener in _arrClientLister)
{
// 각각의 리스너를 중지시키기 위한 메소드를 실행
socketListener.StopSocketListener();
}
// 모든 클라이언트 목록를 제거 합니다.
_arrClientLister.Clear();
// 목록 관리 리스트를 해제합니다.
_arrClientLister = null;
}
private void ServerThreadStart()
{
// 클라이언트 소켓객체를 선언합니다.
Socket clientSocket = null;
// 소켓 리스트 객체를 선언합니다.
TCPSocketListener socketListener = null;
while (!_isStop)
{
try
{
// 서버에 접속된 클라이언트 소켓을 받습니다.
clientSocket = _server.AcceptSocket();
// 연결된 소켓값으로 클라이언트에 대응할 소켓 리스너를 생성합니다.
socketListener = new TCPSocketListener(clientSocket, this);
// 목록으로 락을 겁니다
lock (_arrClientLister)
{
// 연결리스트에 새로운 리스너를 추가시킵니다.
_arrClientLister.Add(socketListener);
}
// 클라이언트와 통신할 쓰레드를 실행 시킵니다.
socketListener.StartSocketListener();
// 연결된 로그를 남깁니다.
//LogWrite("[" + clientSocket.RemoteEndPoint.ToString() + "]" + "이 연결되었습니다.");
}
catch
{
_isStop = true;
}
}
}
private void RemoveThreadStart()
{
// 종료하기 위해 멈춘것이 아니라면 삭제하는 작업을 계속 합니다.
while (!_isStopRemove)
{
// 연결 목록에서 제거할 리스트를 담을 리스트 객체 선언
ArrayList deleteList = new ArrayList();
// 현재의 리스트가 변동이 없도록 락을 걸어 줍니다.
lock (_arrClientLister)
{
foreach (TCPSocketListener socketListener in _arrClientLister)
{
// 연결되어 있지 않아 삭제 표시가 된 리스너를 찾아 내고 중지시킵니다.
if (socketListener.IsMarkedForDeletion())
{
// 마킹 되었다면 삭제 리스트에 추가시킵니다.
deleteList.Add(socketListener);
// 소켓에서 실행하고 있던 리스너를 종료 시킵니다.
socketListener.StopSocketListener();
// 종료하는동안 잠시 쉽니다.
Thread.Sleep(300);
}
}
// 마킹된 리스트를 종료시킵니다.
foreach (TCPSocketListener delListener in deleteList)
{
// 종료된 메세지를 로그에 남깁니다.
//LogWrite(delListener.NickName + " 님이 접속 종료되었습니다.");
// 연결 목록에서 삭제 합니다 .
_arrClientLister.Remove(delListener);
}
}
Thread.Sleep(300);
deleteList = null;
}// end while
}
// 연결되어 있는 모든 클라이언트에게 메세지를 보냅니다.
public void BroadcastMsg(string msg)
{
// 현재의 리스트값이 변동 없는 조건으로 메세지를 보내도록 합니다.
lock (_arrClientLister)
{
// 각각의 클라이언트마다 체크하도록 합니다.
foreach (TCPSocketListener client in _arrClientLister)
{
try
{
// 각각의 클라인언트의 메소드 값을 통해 메세지를 보냅니다.
client.SendMessage(msg);
}
catch
{
//LogWrite("[error]" + ex.ToString());
}
}
}
}
public Service1()
{
InitializeComponent();
Init();
}
protected override void OnStart(string[] args)
{
// 멈춤 상태를 false 로 설정합니다.
_isStop = false;
// 접속이 끊긴 클라이언트 삭제 작업이 멈춤이 아님을 설정합니다.
_isStopRemove = false;
// 로그창에 시작을 알립니다.
//LogWrite("서버를 시작합니다.");
// 쓰레드를 시작할 수 있도록 메소드를 호출합니다.
this.StartServer();
}
protected override void OnStop()
{
// 창에 서버 중지를 알립니다.
//LogWrite("서버를 중지합니다.");
// 서버를 중지할 메소드를 호출합니다.
this.StopServer();
}
}
}
마지막으로 웹 서비스를 위해 작성한 부분입니다.
종전에 Server를 동작시키는 동작을 하지 않고, 똑같은 소스를 [관리도구 -> 서비스]에서
동작시킵니다.
1) 코드작성을 마치고, 디자인모드로 돌아가서 설치관리자를 추가합니다.
2) 아래와 같이 서비스 아이콘이 생성됩니다.
DisplayName은 서비스에 등록되는 이름입니다.
알맞게 사용하시면 되겠습니다.
3) 채팅서비스가 동작하는 화면입니다.
4) 서비스가 백그라운드로 돌아가고 있는 상태에서 사용자측 채팅화면을 띄우고, 채팅하는 모습입니다.