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

강좌 목록으로 돌아가기

필자의 잡담~

뉴 태오 사이트의 뉴 게시판 준비중입니다... 아... 모든 스크립트를 HTC로 바꾸려니 시간이 장난이 아니네요 ㅠㅠ

안녕하세요. 이번강좌에서는 고수준 TCP 소켓을 다뤄보고, .Net의 어셈블리에 알아봅니다.

고수준 클래스를 이용한 소켓 통신

System.Net.Sockets.TcpListener 와 System.Net.Sockets.TcpClient 2개의 클래스로 이루어진TCP 클래스를 통해 저번시간에 Socket 으로 구현한 소스를 똑같이 구현해 보도록 하겠습니다.기존의 Socket 을 통해 구현한 개념과 비슷하지만, Socket 클래스와는 다르게 다루기가 좀 더 쉽다고 합니다. 그래서 "고수준 Tcp 클래스" 라고 하면 "Socket 클래스"는 저수준이라고 명명하고 있습니다.(개인적으로는 intellesence 기능 때문에 둘 다 다루기 편한거 같습니다만,)

아래표는 우리가 이미배운 Socket 클래스의 의 메소드에 대응되는 TCP 클래스의 클래스와 메소등을매칭해본것입니다.

하위 TCP 클래스 방식
Socket socket = new Socket(TCP설정)
socket.Listener();
socket.Bind(IPEndPoint);
Socket Client = socket.Accept();
Client.Write();
Client.Read();
Client.Close();
상위 TCP 클래스 이용방식
TcpListener ln =new TcpListener(EndPoint);ln.Start(); //Socket 의 Listener 에 해당된다.
TcpClient client = ln.AccepTcpClient();
NetworkStream ns = client.GetStream();
ns.Write();
ns.Read();
ns.Close();

잠깐 특성을 알아보자면, 기존의 Socket 클래스는 Socket 생성자로 인스턴스를 생성하면서, TCP인지 UDP 인지에 대한 아래와 같은 옵션으로 지정해줘야 합니다.

Socket server = new Socket (AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);

TCP 클래스의 경우는 따로 TCP 설정없이, TCP만의 통신을 위해 저수준의 Socket으로 작성된 TCPListener, TCPClient 클래스를 .Net 에서는 지원합니다.그래서 복잡한 설정없이

TcpListener ln = new TcpListener(EndPoint)

로 대신 합니다. EndPoint객체를 만들기 위해 IPEndPoint클래스로 인스턴스를 따로 작성하는것은이미 Socket 다루면서 많이 봐왔을 것입니다. 하지만 TCP 객체 에서는 좀 더 편하게 오버로딩된생성자를 가지고 있어 따로 IPEndPoint 인스턴스생성자를 만들지않고 아래와 같이도 가능합니다.

    TcpListener ln = new TcpListener(int port);
        -> TcpListener ln = new TcpListener(6000);

이러한 식으로 포트 번호만 지정해주면 자신의 IP 를 서버IP로 하고 port 만 int 값으로 입력해주면IPEndPoint 인스턴스를 따로 작성해주실 필요가 없습니다. 좀 더 설명하자면,

클라이언트의 접속을 대기하는 TcpListener 클래스와 클라이언트 소켓 객체를 가리키는 TcpClient 클래스로 구현이 되어 있습니다. 내부적으로는 Socket 클래스로 작성되어져 있으므로, Socket 클래스를 써야할지 TcpListener , TcpClient 객체를 써야할지 고민할 필요는 특별히 없을 것입니다. 자신이 편한 방식을 선택해서 프로그래밍을 작성하시면 되겠습니다. 구지 장단점을 따지자면 TCPListener , TcpClient (이하 TCP 클래스) 는 인터페이스가 사용자들이 접하기가 조금 더 쉽다는 점입니다.

    Socket Client = socket.Accept(); // 기존 Socket
    TcpClient client = ln.AccepTcpClient(); // Tcp 클래스

기존의 Socket 을 이용한 방식은 서버소켓객체.Accept(); 메소드가, 접속되는 클라이언트의 IPEndPoint정보를 가지고 소켓 객체로 리턴되는 모습입니다.

TCP 클래스를 예로 좀 더 직관적으로 말하지면, 클라이언트의 접속을 리스닝하고 있는 ln 인스턴스가 AccepTcpClient() 메소드를 호출하여 클라이언트 접속을 대기하다가, 접속이 시도되면 클라이언트측 IPEndPoint의 정보를 가지고 있는 TcpClient 객체로 리턴하게 됩니다.

자.. 둘다 같은말입니다. TCP 클래스로 작성된 TcpClient 객체가 좀 더 직관적으로 보일 뿐입니다.TCP 클래스로 작성된 소켓의 경우 한가지 특이한 점이 있는데, 그것은 TcpClient로 리턴된 객체가직접 통신을 하지 않는다는것입니다. 아래를 보면,

    NetworkStream ns = client.GetStream();
    ns.Write();
    ns.Read();

클라이언트측 인스턴스가 getStream() 메소드를 통해 스트림 통신을 할 수 있도록 NetworkStream 으로 리턴이 됩니다. 즉, 실제적으로 하위 소켓에서의 연결설정과 리스닝, 바인딩 , Accept 를 Socket 객체혼자서 한다면, TCP 클래스에서는 연결설정,리스닝,바인딩,Accept는 TcpListener 접속된 클라이언트소켓은 TcpClient 그리고 실제 송/수신은 NetworkStream 이 하게 되는것입니다. 좀 더 복잡해 보일수는 있지만 어떻게 보면 TCP 클래스가 좀 더 직관적이고 순서 적일 수가 있습니다. 역할이 분담되어 있다고 할 수 있겠습니다. 하지만 위에서 잠깐 말씀드렸듯이 어떻게 프로그래밍을 작성하시던 "선택"입니다. 하지만 선택이란 말은 두가지로 모두 작성 해봐야 "선택" 할 수 있는 면목? 이 생기지 않나 싶습니다. 그럼 이러한 대략적인 내용을 토대로 작성된 전제 소스를 보겠습니다.

[서버측 소스]

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

namespace ServerHighSocket
{
    class Class1
    {
        static byte[] rData = new byte[1024];
        static byte[] sData = new byte[1024];

        [STAThread]    static void Main(string[] args)
        {

            IPAddress ipAddr = Dns.Resolve("127.0.0.1").AddressList[0];
            IPEndPoint ipEnd = new IPEndPoint(ipAddr,6000);

            TcpListener listener = new TcpListener(6000);
            listener.Start();

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

            TcpClient client = listener.AcceptTcpClient();
            NetworkStream ns = client.GetStream();

            while(true)
            {
                int rDataLength = ns.Read(rData,0,rData.Length);

                if (rDataLength > 0)
                {
                    Console.WriteLine("수신데이터>>{0}",Encoding.UTF7.GetString(rData,0,Class1.byteArrayDefrag(rData)+1));
                    ns.Write(rData,0,rData.Length);
                }
            }
        }

        public static int byteArrayDefrag(byte[] sData)
        {
            int endLength = 0;
            for(int i=0; i< sData.Length ;i++)
            {
                if((byte)sData[i] != (byte)0)
                {
                    endLength =i;
                }
            }
            return endLength;
        }
    }
}

[클라이언트측 소스]

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

namespace ClientHighSocket
{
    class Class1
    {
        static byte[] rData = new byte[1024];
        static byte[] sData = new byte[1024];
        
        [STAThread]    static void Main(string[] args)
        {
            TcpClient client = new TcpClient();
            client.Connect("127.0.0.1",6000);
            NetworkStream ns = client.GetStream();
            
            Console.WriteLine("--------------------------");
            Console.WriteLine(" 서버에 접속합니다. ");
            Console.WriteLine("--------------------------");

            while(true)
            {
                if (ns.CanWrite)
                {
                    Console.Write("발송할데이터>>>");
                    sData = Encoding.UTF7.GetBytes(Console.ReadLine());
                    ns.Write(sData,0,sData.Length);

                    int rDataLength = ns.Read(rData,0,rData.Length);

                    if (rDataLength > 0)
                    {
                        Console.WriteLine("수신데이터>>{0}",Encoding.UTF7.GetString(rData,0,Class1.byteArrayDefrag(rData)+1));
                    }
                }
            }
        }

        public static int byteArrayDefrag(byte[] sData)
        {
            int endLength = 0;
            for(int i=0; i< sData.Length;i++)
            {
                if((byte)sData[i] != (byte)0)
                {
                    endLength =i;
                }
            }

            return endLength;
        }
    }
}

기존의 Socket 클래스로 직접 코딩을 해보셨던 분이라면 특별히 어려운 부분들은 없었을 것입니다.단지, TCP 클래스의 이해가 조금 필요할 뿐입니다. 이것으로 대강 소켓에 대한 강좌를 1단락 막을내린것 같습니다. 물론 많이 부족합니다. 사실 저도 공부하고 입는 입장이라 애매하고, 설명이 안되는건 피해간것도 있고 -_-; 강좌를 너무 길게 하지 않으려고보니 몇가지 빼먹은것도 있습니다. 고수준 Udp 소켓통신과 udp 를 이용한 멀티캐스트 통신(1:n) 하지만 중요한것은.. asp 시절 소켓을 만들지 못해 작업상 어떠한 불이익? 을 당하셧거나 -_-;;

"asp 로는 그것도 못해? 니가 못하는거 아냐?"는 식의 억울함을 격으셨던 분들께는 소켓을 시작하는데다소 도움을 주지 않았나 싶습니다. 물론 이 시간으로 강좌가 끝나는 것은 아닙니다.강좌는 계속됩니다.

공용(전역) 어셈블리 와 사설 어셈블리

어셈블리란 짧게 나마 프로그래밍에 관심을 가지셨던 분들이라면. 기계어를 인간이 보기 쉽게 만든 기호화 언어인 어셈블리 또는 기계어 식으로 인식을 하고 계실것입니다. .Net 에서는 그 언어 자체를 가리키는 거 같지는 않습니다. .Net 에서의 어셈블리는 실행파일의 단위입니다. .Net 의 컴파일을 거쳐 생성된 MSIL(MicroSoft intermediate Language) 코드의 EXE, DLL 파일들입니다.(사실은 어셈블 리가 MSIL코드를 포함하고 있습니다. 어셈블리는 MSIL 코드외에도 자기자신의 정보를 가지고 있는 Meta Data, 리소스 등으로 구분되어 있습니다.-아래)

매니 페스트(메타 데이터 영역) MSIL 코드리소스
어셈블리 메타데이터형식 메타 데이터MSIL 코드비 실행 부분

간단하게 설명하자면, 어셈블리 메타데이터의 경우 자신이 필요에 의해 참조된 다른 어셈블리들의 정보를 가지고 있는 데이터베이스 이며, 형식 메타 데이터의 경우 자신의 어셈블리에 대한 Self Description 이라고 할 수 있습니다. 그리고 흔히 .Net 의 코드는 MSIL 코드이다 라고 말하는 MSIL 코드(컴파일된 소스(?) -> 다시 JIT 에 의해 런타임이 컴파일 되겠지요) 그리고 아이콘이나 이미지와 같은 실행을 제외한 데이터들을 담고 있는 리소스 부분이 있습니다.

자, DLL 컴포넌트는 웹프로그래밍 뿐만아니라. .Net 이든 C++ 이던 베이직이든 모든 언어들이 참조될수 있는 하나의 실행파일의 단위가 될 수 있습니다. 이러한 DLL 파일들이 하나의 어플리케이션에만 특히 참조되어 사용될 것이냐. 아니면 여러 어플리케이션이 공통적으로 사용된는 DLL 파일이냐에 따라 공용어셈블리와 사설 어셈블리 로 구분될 수가 있습니다. 우리가 ASP에서 Regsvr32 를 통해 DLL 파일을 레지스트리에 등록하고 사용 되던 것이 공용어셈블리라 볼 수 있으며, 게임이나 어떠한 일반 응용프로그램에서만 등록되어 활용되는 DLL 의 경우를 사설 어셈블리라할 수 있습니다. 사설 어셈블리는 레지스트리에 등록할 필요없이 참조 메뉴를 클릭해서 필요한 컴포넌트를 참조해서 그대로 쓰는 것입니다

우리는 웹 프로그래머이기 때문에 웹과 관련되어 이야기 해보겠습니다.

기존에 ASP로 프로그래밍을 하다보면 ASP 내에서 처리할 수 없는 부분 지금 저희가 배우고 있는 소켓관련이 라던가 캡슐화하여 돌리고 싶은 모듈등을 라이브러리(DLL) 로 만들어 내고 Regsvr32 를 통해 레지스트리에 등록 하게됩니다. 이것은 공용어셈블리의 경우가 되겠습니다. 공용어셈블리의 경우는, DLL을 참조 하고자 하는 파일과 같은 폴더에 없어도 사용이 가능합니다.(사설 어셈블리는 같이 있어야 합니다.) 대신 레지스트리에 등록을 해줘야 하는 절차가 필요합니다.(사설 어셈블리는 작업 파일과 함께 넣어 두면 됩니다.) 레지스트리에 등록된 DLL의 참조는 asp 의 Server 객체를 통해 객체를 생성해내고 만들어진 인스턴스는 메소드와 , 속성등을 통해 정보를 다루게 됩니다.

.Net Framework 환경에서 돌아가는 .Net 어셈블리의 경우 관리코드 라고 부른다면, .Net 이 아닌 c++ 이나 VB 환경에서 생성된 실행파일은 비관리 코드라고 부르게 된다는것을 이미 공부하신분들이라면 아실 것입니다. 문제는 .Net 의 공용어셈블리의 경우 기존의 DLL 과 같은 방식을 통해 ASP에서 사용될 수가 없습니다. .Net 에서의 파일 관리 방법은 레지스트리를 의존하지 않는데, 이는 어셈블리 자신이 서술형식의 메타데이터 정보 가지고 있기 때문에 더 이상 복잡한 방식의 레지스트리에 의존하지 않는 것입니다. 대신 이러한 어셈블리파일들에 대한 관리가 필요한데 그것은 Global Assembly Cache 라고 해서 C:\winnt\Assembly 폴더의 Shell Extension에 의해 특별하게 관리가 되고 있습니다. 그럼 문제가 한가지 생기게 되는데

.Net 은 레지스트리에 의존하지 않는, 메타정보와 Global Assembly 를 통해 DLL 을 관리하고, ASP에서 사용될 공용어셈블리는 레지스트리에 등록을 해야 한다는 것입니다.

그래서 .Net 에서는 DLL 컴포넌트를 기존의 COM 과 같이 작동시키게 하기 위한 레지스트리 등록방법으로 RegAsm.Exe 를 이용하게 됩니다.

c:>Regasm Pluginn.dll [설치]
c:>Regasm /u Pluginn.dll [제거]

사용법은 Regsvr32 와 같이 과 같은 식으로 레지스트리에 등록을 하게 됩니다. 그리고 공용으로 사용될 DLL 은 Global Assemly 에 등록을 하게 되는데 그것은 GacUtil 에 의해 Global Assembly Cache 에 등록을 하게 됩니다. 사용방법은,

c:> GacUtil /i Pluginn.dll [설치]
c:> GacUtil /u Pluginn.dll [제거]

[C:\Winnt\Assembly 경로로 접근해서 GAC 에 등록된 컴포넌트 목록을 본것입니다.]

자 여기까지 이해하셨으면 중요한것이 하나 남아 있는데.. 그것은 GAC 에 등록될 Assembly 들의버전관리를 위한 Strong Name입니다. 해석하면 강력한 이름으로 불리우는 이것은 Public키를 이용한보안 및 버전관리를 위한 해법을 제시해주고 있습니다. 자 일단 SN 을 생성하는 과정을 보면,

이렇게 되면 저만의 고유한 pluginn.key 라는 키 파일을 갖게 되고, 이것은 Gac 에 등록된 Assembly 파일의 위에 보시면 “Public Key Token" 항목에 나타난 token 을 생성하는데 쓰입니다.이렇게 gac 에 등록된 컴포넌트는 버전관리기능도 제공하게 되는데,

예를 들자면, A 라는 어플리케이션에서 test.dll 에 sum() 이라는 메소드를 사용하고 있습니다. 개발자에 의해 test.dll 은 업그레이드가 되어 test.dll 에 sum() 이 아닌 sum2() 로 메소드가 변경하게 되었다면기존의 A 어플레케이션은 DLL Hell 에 빠지게 될 것입니다. 이건 단적인 예의 경우지만 수많은 어플리케이션이 윈도우에 설치되면서, 레지스트리에 수없이 등록되고 삭제되는 DLL 의 경우라면 얘기가 달라지게 됩니다. (이미 격어 보신분들도 있으리라 생각됩니다. 저는 웹쪽만 하다보니 별로..-_-)

이러한 충돌현상을 피하기 위해 .Net 에서는 각 어셈블리의 파일 설명서인 AssemblyInfo.cs 파일을 가지게 되며,

SN.EXE 에 생성된 키 파일은 어셈블리의 메타정보인 AssemblyInfo.cs 파일에 등록되어

컴파일 되게 됩니다. 물론 아래와 같이 버전도 적을 수가 있습니다.

이렇게 sn 키를 포함하여 컴파일된 어셈블리가 되어야.. 비로소 GAC 에 등록이 될 수 있습니다.즉, .Net 에서는 같은 DLL 이라도 파일명과 버전 그리고 SN(StrongName) 에 의해서 어셈블리를다르게 보게되며, 충돌을 최소화 하도록 한것입니다. 사실 이부분에 대해서는 더 많은 내용을 다루어야하지만 우리는.Net 소켓으로 생성된 DLL 컴포넌트를 COM 으로 내보내서 ASP에서 사용하는것이 목적이기 때문에 이정도면 충분한 내용이 될꺼 같습니다.

많이 헤깔리신다면 다음 강좌때 사설어셈블리와 , 공용어셈블리를 직접 예로 해보는 세션을 가져보도록 하겠습니다.

그리고 추가로 스트림에서의 엔코딩 방식인 UTF7,UTF8에 대해 알아보려고 합니다. 문자 Encoding 에 대해서는 어떤 분이 메일로 질문을 해주셨는데.. 이부분을 강좌로다루는게 좋을거라고 생각이 들어 저도 좀 더 공부하고 보충에서 다음강좌에 추가하도록 하였습니다. 메일을 주신분께는 개인적으로 메일을 드리지 못해 죄송하고요, 다음 강좌를 통해궁금증을 해결하셨으면 좋겠습니다.

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


authored by


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

로딩 중입니다...

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