네트워크 및 보안/네트워크 프로그래밍 with C#

2. 에코 서버 만들기

언제나휴일 2019. 1. 4. 10:59
반응형

2. 에코 서버 만들기



 여기에서는 에코 서버 프로그램을 콘솔 응용프로그램 만들기로 할게요. 먼저 콘솔 응용프로그램(콘솔 앱)을 만드세요. 그리고 에코 서버를 정의할 EchoServer 이름의 클래스를 추가하세요. 여기에서는 EchoServer를 별도의 라이브러리로 제작할 수 있게 클래스의 접근 수준을 public으로 지정할게요.

namespace 에코_서버

{

    public class EchoServer

    {

    }

}

 

 EchoServer 개체는 생성할 때 클라이언트가 연결 시도할 때 필요한 서버의 IP 주소와 포트를 입력인자로 전달합니다. 그리고 이는 비대칭 자동 속성으로 정의한 멤버에 설정하게 작성하세요.

        public string IPStr

        {

            get;

            private set;

        }

        public int Port

        {

            get;

            private set;

        }

        public EchoServer(string ipstr, int port)

        {

            IPStr = ipstr;

            Port = port;

        }

 

 이제 에코 서버를 가동하는 Start 메서드를 추가하여 구현합시다.

        public void Start()

        {

 먼저 다른 호스트와 통신에 사용할 소켓을 생성합니다. 소켓을 생성할 때는 네트워크 주소체계와 소켓 타입 및 프로토콜을 입력 인자로 전달합니다. 여기에서는 IPv4에 해당하는 네트워크 주소체계를 사용할 것이며 소켓 타입은 TCP 프로토콜을 사용하는 스트림 방식을 사용할게요.

            //소켓 생성- 다른 호스트와 통신에 사용할 개체 생성

            Socket lissock = new Socket

                (

                AddressFamily.InterNetwork,//네트워크 주소 체계

                SocketType.Stream,//소켓 타입 결정(전송 방식 선택)

                ProtocolType.Tcp//프로토콜 선택

                );

 

 생성한 소켓은 서버 자신의 네트워크 인터페이스와 결합을 합니다. 이 때 Socket 개체의 Bind 메서드를 호출하는데 입력 인자로 IPEndPoint를 전달해 주어야 합니다. IPEndPoint는 단말 정보로 IP 주소와 포트 번호로 구성합니다.

            //소켓과 (로컬 호스트) 주소 바인딩

            //주소: IP 주소와 포트번호로 구성

            IPAddress ipaddr = IPAddress.Parse(IPStr);

            IPEndPoint servpt = new IPEndPoint(ipaddr, Port);

            lissock.Bind(servpt);

 

 TCP 서버는 Bind한 소켓 개체가 클라이언트의 연결 요청을 수락하기 위해 백로그 큐 크기를 결정해야 합니다. 이 때 호출할 메서드가 Listen입니다. 백로그 큐는 연결 요청을 수락하는 과정동안 연결 요청한 클라이언트 정보를 임시로 기억하기 위한 저장공간으로 연결을 수락하면 해당 공간은 반납하여 다시 사용할 수 있습니다.

            //백로그 큐 크기 결정

            //백로그 큐 - 연결 요청한 클라이언트의 요청을 수락하는 과정동안

            //기억해야 하는 정보를 보관하는 임시 큐

            lissock.Listen(5);

 

 이제 클라이언트의 연결 요청을 수락하고 클라이언트와 에코 서비스를 수행하는 부분을 작성합시다. 여기에서는 동시에 하나의 클라이언트와 에코 서비스를 수행하게 작성할게요. 여기서 작성하는 에코 서버는 TCP 서버의 기본 절차를 보여주기 위한 목적으로 작성하는 것입니다.

 

 연결 요청을 대기하고 수락하는 메서드는 Accept이며 현재 연결을 수락한 클라이언트와 통신할 수 있는 소켓 개체를 반환합니다. 여기에서는 실제 통신하는 부분은 DoIt 메서드를 별도로 정의하기로 할게요.

            while (true)//반복

            {

                //연결 대기 및 수락

                //반환한 소켓으로 실제 클라이언트와 통신을 주고 받음

                Socket dosock = lissock.Accept();

                //현재 연결한 클라이언트와 통신

                DoIt(dosock);

            }

        }

 

        private void DoIt(Socket dosock)

        {

        }

 

 이제 실제 연결한 클라이언트와 에코 서비스를 수행하는 DoIt 메서드를 구현합시다.

        private void DoIt(Socket dosock)

        {

 먼저 연결 요청 수락한 클라이언트의 정보를 콘솔 화면에 출력합시다. Socket 개체는 자신의 로컬 단말 정보에 접근할 수 있게 LocalEndPoint 속성을 제공하고 상대 단말 정보에 접근할 수 있게 RemoteEndPoint를 제공하고 있어요.

            Console.WriteLine("{0}의 연결 요청 수락", dosock.RemoteEndPoint);

 

 먼저 데이터를 수신할 버퍼를 생성하세요.

            byte[] packet = new byte[1024];

 

 에코 서비스에서는 클라이언트로부터 데이터를 받고 해당 내용이 "Exit"이 나올 때까지 수신한 메시지를 다시 전송하는 것을 반복할 거예요. 일단 무한 루프로 표현할게요.

            while (true)

            {

 

 먼저 클라이언트로부터 데이터를 수신합니다. 이 때 Receive 메서드를 호출합니다.

                //클라이언트로부터 데이터를 받음

                dosock.Receive(packet);

 

 수신한 byte 배열에 내용을 문자열로 변환하는 방법은 여러가지가 있어요. 여기에서는 메모리 스트림 개체를 BinaryReader를 통해 문자열로 변환하는 방법을 사용할게요.

                MemoryStream ms = new MemoryStream(packet);

                BinaryReader br = new BinaryReader(ms);

                String msg = br.ReadString();

 

 수신한 메시지를 콘솔 화면에 출력하세요.

                Console.WriteLine("{0}:{1}", dosock.RemoteEndPoint, msg);

 

 수신한 문자열이 "Exit"이면 루프를 탈출합니다.

                if (msg == "Exit")

                {

                    break;

                }

 

 루프를 탈출하지 않았다면 수신한 데이터를 다시 클라이언트에게 전송합니다. 이 때 Send 메서드를 호출합니다.

                //받은 데이터 다시 클라이언트한테 전송

                dosock.Send(packet);

            }

 

 에코 서비스를 끝났기 때문에 소켓을 닫아주어야 합니다. Close 메서드를 호출하세요.

            //현재 연결한 클라이언트와 연결 종료

            dosock.Close();

        }

 

 이제 Program 클래스의 진입점(Main) 메서드에 에코 서버를 생성하고 가동하는 코드를 작성하세요. 에코 서버를 가동하기 위해서는 IP 주소와 포트 정보를 전달해 주어야 합니다.

 

using System;

 

namespace 에코_서버

{

    class Program

    {

        static void Main(string[] args)

        {

            Console.Write("에코 서버 IPv4 주소:");

            string ipstr = Console.ReadLine();

            int port = 13000;

            Console.Write("포트 번호:");

            int.TryParse(Console.ReadLine(), out port);

            Console.WriteLine("... IPv4:{0} 포트 번호:{1} ...", ipstr, port);

            try

            {

                EchoServer es = new EchoServer(ipstr, port);

                es.Start();

            }

            catch

            {

                Console.WriteLine("정상적으로 에코 서버를 가동하지 못하였습니다.");

            }

        }

    }

}

 

//EchoServer.cs

using System;

using System.IO;

using System.Net;

using System.Net.Sockets;

 

namespace 에코_서버

{

    public class EchoServer

    {

        public string IPStr

        {

            get;

            private set;

        }

        public int Port

        {

            get;

            private set;

        }

        public EchoServer(string ipstr, int port)

        {

            IPStr = ipstr;

            Port = port;

        }

        public void Start()

        {

            //소켓 생성- 다른 호스트와 통신에 사용할 개체 생성

            Socket lissock = new Socket

                (

                AddressFamily.InterNetwork,//네트워크 주소 체계

                SocketType.Stream,//소켓 타입 결정(전송 방식 선택)

                ProtocolType.Tcp//프로토콜 선택

                );

            //소켓과 (로컬 호스트) 주소 바인딩

            //주소: IP 주소와 포트번호로 구성

            IPAddress ipaddr = IPAddress.Parse(IPStr);

            IPEndPoint servpt = new IPEndPoint(ipaddr, Port);

            lissock.Bind(servpt);

            //백로그 큐 크기 결정

            //백로그 큐 - 연결 요청한 클라이언트의 요청을 수락하는 과정동안

            //기억해야 하는 정보를 보관하는 임시 큐

            lissock.Listen(5);

            while (true)//반복

            {

                //연결 대기 및 수락

                //반환한 소켓으로 실제 클라이언트와 통신을 주고 받음

                Socket dosock = lissock.Accept();

                //현재 연결한 클라이언트와 통신(말 같지도 않음)

                DoIt(dosock);

            }

        }

 

        private void DoIt(Socket dosock)

        {

 

            Console.WriteLine("{0}의 연결 요청 수락", dosock.RemoteEndPoint);

            byte[] packet = new byte[1024];

            while (true)

            {

                //클라이언트로부터 데이터를 받음

                dosock.Receive(packet);

                MemoryStream ms = new MemoryStream(packet);

                BinaryReader br = new BinaryReader(ms);

                String msg = br.ReadString();

                Console.WriteLine("{0}:{1}", dosock.RemoteEndPoint, msg);

                if (msg == "Exit")

                {

                    break;

                }

                //받은 데이터 다시 클라이언트한테 전송

                dosock.Send(packet);

            }

            //현재 연결한 클라이언트와 연결 종료

            dosock.Close();

        }

    }

}

//Program.cs

using System;

 

namespace 에코_서버

{

    class Program

    {

        static void Main(string[] args)

        {

            Console.Write("에코 서버 IPv4 주소:");

            string ipstr = Console.ReadLine();

            int port = 13000;

            Console.Write("포트 번호:");

            int.TryParse(Console.ReadLine(), out port);

            Console.WriteLine("... IPv4:{0} 포트 번호:{1} ...", ipstr, port);

            try

            {

                EchoServer es = new EchoServer(ipstr, port);

                es.Start();

            }

            catch

            {

                Console.WriteLine("정상적으로 에코 서버를 가동하지 못하였습니다.");

            }

        }

    }

}

 

 


반응형