'TCP/IP-Network'에 해당되는 글 1건

  1. 2007.03.27 [#1] C#으로 WinForm, WebForm 채팅 프로그램 구현 3
posted by 권오성의 Biomedical Engineering 2007. 3. 27. 16:43

기본 동작원리는 다음과 같습니다.

먼저 클라이언트가 접속하기 위해서는 서버가 동작하고 있어야 합니다.

채팅서버를 사용하는 화면예시..

1) 서버측 연결화면.

사용자 삽입 이미지

아래의 파일은 .NET2005(Framework 2.0), Windows2003 Server, C#으로 구현되었습니다.

압축을 풀고 솔루션을 열면 서버(Server), 클라이언트(Client), ChattingService(웹기반)솔루션이

차례로 열릴 것입니다.
사용자 삽입 이미지
사용자 삽입 이미지

----- Server (Server.CS) => -----

using System;
using System.IO;
using System.Net;
using System.Data;
using System.Text;
using System.Drawing;
using System.Net.Sockets;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;

namespace Server
{
  public partial class Server : Form
  {
    // 기본 포트
    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);

    // 서버 생성자.
    public Server()
    {
      InitializeComponent();
      Init();
    }

    // 서버의 TCP 리스너를 초기화 합니다.
    private void Init()
    {
      try
      {
        // 서버를 실행하는 컴퓨터의 아이피를 찾아 종점을 생성합니다.
        IPHostEntry localHostEntry = Dns.GetHostByName(Dns.GetHostName());
        // TcpListener 로 서버 객체를 생성합니다.
        _server = new TcpListener(localHostEntry.AddressList[0], _svrPort);
      }
      catch (Exception ex)
      {
        _server = null;
      }
    }

    // 시작 버튼을 누르면 서버를 시작합니다.
    private void btnStart_Click(object sender, EventArgs e)
    {
      // 멈춤 상태를 false 로 설정합니다.  
      _isStop = false;
      // 접속이 끊긴 클라이언트 삭제 작업이 멈춤이 아님을 설정합니다.
      _isStopRemove = false;
      // 로그창에 시작을 알립니다.
      LogWrite("서버를 시작합니다.");
      // 쓰레드를 시작할 수 있도록 메소드를 호출합니다.
      this.StartServer();
    }

    // 중짐 버튼을 누르면 서버의 서비스를 중지합니다.
    private void btnStop_Click(object sender, EventArgs e)
    {
      // 창에 서버 중지를 알립니다.
      LogWrite("서버를 중지합니다.");
      // 서버를 중지할 메소드를 호출합니다.
      this.StopServer();
    }


    // 어플리 케이션의 쓰레드에 포함되기 위해 델리게이트 이용
    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 (SocketException se)
        {
          _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 (Exception ex)
          {
            LogWrite("[error]" + ex.ToString());
          }
        }// End of foreach
      }// End of lock
    }// End of Method
  }// End of Class
}// End of Namespace