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

강좌 목록으로 돌아가기

필자의 잡담~

인기 폭발~~ 많은 분들이 관심을 가지고 기다리시던~~ 플러그인(pluginn@naver.com) 님의 .NET 소켓 프로그래밍 강좌. 그 다섯번째 시간입니다. 재미있게 보고 계시는 분은 격려 메일 한번 날려주세여~ ^^

벌써 2004년도 5월에 접어들고, 몇일이 지났습니다. 역시 시간은 상당히 빠르네요. 강좌를 3월부터작성하기 시작했는데 2개월이나 지났다니 지나고 나면 빠르고, 아쉽고 뭔가 허전한게 시간 아닌가 싶네요. 그리고 메일로 격려를 해주신 몇몇 분께 감사드립니다. 힘이나네요. 부족하지만 읽어주셔서다시한번 감사드립니다.

Socket UDP 프로그래밍

지난시간까지 Socket 클래스에 대한 자세한 사용법과 소켓 생성을 하기위해 서버와 클라이언트가갖춰야할 몇 가지 기본적은 구조에 대해 공부해 보았습니다. 이번시간에는 지난시간의 Server와Client 프로그램들을 UDP 프로그램으로 수정해 보겠습니다. "작성" 이 아니라 "수정" 이라는 말에느낌을 받으신분들이 있을듯 싶습니다. 예 그렇습니다. 구조적으로 똑같습니다. 단지 TCP 와 UDP가 가지는 개념이 조금 다르기 때문에 통신 방법이 다릅니다. 그렇기 때문에 지난 강좌의 TCP 소스를이해하셨다면 UDP를 쉽게 이해하실수 있습니다. 좀 불편하시더라도 4강좌의 소스를 참고해 주시기 바랍니다.

[서버측 소스]

- 소스생략 -

try
{
    //Server = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
    Server = new Socket(AddressFamily.InterNetwork,SocketType.Dgram,ProtocolType.Udp);
    Server.Bind(serverEndPoint);
    //Server.Listen(10);

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

    //Client = Server.Accept();

Socket 클래스의 생성자 인자들의 구성이 UDP 로 바뀌었습니다. 서버로 사용된 EndPoint 객체를 Bind 하였습니다, UDP 는 TCP와 같이 연결하고, 데이터를 가져오는 연결과정(3Way-HandShaking)이 필요없기 때문에, 접속자들을 BackLag에 쌓아두고 하나씩 연결을 설정할 필요가 없습니다. 그래서 Listen 메소드를 사용할수 없습니다. 클라이언트 측에서 말씀을 드린다면 데이터를 전송하면 그만인 것입니다. 그것이 잘갔는지, 패킷이 오류가 있어서 다시 전송을 요청해야 하는지는, UDP는 다른 데이터에 밀려 늦게 수신 되던지 알 수 없고 관심도 없기 때문입니다. 1강좌에서 말씀드렸듯이 방송파와 비슷한 상태입니다. KBS(클라이언트가 데이터를 발송하므로..)에서 전국에 KBS 방송파를 뿌립니다. 각 가정집은 방송파를 수신하겠지만 지방이나 각, 집안 사정마다 수신되는 차이가 있을수도 있고, 아예 방송파가 못 받는 집이 있을것입니다. KBS는 방송파를 보내면 그만이지 방송파로 수신이 잘되는지 안되는지를 다시 수신하지 않는것과 같습니다. Server.Accept(); 와 같은 연결설정 메소드도 이용하실 수 없습니다. 쉽게 정리를하지면 Server.Bind() 만으로 UDP 연결설정이 모두 끝나고 , Send() 자체로 연결과 발송이 동시에 이루어 집니다. 데이터를 보내는 순간 연결해서 발송하게 되는것입니다.

//if(Server.Connected)
//{

    while(true)
    {
        Server.Receive(getByte,0,getByte.Length,SocketFlags.None);
        stringbyte = Encoding.UTF7.GetString(getByte);
        Console.WriteLine(Server.Connected);
        if (stringbyte != String.Empty)
        {
            int getValueLength = 0;

            getValueLength = byteArrayDefrag(getByte);
            stringbyte = Encoding.UTF7.GetString(getByte,0,getValueLength+1);

            Console.WriteLine("수신데이터:{0} | 길이:{1}",stringbyte,getValueLength+1);
            setByte = Encoding.UTF7.GetBytes(stringbyte);
            Server.Send(setByte,0,setByte.Length,SocketFlags.None);
        }
    }
//}

위의 소스 수정으로 모든 수정이 끝났습니다. Server.Connected 역시 클라이언트가 접속했는지 여부는UDP는 중요하지 않습니다. UDP 연결이란 개념이 없기 때문입니다. 지금쯤 혼란이 조금 오시거나 헤깔리시는 분들은 다시 1강좌 내용을 복습하시고 보시기 바랍니다. 이제 클라이언트 측 소스를 보겠습니다.

[클라이언트 소스]

    //socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
    socket = new Socket(AddressFamily.InterNetwork,SocketType.Dgram,ProtocolType.Udp);

    Console.WriteLine("-----------------------------------------------------");
    Console.WriteLine(" 서버로 접속을 시작합니다. [엔터를 입력하세요] ");
    Console.WriteLine("-----------------------------------------------------");
    Console.ReadLine();

    socket.Connect(serverEndPoint);

클라이언트 소스는 이게 끝입니다. 소스를 보시면 마지막줄 소스에 조금 의문을 가질것입니다.

socket.Connect(ServerEndPoint);

연결이란게 서버에 없었는데, 클라이언트에는 왠 연결.?. 이것은 EndPoint(서버측연결점)를 바인드 하기위한, 하나의 Connect 메서드일 뿐입니다.즉 연결설정이 되는것입니다. 연결과 전송의 개념을 포함하고 있는것은 역시, Server.Send() 메소드가 되겠습니다. 자 조금 혼란이 오실 수 있을것입니다.자 TCP 와 UDP 개념을 조금 이해하신 분이라면 쉽게 이해 할 수 있는 소스입니다. 기존에 제대로 작동되던 화면을 잠깐 보면,

우측 클라이언트에서 hi 라고 발송된것이 , 좌측의 서버측이 수신하면 화면에 출력하고 다시 클라이언트측으로 받은 데이터를 발송하게 됩니다. 그럼 수정된 UDP 소켓으로 작성된 프로그램을 실행해보겠습니다.

위에 클라이언트에서 발송한 데이터 "hi"를 서버가 수신을 잘 하였는데, 서버가 수신한 데이터를 다시클라이언트에게 발송하려니 저러한 오류가 발생되었습니다. TCP 통신에서는 서버와 클라이언트간의연결과정(3way HandShaking)에서 클라이언트와 서버간의 소켓 정보가 이미 교환됩니다.이 과정에서 연결이 끝나면 서버소켓의 accept() 메소드는 클라이언트측의 소켓정보를 가져오고, 그것으로 서버와 클라이언트간의 세션이 성립되어 서로간의 통신이 용이하게 됩니다. 하지만 UDP는 연결이 없는 비연결(Connnectless)지향 프로토콜이기 때문에 전송할 상대방의 IP와 포트로 또 하나의 소켓을 생성해 주어야 합니다. 아래 간단하게 그림으로 정리해 보겠습니다.

연결지향형인 TCP 의 경우 클라이언트와 서버간의 연결에 대한 인증부분이 끝난후 세션이 설정되고서로 Send/Receive 가 가능한 상태입니다.

비연결지향형인 UDP는 연결설정이 없으므로 바로 Send 메소드로 데이터를 발송합니다. TCP와 같은 연결 설정이 없으므로 어디서 데이터가 왔는지 알 수 없습니다. 일부 해커나 컴퓨터 침입자들이익명성을 가진 UDP 프로토콜을 이용한 시도가 많이 이루어 지곤합니다. 그래서 기관이나 관공서 같은 방화벽을 갖추고 있는 시스템에서는, UDP 를 지원하지 않는다고도 합니다.

위와같은 개념으로 좌측에서는 Receive 를 기다리고 있으므로 우측컴퓨터에서 Send 하게 되면,Send 와 Receive 가 이루어 지지만, 우측에서 좌측으로 Send 할 경우 좌측컴퓨터(clinet 모듈)에서는접속을 기다리는 부분은 만들지 않았으므로 일방적인 전송만 가능하게 되는것입니다.아래는 쌍방향이 가능하도록 수정해본 그림입니다.

서로 2개의 소켓을 생성하여, 데이터를 보낼때는 1번소켓으로 받을때는 2번소켓으로 구성한 형식입니다. 물론 Connect-less 합니다. 쌍방향으로 통신이 가능하도록 코딩하도록 수정해보겠습니다.서버와 클라이언트의 관계는 서로 서버고 서로 클라이언트가 되는것입니다. 숙지하시고 소스를 보시기 바랍니다.

[서버]

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

namespace ServerSideSocket
{
    class ServerClass
    {
        public static Socket Server , ServerYou , Client;
        
        public static byte[] getByte = new byte[1024];
        public static byte[] setByte = new byte[1024];
        
        public const int sPort = 5000;
        public const int sPortyou = 5001;
        
        [STAThread]    static void Main(string[] args)
        {
            string stringbyte = null
            IPAddress serverIP = IPAddress.Parse("127.0.0.1");
            IPAddress serverIPYou = IPAddress.Parse("127.0.0.1");

            IPEndPoint serverEndPoint = new IPEndPoint(serverIP,sPort);
            IPEndPoint serverEndPointYou = new IPEndPoint(serverIPYou,sPortyou);
            
            try
            {
                Server = new Socket(AddressFamily.InterNetwork, SocketType.Dgram,
                                ProtocolType.Udp);

                ServerYou = new Socket(AddressFamily.InterNetwork, SocketType.Dgram,
                                ProtocolType.Udp);

                
                Server.Bind(serverEndPoint);
                ServerYou.Connect(serverEndPointYou);

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

                while(true)
                {
                    Server.Receive(getByte,0,getByte.Length,SocketFlags.None);
                    stringbyte = Encoding.UTF7.GetString(getByte);
                    Console.WriteLine(Server.Connected);
                    if (stringbyte != String.Empty)
                    {
                        int getValueLength = 0;

                        getValueLength = byteArrayDefrag(getByte);
                        stringbyte = Encoding.UTF7.GetString(getByte,0,getValueLength+1);

                        Console.WriteLine("수신데이터:{0} | 길이:{1}",
                                        stringbyte,getValueLength+1);
                        setByte = Encoding.UTF7.GetBytes(stringbyte);
                        ServerYou.Send(setByte,0,setByte.Length,SocketFlags.None);
                        Console.WriteLine("(((((발송했음-" +
                                        Encoding.UTF7.GetString(setByte) + ")))");
                    }
                }
            }

            catch(System.Net.Sockets.SocketException socketEx)
            {
                Console.WriteLine("[Error]:{0}",socketEx.Message);
            }
            catch(System.Exception commonEx)
            {
                Console.WriteLine("[Error]:{0}",commonEx.Message);
            }
            finally
            {
                Server.Close();
                ServerYou.Close();
            }
    }

        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;
        }
    }
}

중요한 것은 위의 그림의 토대로 작성된 부분입니다. 중요한 부분만 보도록 하겠습니다.

public const int sPort = 5000;
public const int sPortyou = 5001;

IPAddress serverIP = IPAddress.Parse("127.0.0.1");
IPAddress serverIPYou = IPAddress.Parse("127.0.0.1");

IPEndPoint serverEndPoint = new IPEndPoint(serverIP,sPort);
IPEndPoint serverEndPointYou= new IPEndPoint(serverIPYou,sPortyou);

Server = new Socket(AddressFamily.InterNetwork , SocketType.Dgram,ProtocolType.Udp);
ServerYou = new Socket(AddressFamily.InterNetwork , SocketType.Dgram , ProtocolType.Udp);

Server.Bind(serverEndPoint);
ServerYou.Connect(serverEndPointYou);

녹색으로 표기된 부분들의 소스입니다. 위의 그림처럼 송/수신할수 있도록 2개의 소켓을 준비하고줄친 부분들은 수신용으로 이용될 소켓의 속성이 되겠습니다. 현재 PC의 127.0.0.1:5000 이 수신용(서버)으로 기다리게될 소켓이고 127,0.0.1:5001 이 발신용(클라이언트)이 될 소켓 속성이 되겠습니다.노란색부분이 실제 메소드를 처리하는 부분입니다. 아래와 같습니다. 소켓 객체를 소스를 따라 찾아보면 쉽게 이해가 가실 것입니다.

Server.Receive(getByte,0,getByte.Length,SocketFlags.None);ServerYou.Send(setByte,0,setByte.Length,SocketFlags.None);

[클라이언트]

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

namespace ClientSideSocket
{
    class ClientClass
    {
        public static Socket socket,socketme;
        public static byte[] getbyte = new byte[1024];
        public static byte[] setbyte = new byte[1024];

        public const int sPort = 5000;            // 발신용
        public const int sPortMe = 5001;        // 수신용

        [STAThread] static void Main(string[] args)
        {
            string sendstring = null

            int byteEndLength = 0;

            IPAddress serverIP = IPAddress.Parse("127.0.0.1");
            IPAddress ServerIPMe = IPAddress.Parse("127.0.0.1");

            IPEndPoint serverEndPoint = new IPEndPoint(serverIP,sPort);
            IPEndPoint serverEndPointMe = new IPEndPoint(ServerIPMe,sPortMe);

            socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram,
                            ProtocolType.Udp);
            socketme = new Socket(AddressFamily.InterNetwork, SocketType.Dgram,
                            ProtocolType.Udp);

            Console.WriteLine("-----------------------------------------------------");
            Console.WriteLine(" 서버로 접속을 시작합니다. [엔터를 입력하세요] ");
            Console.WriteLine("-----------------------------------------------------");
            Console.ReadLine();

            socket.Connect(serverEndPoint);
            socketme.Bind(serverEndPointMe);

            if (socket.Connected)
            {
                Console.WriteLine(">> 정상적으로 연결되었습니다(전송한 데이터를 입력해주세요)");
            }

            while(true)
            {
                        Console.Write(">>");
                sendstring = Console.ReadLine();
                sendMessage(sendstring);
            }
        }

        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;
        }

        public static void sendMessage(string sendmsg)
        {
            string getstring = null

            if(sendmsg != string.Empty)
            {
                int getValueLength = 0;

                setbyte = Encoding.UTF7.GetBytes(sendmsg);
                socket.Send(setbyte,0,setbyte.Length,SocketFlags.None);

                socketme.Receive(getbyte,0,getbyte.Length,SocketFlags.None);

                getValueLength = byteArrayDefrag(getbyte);
                getstring = Encoding.UTF7.GetString(getbyte,0,getValueLength+1);

                Console.WriteLine("(수신된 데이터 :{0} | 길이{1})" , getstring ,
                              getValueLength+1);
            }
            else
            {
                Console.WriteLine("수신된 데이터 없음");
            }
        }
    }
}

클라이언트 측 소스도 서버와 상당히 비슷합니다. 조금 다른점이 있다면 발송 부분을 메소드로 빼서작성해 보았습니다. 서버측 소스와 Console.ReadLine 으로 받아 sendMessage() 로 처리하는 부분을빼고는 같습니다. 최종 실행된 화면입니다.


Thread(쓰레드)

윈도우의 TaskManager 를 띄워보시면, 윈도우 안에서 다중으로 돌아가는 Process 들을 보실 수 있습니다. 이렇게 동시에 어러개의 Process 들을 처리하는것을 멀티 프로세싱 이라고 한다면,프로세스 하나안에서 또 여러개의 작업들이 이루어 질 수 있습니다. 그것을 멀티 쓰레드 라고 합니다.ASP에서 .Net 으로의 한단계 업그레이드된 개발자라면 Thread 처리를 반드시 이해야할 것입니다.소켓에 대한 강좌 이므로 쓰레드에 대한 내용은 간단하게 알아보고 넘어가도록 하겠습니다.

하나의 프로세스안에서 여러개의 쓰레드가 동작하는 경우는 게임을 예로드는게 가장 쉬울꺼같습니다.일반적으로 간단하게 작성된 프로그램에서는 하나의 사건만을 다루게 됩니다. 하지만 게임 같은경우는미사일을 쏘면서 방향키를 조작하게됩니다.(물론 더 복잡합니다만),

미사일키은 Ctrl 키를 누루는 순간 저러한 메소드를 계속 호출하게 될껏이고,

private int AttackMissile(int arms)
{
    // 무기를 발사한다.
}

방향키를 누르는 순간에는..

private int MoveAirPlain(KeyCode)
{
    switch()
    {
        case    // 우측
        case    // 좌측

    }
}

이러한 메소드가 수없이 호출 될 것입니다. 하지만 문제는 미사일을 쏘면서 항상 방향키를 움직이는데 있습니다. 한번만 호출되는것이 아니라 계속입니다. 물론 사용자가 버튼을 누르는 속도보다 내부에서컴퓨터가 방향키 메소드를 처리하고 미사일을 처리하는 속도가 빠르지않냐고 이유를 다신다면 할말을 없습니다만, 채팅과 이나 여러사용자가 동시에 접속해서 다중으로 소켓을 처리하기 위해서는 반드시쓰레드가 필요하여, 채팅뿐만아니라 다중사용자 처리에서는 1 User 당 1 Thread 로 처리하여 프로그래밍 해야합니다. 쓰레드를 이용한 소켓 통신은 7강좌 정도에서 지금까지 배운 내용으로 ASP 와 .Net 소켓 간의 통신을 위해 작은 프로젝트를 할 때 자세히 알아 보도록 하겠습니다.

이번시간에 고수준의 TCP 소켓 객체를 알아보려고 했는데 좀 길어져서 다음시간에 고수준 TCP 클래스를 알아보고, 공용어셈블리와 사설 어셈블리에 대해서 알아보도록 하겠습니다.

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


authored by


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

로딩 중입니다...

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