원격 제어 프로그램 6. 원격 제어 요청 수신 서버

원격 제어 요청을 수신하는 SetupServer 클래스도 정적 클래스로 정의합시다.

public static class SetupServer
{

서버 측은 연결 요청을 수신하기 위한 Listening 소켓을 생성하는 부분과 연결 요청을 대기하고 수용하는 부분으로 나눌 수 있습니다. 특히 연결 요청을 대기하고 수용하는 부분은 무한 반복할 것으로 블로킹을 막기 위해 여기에서는 스레드를 사용할게요.

Listening 소켓을 멤버로 선언합시다.

    static Socket lis_sock; //연결 요청 수신 Listening 소켓

무한 대기하는 부분의 스레드를 멤버로 선언합시다.

    static Thread accept_thread = null; //연결 요청 허용 스레드

연결 요청이 왔을 때 이벤트 처리를 위해 RecvRCInfoEventHandler 형식의 이벤트를 멤버로 선언하세요.

    //연결 요청 수신 이벤트 핸들러
    static public event RecvRCInfoEventHandler RecvedRCInfo = null;

Setup서버를 시작하는 메서드에서도 IP 주소와 포트 번호를 입력 인자로 받습니다.

    static public void Start(string ip, int port) 
    {

먼저 TCP 소켓을 생성합니다.

        //로컬 호스트의 IPEndPoint 개체 생성
        IPAddress ipaddr = IPAddress.Parse(ip);
        IPEndPoint ep = new IPEndPoint(ipaddr, port);
            //연결 요청 수신 Listening 소켓 생성
            lis_sock = new Socket(AddressFamily.InterNetwork, //네트워크 주소 체계
                SocketType.Stream,//전송 방식
                ProtocolType.Tcp);//프로토콜

소켓과 IPEndPoint를 결합하고 Back 로그 큐 크기를 설정하세요.

        lis_sock.Bind(ep);//소켓과 IPEndPoint 결합
        lis_sock.Listen(1); //Back 로그 큐 크기 설정

연결 요청을 대기하고 수용하는 스레드를 생성하고 시작합니다.

        ThreadStart ts = new ThreadStart(AcceptLoop); //연결 요청 허용 쓰레드 진입점 개체 생성
        accept_thread = new Thread(ts); //연결 요청 허용 쓰레드 생성
        accept_thread.Start(); //연결 요청 허용 쓰레드 시작
    }

다음은 연결 요청을 대기하는 메서드를 작성합시다.

    static void AcceptLoop()
    {
        try
        {

연결 요청을 대기하고 수용하는 부분 Listen 소켓의 Accept 메서드를 호출는 것을 반복합니다.

            while (true)
            {
                Socket do_sock = lis_sock.Accept();//연결 수락
                if (RecvedRCInfo != null) //연결 요청 수신 이벤트 핸들러가 있을 때
                {

연결 요청을 수신하는 이벤트 핸들러가 있다면 이벤트 인자를 생성하여 이벤트를 발생합니다.

                    RecvRCInfoEventArgs e = new RecvRCInfoEventArgs(do_sock.RemoteEndPoint);
                    RecvedRCInfo(null, e); //이벤트 발생
                }

연결 요청을 수신하였는지 알 수 있게 이벤트를 발생했으니 do_sock은 닫습니다.

                do_sock.Close();//소켓 닫기
            }
        }
        catch
        {
            Close();
        }
    }

SetupServer를 종료할 수 있게 Close 메서드를 정의합시다.

    public static void Close()
    {

소켓을 해제화 작업을 수행합니다.

        if (lis_sock != null) 
        {
            lis_sock.Close();
            lis_sock = null;
        }
    }
}

다음은 이번 실습에서 작성한 소스 코드입니다.

//SetupServer.cs
using System.Net.Sockets;
using System.Threading;
using System.Net;

namespace 원격제어기
{
    /// <summary>
    /// 연결 요청 수신 서버 클래스 - 정적 클래스
    /// </summary>
    public static class SetupServer
    {
        static Socket lis_sock; //연결 요청 수신 Listening 소켓
        static Thread accept_thread = null; //연결 요청 허용 스레드

        /// <summary>
        /// 연결 요청 수신 이벤트 핸들러
        /// </summary>
        static public event RecvRCInfoEventHandler RecvedRCInfo = null;

        /// <summary>
        /// 연결 요청 수신 서버 시작 메서드
        /// </summary>
        /// <param name="ip">서버의 IP주소</param>
        /// <param name="port">포트</param>
        static public void Start(string ip, int port) 
        {
            //로컬 호스트의 IPEndPoint 개체 생성
            IPAddress ipaddr = IPAddress.Parse(ip);
            IPEndPoint ep = new IPEndPoint(ipaddr, port);
            //연결 요청 수신 Listening 소켓 생성
            lis_sock = new Socket(AddressFamily.InterNetwork, //네트워크 주소 체계
                SocketType.Stream,//전송 방식
                ProtocolType.Tcp);//프로토콜

            lis_sock.Bind(ep);//소켓과 IPEndPoint 결합
            lis_sock.Listen(1); //Back 로그 큐 크기 설정
            //연결 요청 허용 쓰레드 진입점 개체 생성
            ThreadStart ts = new ThreadStart(AcceptLoop); 
            accept_thread = new Thread(ts); //연결 요청 허용 쓰레드 생성
            accept_thread.Start(); //연결 요청 허용 쓰레드 시작
        }

        static void AcceptLoop()
        {
            try
            {
                while (true)
                {
                    Socket do_sock = lis_sock.Accept();//연결 수락
                    if (RecvedRCInfo != null) //연결 요청 수신 이벤트 핸들러가 있을 때
                    {
                        RecvRCInfoEventArgs e = new RecvRCInfoEventArgs(
                                                do_sock.RemoteEndPoint);//이벤트 인자 생성
                        RecvedRCInfo(null, e); //이벤트 발생
                    }
                    do_sock.Close();//소켓 닫기
                }
            }
            catch
            {
                Close();
            }
        }
        /// <summary>
        /// 연결 요청 수신 서버 닫기
        /// </summary>
        public static void Close()
        {            
            if (lis_sock != null) 
            {
                lis_sock.Close();
                lis_sock = null;
            }
        }
    }
}

이 책에서는 비동기적인 요소를 스레드로 작성하고 있습니다. 소켓의 비동기 메서드를 이용하여 프로그래밍하면 보다 나은 형태로 작성할 수 있습니다. 그 부분은 여러분의 몫으로 남길게요.

*Accept 부분을 BeginAccept 메서드 호출로 비동기 처리하는 코드입니다. 동영상 강의를 참고하세요. 동영상 강의에서는 RecvRCInfoEventArgs 부분을 라이브러리로 제작하였습니다.

그리고 해당 형식의 namespace를 RCEventArgsLib로 정했습니다.

첫 using 문은 이러한 이유 때문입니다.*

using RCEventArgsLib;
using System;
using System.Net;
using System.Net.Sockets;

namespace 원격제어기
{
    public static class SetupServer
    {
        static Socket lis_sock;
        static public event RecvRCInfoEventHandler RecvedRCInfoEventHandler = null;
        static string ip;
        static int port;
        
        public static void Start(string ip,int port)
        {
            SetupServer.ip = ip;
            SetupServer.port = port;
            SocketBooting();
        }

        private static void SocketBooting()
        {
            IPAddress ipaddr = IPAddress.Parse(ip);
            IPEndPoint ep = new IPEndPoint(ipaddr, port);
            lis_sock = new Socket(AddressFamily.InterNetwork,
                SocketType.Stream, ProtocolType.Tcp);
            lis_sock.Bind(ep);
            lis_sock.Listen(1);
            lis_sock.BeginAccept(DoAccept, null);
        }
        static void DoAccept(IAsyncResult result)
        {
            if(lis_sock == null)
            {
                return;
            }
            try
            {
                Socket sock = lis_sock.EndAccept(result);
                DoIt(sock);
                lis_sock.BeginAccept(DoAccept, null);
            }
            catch
            {
                Close();
            }
        }
        static void DoIt(Socket dosock)
        {
            if(RecvedRCInfoEventHandler != null)
            {
                RecvRCInfoEventArgs e = new RecvRCInfoEventArgs(dosock.RemoteEndPoint);
                RecvedRCInfoEventHandler(null, e);
            }
            dosock.Close();
        }
        public static void Close()
        {
            if(lis_sock != null)
            {
                lis_sock.Close();
                lis_sock = null;
            }
        }
    }
}