login register Sysop! about ME  
qrcode
    최초 작성일 :    2004년 04월 08일
  최종 수정일 :    2004년 04월 08일
  작성자 :    pluginnz (최윤규)
  편집자 :    Taeyo (김 태영)
  읽음수 :    26,999

강좌 목록으로 돌아가기

필자의 잡담~

플러그인(pluginn@naver.com) 님의 .NET 소켓 프로그래밍 강좌, 이번엔 세번째 시간입니다.

앞서 올라온 강좌를 보고 상당히 딱딱함을 느꼈습니다. 그래서 좀 더 부드럽게 작성해 보고자 글의 스타일을 조금 바꿔봤습니다. 보시면서 아시겠지만 강좌를 쓰는 저도 많이 부족합니다. 하지만 저보다 조금 늦게 시작하시는 분들께는 분명 참고가 되는 글이 될 껍니다.


소켓 생성하기

이번 강좌에서는 저수준 클래스인 System.Net.Sockets.Socket 클래스에 대해 자세히 알아보고,1,2강좌에서 배운 지식을 토대로 심플하게 소켓 통신이 가능한 프로그램을 작성해 보겠습니다. 시간이되면 간단한 데이터를 발송하여 서버와 클라이언트간에 데이터가 전송되는 것도 확인해보겠습니다.

프로그램에서 소켓은 IP 와 Port 로 구성된 논리적인 연결점(종단점) 이라고 말씀드렸습니다.(물론 IP 와 Port 로 구성된 소켓 객체입니다.) 그리고, 1강좌에서 나름대로 네트워크의 용어에 대해 심오하게 정의해 보았습니다.기억을 되 더듬어 보면,

"서로 원격(또는 내부)으로부터 떨어져 있는 컴퓨터간에 소켓이라는 연결점 (종단점)을 통해 TCP/IP (TCP 혹은 UDP )로 연결되어져 있는 한 개 이상의 노드(컴퓨터)"

이란 내용이었습니다. 그럼 우리가 준비 해야할 것을 정리해 보겠습니다.

IPAddress 객체 , Port(int형) 만 있으면 소켓을 만들 수 있겠죠? 소켓을 생성하는 클래스는 이미System.Net.Sockets 네임스페이스에서 제공되니까 말이죠. 그리고 데이터를 수신할 서버와 클라이언트를 만들어야 하겠죠. 보기 편하게 아래 그림으로 정리해 보았습니다.

서버측의 소켓을 설명드리면 클라이언트들이 접속할 수 있도록 자신(서버)의 로컬 IP와 Port 를 지정한 소켓 인스턴스가 되겠구요.. Client측 의 소켓은 자신이 접속할 서버의 속성 즉, 서버에서 접속을 허용할 소켓의 IP,Port 를 클라이언트에서 지정해 줘야합니다. Socket Option 이라고 써있는 부분들은TCP/IP에서 지원하는 IP 버전이나 프로토콜 형식들을 지정해 줄수있는 Socket 의 인자값들 입니다.이제 Socket 클래스에 대해 좀 더 알아보도록 하겠습니다.


System.Net.Sockets 의 Socket 클래스

.Net 에서는 저수준의 Socket 클래스 외에도 고수준의 TCP , UDP 통신을 위한 클래스를 별도로 제공합니다. 고수준의 소켓통신을 위한 클래스의 경우는 저수준의 소켓 클래스 보다 좀 더 직관적이고(Tcp 의 경우 서버측에서는 TcpListener 로 생성된 객체로 클라이언트의 접속을 기다리고, 클라이언트는 TcpClient 클래스를 이용해 객체를 생성하여 접속을 시도 하면 되며,Udp의 경우 UdpClient 를 이용해 서버/클라이언트의 역할을 다하게 됩니다.) 편리한 인터페이스를 제공 합니다.

Socket 과는 달리 일반적으로 스트림 형태로 데이터를 주고 받게 되는데,(바이트 형태도 가능합니다.) 이때 NetworkStream 클래스를 이용해서 스트림 형태로 주고 받습니다. 고수준 클래스에서는 객체.AcceptSocket() 라는 메소드를 제공하여 Socket 객체를 획득 하여 직접 Socket 클래스의 객체로 받아서 처리할 수 있도록 유연성도 제공합니다. 일단 우리는 Socket 을 위주로 공부해 나가겠습니다.

System.Net.Sockets 의 Socket 클래스가 갖는 옵션을 살펴보면,

Socket(AddressFamily , SocketType , ProtocolType) 의 시그너쳐를 가지는 생성자만 가집니다.

AddressFamily 옵션 System.Net.Sockets.AddressFamily 열거형
AddressFamily 옵션은 다루게될 소켓의 주소 체계를 선택합니다.여기서 우리가 사용할 것은 InterNetwork입니다. 정확히 말하면AddressFamily.InterNetwork 가 되겠습니다. 이것은 IPv4 버전으로현재 우리가 사용하기 위해 셋팅 되어있는 IP 체계의를 말합니다.바로 아래 InterNetworkV6 라고 보이는데 바로 앞으로 우리가 사용할128bit 의 주소체계를 가지고 있는 방식의 열거형 값입니다.많이보던 IPX 등등 많은 옵션들도 보입니다.

SocketType 옵션System.Net.Sockets.SocketType 열거형
SocketType 옵션에서는 프로토콜이 통신할 소켓 타입을 지정합니다.TCP/IP에서 사용될 프로토콜은 크게 TCP 와 UDP 방식이 있다고했었습니다. 여기서 TCP 방식은 Stream 으로 열거형으로 정의되어있고, UDP 는 Dgram 으로 되어있습니다. Raw 형 타입의 경우는ICMP 와 같은 하위 프로토콜을 이용한 어플리케이션을 작성하실 때지정하실 수 있습니다.

ProtocolType 옵션 System.Net.Sockets.ProtocolType 열거형
여기서는 소켓의 프로코톨 형식을 열거형 으로 저정해 줄 수 있습니다. TCP 인지 UDP 인지 SocketType에서 잠깐 설명했던,Raw 형식도 보이네요. 여기서 중요한건 SocketType 이나 AddressFamily 의 옵션에서 지정된 주소체계와 매칭이 되어야 한다는 것입니다. SocketType.Stream 과 ProtocolType.Tcp 와 같이 호환되는ProtocolType을 정확히 설정하는것이 중요합니다.(아래 예를 참고하세요)


[TCP 를 이용할 소켓 설정]
Socket tcpSocket = new Socket(AddressFamily.InterNetwork ,SocketType.Stream ,ProtocolType.Tcp);

[UDP 를 이용할 소켓 설정]
Socket udpSocket = new Socket(AddressFamily.InterNetwork ,SocketType.Dgram ,ProtocolType.Udp);


ProtocolType에서 잠깐 설명했듯이 AddressFamily 와 SocketType 과 ProtocolType 의 일치성이 중요합니다. 위의 예에서 현재 전세계는 IPv4 체계를 이용하고 있기 때문에 AddressFamily.InterNetwork 를 이용했습니다. TCP 의 경우 SocketType 과 ProtocolType 의 각 형식에 맞게 일치시켰다는 점만인지 하시면 뭐 특별히 어려운것은 없겠습니다.

그럼 지금까지 배운 지식으로 소켓 클래스를 간단하게 작성하고 다음 세션에서 서버와 클라이언트를구성해도보록 하겠습니다. 지금까지 설명을 토대로 작성된 소스이니 설명은 주석으로 대신 하겠습니다.

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

namespace SocketCreate
{
    class SocketClass
    {
        [STAThread] static void Main(string[] args)
        {
            // 서버로 사용될 컴퓨터의 IPAddress 객체 생성 현 PC 를 서버로 이용합니다.
            IPAddress ipaddr = Dns.Resolve("localhost").AddressList[0];
            //IPAddress ipaddr = IPAddress.Parse("127.0.0.1"); 해도 상관없습니다.

            // 소켓에 이용될 연결 종점을 생성합니다. 포트는 8000번
            IPEndPoint ipend = new IPEndPoint(ipaddr,8000);
            // 소켓객체를 생성합니다.
            Socket socket = new Socket(AddressFamily.InterNetwork ,
                   SocketType.Stream,ProtocolType.Tcp);
            socket.Bind(ipend); // 위에서 생성한 연결종점을 bind 합니다.

            Socket newSocket = socket.Accept();// 클라이언트의 접속을 기다립니다.
        }
    }
}


0 ~ 1023 까지는 시스템 포트로서 시스템이 사용하게될 포트로 예약되어져 있다고 생각하시면 됩니다.1024 ~ 49151 까지가 사용자 포트가 됩니다. MSSQL 이나 네트워크 게임등 네트웍 통신이 필요로하는응용프로그램들이 사용하게될 범위 이며 , 우리가 작성하게될 프로그램들도 저 범위안에서 포트를지정하여 사용하게 됩니다.

소켓 통신을 위한 서버 / 클라이언트 구성하기

이제 설명할 내용들은 소켓끼리 서로 통신하기 위한 간단한 구성도입니다. 물론 앞으로 소켓을 이용해서 자주 개발 한다면 머리에 자동으로 그려지기 때문에 구지 구상하고 할것도 없습니다.asp나 asp.net 을 처음 배울때 DB 서버를 접속해서 데이터를 가져오는 위해 그 전에 작업해야 했던 일련의 과정처럼, 소켓 통신을 위해서도 마찬가지로 일련의 절차가 필요합니다.

Socket 을 통한 데이터 통신은 모두 Byte 를 이용하여 통신을 하게 됩니다. 그래서 데이터 전송시 Byte[] 배열에 데이터 를 입력하고 Send/Receive 를 하게 됩니다. 참고로 Obj는 소켓 객체를 말합니다.
소켓 준비
|
IPAddress , Port서버측 IPAddress , Port소켓 준비
|
소켓 개방/바인드
|
Socket() 서버측 옵션 설정
Socket.Bind(IPAddress,Port);
Obj.Connect(IPaddress,Port);원격호스트 연결
|
연결 리스닝
|
Socket.Listener();Obj.Send() / Obj.Receive();데이터 송.수신
|
Client 연결허용
|
Socket.Accept();Obj.Shutdown();
Obj.Close();
소켓 종료
데이터 송수신
|
Obj.Send()/Obj.Receive()
소켓 종료Obj.Shutdown();
Obj.Close();

서버측의 소켓 개방 및 클라이언트 측의 소켓 접속을 위한 준비를 한눈에 볼 수 있게 정리해 보았는데 정리가 잘 되었는지 모르겠습니다. 제가 순서 옆에 메소드들을 모두 작성해 두었습니다.순서에 맞게 메소드를 보시면 쉽게 이해하실 수 있을껍니다 연결은 Connect() 메소드를 데이터 송/수신은 Send()/Receive() 를 단어 뜻만 대충 보아도 무슨 기능을 하는 메소드인지 쉽게 이해 가리라 생각 됩니다. 이 그림에서 중요한 것은 메소드 보다 ,일련의 순서들입니다 소켓의 생성부터 소멸까지의 과정을 숙지 하시기 바랍니다. 메소드들은 어차피 코딩해 나가면서 숙지하게 될것입니다.


소켓을 통해 데이터가 전송되는 시간/클라이언트가 접속되는 시간등, 소켓이 어떤 작업을 하는 시간에는 소켓이 Blocking 됩니다. 즉 쓰레드가 독점되므로 아무작업을 할 수 없습니다. 이런 상황을 동기화 되었다고 합니다. 한명의 처리가 끝날때 까지 동기화 하여 처리해야 할 경우도 필요하겠지만, 여러 클라이언트가 동시에 접속하고 처리되도록 비동기화 되어 처리 해야할 경우도 많습니다. 그래서 Socket 클래스에서는 비동기용 접속 , 송/수신용 메소들를 따로 제공 합니다. 메소드 이름만 간단히 바꿔주면 해결 되므로 일단 동기화하는 방법을 알아본 후 차차 알아보도록 하겠습니다.


서버 소켓 프로그래밍 하기

위의 순서의 개념을 잘 이해 하셨다면 다음과 같이 코딩에 들어갑니다. 서버/클라이언트 모두 TCP 를 이용했습니다.

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

namespace ServerSide
{
    class sSocket
    {
        [STAThread]
        static void Main(string[] args)
        {
            Socket ssocket , csocket;
            
            byte[] rData = new byte[1024];
            byte[] sData = new byte[1024];

            IPAddress ipaddr = IPAddress.Parse("127.0.0.1");
            IPEndPoint endpoint = new IPEndPoint(ipaddr,8000);

            ssocket = new Socket(AddressFamily.InterNetwork , SocketType.Stream ,
                         ProtocolType.Tcp);
            
            ssocket.Bind(endpoint);
            ssocket.Listen(5);

            Console.WriteLine("클라이언트의 연결을 기다립니다..");

            while(true)
            {
                csocket = ssocket.Accept();
                
                if (csocket.Connected)
                {
                    while(true)
                    {
                        int rDataLength = csocket.Receive(rData);
                        
                        if(rDataLength > 0)
                        {
                            Console.WriteLine(System.Text.Encoding.UTF8.GetString(rData));
                        }

                    }
                }
            }
        }
    }
}

클라이언트 소켓 프로그래밍 하기

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

namespace ClientSide
{
    class cSocket
    {
        [STAThread]    
        static void Main(string[] args)
        {
            Socket csocket;

            byte[] sData = new byte[1024];
            byte[] rData = new byte[1024];
            string inputStr;

            IPAddress ipaddr = IPAddress.Parse("127.0.0.1");
            IPEndPoint endpoint = new IPEndPoint(ipaddr,8000);

            csocket = new Socket(AddressFamily.InterNetwork ,SocketType.Stream ,
                                ProtocolType.Tcp);
            Console.WriteLine("서버의 연결을 시도합니다.[엔터를 치면 연결됩니다.]");
            Console.ReadLine();

            csocket.Connect(endpoint);

            if (csocket.Connected)
            {
                Console.WriteLine("서버의 연결이 성공했습니다.");
                Console.WriteLine("전송할 데이터를 입력해 주십시요");

                while(true)
                {
                    Console.Write("전송할 데이터]");
                    inputStr = Console.ReadLine();

                    if (inputStr.Trim() != String.Empty)
                    {
                        sData = System.Text.Encoding.UTF8.GetBytes(inputStr);
                        csocket.Send(sData);
                    }
                }
            }
            else
            {
                Console.WriteLine("서버의 연결이 실패 되었습니다.");
            }
        }
    }
}

저는 아래와 같이 프로젝트에 하나의 프로젝트를 추가하여 2개의 프로젝트로 코딩했습니다.

아래는 2개의 프로젝트를 동시에 컴파일 하기위해 아래와 같이 설정했습니다. 참고하세요.

실행을 하면 아래와 같이 클라이언트 / 서버측 실행화면이 동시에 보이게 됩니다. 엔터를 입력하여서버를 접슥합니다.

이제 클라이언트측에서 서버에 접속을 완료 하였습니다. text를 입력하여 서버측에 수신이 잘되나확인합니다.

hi 를 입력하니 서버측에서 수신이 잘 됩니다. 그런데 다시 hi 라고 쳤는데.. 처음 발송된 hi 와상당히 많은 공간을 띄고 나서 그다음 hi 가 출력되었습니다. 왜 그럴까요? 다음 강좌때 해답을알려드리겠습니다. 눈썰미가 있으신분들은 벌써 눈치채셨을지도 모르겠네요.

정말 간단한 코딩을 해보았는데.. 앞으로 코딩해 나가는데 정말 중요한 예제입니다. 위의 콘솔 어플리케이션으로 작성된 소켓소스를 토대로 예외처리나 인터페이스 , 클래스화 하여 처리하면 실무에서사용가능한 컴포넌트가 되는 것입니다. 물론 그전에 거쳐야할 몇가지 지식들이 좀더필요하구요.

이번강좌는 이정도로 하겠습니다. 다음 강좌에서는 이보다 조금 더 정교하게 코딩을 하고,예외처리등을 강화해 보도록 하겠습니다. 그때는 설명도 함께 덧붙여 보도록 하겠습니다.

끝까지 강좌 읽어주셔서 감사합니다.


authored by


 
 
.NET과 Java 동영상 기반의 교육사이트

로딩 중입니다...

서버 프레임워크 지원 : NeoDEEX
based on ASP.NET 3.5
Creative Commons License
{5}
{2} 읽음   :{3} ({4})