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

강좌 목록으로 돌아가기

필자의 잡담~

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

4번째 강좌 시간입니다. 이번시간에는 Socket(저수준) 클래스를 이용한 프로그램을 완성해보고 이해하는데 중점을 두겠습니다. 지난 시간에 심플하게 콘솔 어플리케이션으로 만들어 보았는데 무책임하게 소스만 보여드리고 강좌를 접어 버렸었네요. 하지만 그 소스를 코딩해보고 많은 궁금증을 가지셨거나, 최소한 이러한 부분들은 "왜?" 라고 의문을 갖으션 분들은 한 단계 업그레이드 되는 시간이 될껍니다. 물론 지금까지 강좌를 읽어오신분들도 한 단계 업그레이드 될껍니다. ^^

서버 & 클라이언트

예전의 Host 와 Terminal 환경에서 현재의 Server 와 Client 환경을 보면 그 차이를 확연히 느낄수 있습니다. 중앙 집중식이었던 Host 시절에는 일방적인 Response/Request 였다면 Server & Client 환경은 interactive 한 Response/Request 라고 할 수 있습니다. 예전의 중앙집중식 host 방식은 데이터와 클라이언트 프로그램을 서버에 모두 올려서 클라이언트에서는 (윈도우 터미널처럼) 서버에 접속해서 프로그램을 실행하고, 또 데이터를 가져오는 서버에 집중적인 반면 ,현재의 Server & Client 환경은 똑똑한 Client 덕분에 Client 와 Server 할 일이 구분되고(서버스크립,클라이언트 스크립 으로 구분되듯이) Client 가 서버에게 Request 하고 Server 는 Client에서 Response 되는 좀 더 지능적이고 서버에 Load 를 덜어주는 진보적인 형태로 발전해 왔습니다.

결국 현재 소켓 프로그램도 서버와 클라이언트에게 Response 하고 Request 할 수 있는 통로를 제공하고 클라이언트 부분 소켓은 클라이언트 부분을 고려하여 예외처리나 오류 발생시 사용자 인터페이스 처리, 서버측 에는 서버측 리소스 관리를 위한 효율적인 코딩 및 다중 접속자를 위한 비동기(쓰레드)처리, 로그처리 등등으로 개발하여 좀 더 진보적으로 통신할 수 있는 환경을 제공하도록 하는 것입니다.

3강을 통하여 기본적인 소켓 통신에 대해 배웠습니다(물론 10% 도 안되는 내용이지만 이것은 나머지 90%를 움직이는데 해당되는 중요한 내용입니다.).자.. 지금까지 강좌를 통해 이해하지 못했던 부분이 있다면 이번강좌에 해결하도록 해야 합니다. 물론 이번강좌에서 다 해결하기에는 부족 합니다 단지, 이번강좌를 통해서 1,2,3 강좌를 다시 읽었을때 "아~" 하고 이해 하는 계기가 되야 한다는 말입니다.

위의 서버와 클라이언트의 개념을 가지고 서버는 부분은 더욱 서버답게 클라이언트는 클라이언트 답게프로그램을 작성해보도록 하겠습니다.

서버측 소켓 프로그래밍

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

namespace ServerSideSocket
{
    class ServerClass
    {
        public static Socket Server , Client;
        
        public static byte[] getByte = new byte[1024];
        public static byte[] setByte = new byte[1024];
        
        public const int sPort = 5000;
        
        [STAThread]
        static void Main(string[] args)
        {
            string stringbyte = null
            IPAddress serverIP = IPAddress.Parse("127.0.0.1");
            IPEndPoint serverEndPoint = new IPEndPoint(serverIP,sPort);
            
            Server= new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
            Server.Bind(serverEndPoint);
            Server.Listen(10);

            Console.WriteLine("-----------------------------------------------------");
            Console.WriteLine(" 클라이언트의 연결을 기다립니다........ ");
            Console.WriteLine("-----------------------------------------------------");
                                    
            Client = Server.Accept();
    
            if(Client.Connected)
            {
                while(true)
                {
                    Client.Receive(getByte,0,getByte.Length,SocketFlags.None);
                    stringbyte = Encoding.UTF7.GetString(getByte);
                        if (stringbyte != String.Empty)
                    {
                        Console.WriteLine("수신데이터:{0} | 길이:{1}",stringbyte,stringbyte.Length);
                        setByte = Encoding.UTF7.GetBytes(stringbyte);
                        
                        Client.Send(setByte,0,setByte.Length,SocketFlags.None);
                    }
                }
            }
        }
    }
}

일단 서버측 소스를 분석후 클라이언트측의 소스를 작성하고 그 다음 소스를 보강해 나가겠습니다.자~~ 저번소스와 시작은 비슷합니다. 그리 어렵지는 않습니다.

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

자~~ using 지시어를 통해 전 시간에 배웠던 각종 네임스페이스들을 모두 선언합니다. 간단하게 살펴보면 System.Net 클래스에는 소켓이 필요로 하는 연결 종점 생성을 위한 IPAddress,IPEndPoint , Dns 클래스 등이 포함되어 있고 , System.Net.Sockets 클래스에는 우리가 생성할 소켓클래스가 포함되어져 있습니다.

System.Text는 System.Text.Encoding 클래스를 사용하기 위해 선언했는데, 간단히 설명을 드리자면 .Net 에서의 소켓통신은 Byte 단위로 통신을 한다고 말씀 드렸습니다. 하지만 특별한 경우가 아니고서는 일반적으로 Text 에 대한 처리는 String 으로 하게됩니다. Console.ReadLine(); 이나 윈폼,웹폼과 같은 어플리케이션에서의 입력 컨트롤에서 의 Value 들도 대부분 String 일것입니다.

Byte 단위는 그만큼 다루기가 불편하고 효율성이 떨어지기 때문입니다. 하지만 저희는 byte 통신을해야하기 때문에 System.Text.Encoding 클래스를 사용하여, string 단위의 문자열을 Byte 단위로 쉽게변환하고 Byte 단위의 배열을 String 으로 쉽게 변환 하게 하기 위해서 Text 네임스페이스를 포함 했습니다

(Byte <-> String , String <-> Byte 변환시 데이터 포맷까지 지정해 줄 수 있기 때문에 한글과 같은 2byte 문자 전송시 포맷변환도 해 줄 수 있습니다.)

namespace ServerSideSocket
class ServerClass

자~~ 서버의 네임스페이스는 ServerSideSocket 으로 임으로 제가 지정했습니다. Class 도 ServerClass로 임으로 지정했습니다.

class ServerClass
{
    public static Socket Server , Client;
    public static byte[] getByte = new byte[1024];
    public static byte[] setByte = new byte[1024];
    public const int sPort = 5000;

이번에는 외부로 선언된 각종 변수 들입니다. 클래스 내부에 선언해서 Main 함수에서 사용 가능하도록정적으로 선언하였습니다. 하지만 지금과 같은 프로그램에서는 Main 함수 내부에 정의하여 사용하여도무방합니다. 클래스를 인스턴스로 생성해서 사용하지 않을 것이기 때문입니다. 하지만 저는 구지클래스 변수들로 선언하여 Main 메소드에서 사용해봤습니다.

Server 소켓 객체는 서버측의 IPEndPoint 로 bind 돼서 Accept 되어 클라이언트를 기다리게 될 서버측소켓 객체 이고, Client 객체는 Server.Accept() 메소드로부터 리턴되는 클리언트 측의 소켓 객체를받기 위해 선언한 소켓 객체입니다. getByte 와 setByte 는 변수명에서 알 수 있듯이 데이터를 받고, 보내고 할 변수(버퍼)가 되겠습니다. .Net 에서의 소켓통신은 바이트 이기 때문에 byte 형 으로 선언 되었고, 데이터가 글자 한자가 아니라 여러 자도 가능하도록 배열로 선언하였습니다.

서버에서 클라이언트를 기다리게될 포트는 5000 번으로 임으로 지정했습니다. sPort 에 static 을 붙이지 않은 이유는 const 변수 자체가 정적이기 때문입니다. 다음 소스를 보겠습니다.

[STAThread]
static void Main(string[] args)
{
    string stringbyte = null
    IPAddress serverIP = IPAddress.Parse("127.0.0.1");
    IPEndPoint serverEndPoint = new IPEndPoint(serverIP,sPort);

    Server= new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
    Server.Bind(serverEndPoint);
    Server.Listen(10);

stringbyte 변수를 아래 소스에서 쓰일 수신된 byte 배열의 변수의 데이터를 string 형태로 변환하여 저장할 변수가 되겠습니다. IPAddress 와 IPEndPoint 클래스로 정의된 객체변수의 경우 3강에서 알아봤듯이 Socket 클래스가 bind 될 EndPoint 를 생성하기 위해, 각각 서버의 IP는 현재 로컬 IP 로써 LoopBack 주소를 이용하였습니다. port 는 const로 선언한 변수를 이용 하였습니다.

소켓의 열거형으로 선언된 속성들을 가만이 살펴보면, TCP 로 통신하기위한 속성이라는것을 쉽게 알아 보실 수 있을 껍니다. Server.Listen(10) 는 소켓의 BackLog 를 10개를 지정 했습니다클라이언트의 동시 접속이 있을 경우 Queue 에 10개까지 저장 하겠다는 뜻입니다.

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

자~~ 클라이언트가 접속 가능하도록 준비를 모두 끝냈습니다. 이제 Accept 메소드를 호출하기 전에Text를 출력하여 진행 상태를 알도록 해 놓았습니다. 지금까지 이상이 없다면 예외가 발생하지않고, 위의 텍스트가 출력될것입니다.

Accept() 메소드를 호출하는 순간 스레드가 멈춤니다. 그러므로 Accept() 메소드를 호출 하기 전에text 를 먼저 출력하였습니다.

    if(Client.Connected)
    {
        while(true)
        {
            Client.Receive(getByte,0,getByte.Length,SocketFlags.None);
            stringbyte = Encoding.UTF7.GetString(getByte);

            if (stringbyte != String.Empty)
            {
                Console.WriteLine("수신데이터:{0} | 길이:{1}",stringbyte,stringbyte.Length);
                Client.Send(setByte,0,setByte.Length,SocketFlags.None);
            }
        }

이 부분이 약간 중요합니다. 왜 무한 루프를 돌아야 하는지 알아야 합니다. 서버는 클라이언트로부터발신된 데이터를 언제 수신해야할지 알 수 없습니다. 반대로 클라이언트는 데이터를 입력하고 [Enter] 를 치는 순간 발송되게 하면 되기 때문에 발송될 이벤트 시간을 알 수 있습니다. 그래서 무한 Loop 를 돌려 항상 Receive 해야 합니다. 그래서 서버의 부하가 큰편입니다.서버에 100명의 사용자가 접속했다면 100개의 소켓 객체가 각각의 클라이언트의 소켓을 무한루프를 돌며 검사하게 됩니다.그래서 서버의부하가 크게됩니다.

위의 설명을 참조로 소스를 설명하자면 이런 시나리오가 됩니다.클라이언트로부터 데이터가 언제 올지 모르는 서버는 무한 Loop 를 돌면서 Accept() 로 얻은 클라이언트 측 소켓을 항상 검사하여 데이터 유무를 확인합니다. 만약 데이터가 수신되면(클라이언트 측에서 소켓으로 데이터를 발송하면) getByte 배열에 저장한후 stringbyte 로 byte 배열의 문자들을 문자열로 바꾼후 String.Empty 로 수신된 데이터가 있는지 없는지 검사하게 됩니다. 수신된 데이터가 있다면 화면에 출력하고, 수신된 데이터를 발송한 클라이언트 측으로 재 발송하게 됩니다.

Client.Receive(getByte,0,getByte.Length,SocketFlags.None);

Recevie 나 Send 메소드의 경우 같은 시그너쳐를 같게되는데, 좀 자세하게 살펴보자면, getByte 는 배열에 데이터를 받되, 2번째 인자인 0부터 3번째 인자인 배열의 길이 만큼 그러니까, 1024 까지 저장할 수 있도록 해줍니다. SocketFlags 옵션은 none 으로 여기서 사용하지 않았습니다.

참고로 Receive 나 Send 메소드의 경우 int 형으로 송/수신된 데이터의 길이를 리턴하게 됩니다.

int gDataLength = Client.Receive(getByte,0,getByte.Length,SocketFlags.None);

이런식으로 gDataLength 변수의 값을 체크하여 데이터가 유무를 확인 할 수 도 있습니다.

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

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

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

        public const int sPort = 5000;

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

            IPAddress serverIP = IPAddress.Parse("127.0.0.1");
            IPEndPoint serverEndPoint = new IPEndPoint(serverIP,sPort);

            socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
            Console.WriteLine("-----------------------------------------------------");
            Console.WriteLine(" 서버로 접속을 시작합니다. [엔터를 입력하세요] ");
            Console.WriteLine("-----------------------------------------------------");
            Console.ReadLine();

            socket.Connect(serverEndPoint);

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

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

                if(sendstring != String.Empty)
                {
                    setbyte = Encoding.UTF7.GetBytes(sendstring);
                    socket.Send(setbyte,0,setbyte.Length,SocketFlags.None);

                    socket.Receive(getbyte,0,getbyte.Length,SocketFlags.None);
                    getstring = Encoding.UTF7.GetString(getbyte);
                    Console.WriteLine(">>수신된 데이터 :{0} | 길이{1}" , getstring , getstring.Length);
                }
                            }
        }
    }
}

서버 측 소스를 유심히 보셨다면 비슷한 부분이 많다는것을 아실 수 있을 것입니다. 서버측을이해하셨다면 클라이언트측도 쉽게 이해하실 수 있을 것입니다.

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

    public const int sPort = 5000;

서버측에서 선언한 변수들과 같은 목적으로 쓰일 클라이언트 변수 들입니다. 한가지 집고 넘어갈 것은sPort 부분입니다. 서버에서는 1 ~ 1024 포트(시스템에서 지정된포트)를 피해 임으로 지정했습니다만,클라이언트는 서버로의 접속을 해야 하기 때문에 서버측의 포트를 EndPoint 포트로 입력해 주어야 합니다.

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

    IPAddress serverIP = IPAddress.Parse("127.0.0.1");
    IPEndPoint serverEndPoint = new IPEndPoint(serverIP,sPort);

Console.Readline() 으로 수신될 문자열을 저장할 sendstring 변수와 서버로부터 받은 byte 타입의데이터를 문자열로 저장할 getstring 변수를 각각 선언하였습니다. 서버측과 마찬가지로 byte <-> string , string <-> byte 변환 과정은 System.Text 네임스페이스의 Encdoing 객체를 이용해서 변환하였습니다. 마지막 2줄은 많이 본 듯한 소스입니다. 그렇습니다. 소켓이 bind 할 서버측 EndPoint인 연결 종점이 되겠습니다.

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

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

    socket.Connect(serverEndPoint);

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

IPv4 와 TCP 형태의 속성으로 소켓을 생성하고, 서버측으로 접속할 EndPoint 를 매개 객체로 넘겨접속을 시도합니다. 이때 접속 하는 시점을 권한을 유저에게 주기위해 메시지를 출력후 Console.ReadLine() 명령을 주어 잠시 대기 시켰습니다. 연결이 완료되면 Socket 객체인 socket 의 속성중 Connected 속성을 이용해서 접속이 완료되었는지 검사합니다. (접속이 정상적으로 이루어 졌을 경우 true 를 리턴합니다.)

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

    if(sendstring != String.Empty)
    {
        setbyte = Encoding.UTF7.GetBytes(sendstring);
        socket.Send(setbyte,0,setbyte.Length,SocketFlags.None);
        socket.Receive(getbyte,0,getbyte.Length,SocketFlags.None);
        getstring = Encoding.UTF7.GetString(getbyte);
        Console.WriteLine(">>수신된 데이터 :{0} | 길이{1}" , getstring , getstring.Length);
    }
}

클라이언트 부분에서도 무한 루프로 돌린 이유가 있는데, 채팅과 비슷한 효과로 계속해서 테스트 해보기 위해서입니다. 루프를 돌면서 sendstring = Console.ReadLine();부분에 멈춰서 사용자 입력을기다립니다.

setbyte = Encoding.UTF7.GetBytes(sendstring);

사용자가 입력한 데이터를 담고있는 sendstring 변수내용을 Encoding 객체의 메소드를 이용해서 UTF7 포맷으로 Byte배열로 넘기는 모습니다. 한글을 UTF7 포맷을 이용해야 깨지지 않고 제대로 전송됩니다.socket.Send(setbyte,0,setbyte.Length,SocketFlags.None); 로 입력된 데이터를 서버측으로 전송하면서버측에서는 다시 클라이언트로 되돌려보내므로 바로 Receive 하여 서버로 부터의 수신이 정상적인지확인 합니다.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

위와 같이 소스를 작성하셨으면 이제 실행을 합니다.

자~~ 3강에서의 소스와 같이 수신데이터 이유의 출력 문자열 과의 사이가 많이 떨어진것을 볼 수 있습니다. 이것은 수신/발신 버퍼로 사용되는 byte 배열의 크기 때문인데.. 우리가 "hi" 라고 단지 2byte만 발송했는데 아래와 같이..

Client.Receive(getByte,0,getByte.Length,SocketFlags.None);

0 ~ 1024 (getByte.Length) 만큼을 발송하고, 수신하기 때문에.. 이러한 현상이 발생된 것입니다.

즉, 1024 byte 중 맨앞에는 hi라고 데이터가 들어가지만 나머지 부분은 모두 공백으로 초기화 된 것입니다.(실제로 byte 배열안에는 0 으로 초기화 됩니다.)

자 그럼 일단 저러한 현상이 발생되지 않도록 소스를 수정해 보겠습니다.

서버와 클라이언트 소스에 아래의 메소드를 추가합니다.

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

이 메소드가 하는일은 위의 문제점을 해결하기 위한 메소드입니다. 메소드의 시그너쳐를 보면byte 배열의 type 을 갖는 sData 변수로 데이터 배열을 넘깁니다. 그럼 내부에서는 for 문을sData 가 받은 배열의 sData.Length 까지 돌면서 (byte)sData[i] != (byte)0 일때 까지 즉,사용자가 입력한 데이터가 아닌 byte 배열의 초기화 문자인 (byte)0 이 나오는 배열의 첨자를알아내여 int 형태로 리턴하게 됩니다.

그리고 서버측의 소스를 아래와 같이 수정합니다.

[원본소스]
if (stringbyte != String.Empty)
{
    Console.WriteLine("수신데이터:{0} | 길이:{1}",stringbyte,stringbyte.Length);
    setByte = Encoding.UTF7.GetBytes(stringbyte);
    Client.Send(setByte,0,setByte.Length,SocketFlags.None);
}
[수정후 소스]
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);
    Client.Send(setByte,0,setByte.Length,SocketFlags.None);
}

byteArrayDefrag 메소드로부터 수신한 return endLength; 값을 getValueLength 로 수신하여,

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

배열의 범위를 지정하여 string 으로 리턴하기위해 오버로드된 메소드를 사용합니다.

stringbyte = Encoding.UTF7.GetString(getByte,0,getValueLength+1);
Console.WriteLine("수신데이터:{0} | 길이:{1}",stringbyte,getValueLength+1);

밑줄 그은 부분을 보면 +1을 했는데 byteArrayDefrag 메소드가 리턴하는 값은 위에서 설명한 것처럼배열의 첨자 이기 때문에 +1을 해줬습니다. 배열은 0부터 시작하기 때문에 "hi" 라고 2바이트를 입력할 경우 아래와 같이 2byte가 입력 되었지만 첨자는 길이의 -1 을 가리키고 있기 때문에+1을 해준 것입니다.

sData 배열 첨자01234
입력된 데이터hi(byte)0(byte)0(byte)0

클라이언트 부분도 마찬가지입니다. 위의 메소드를 추가하고 아래와 같이 클라이언트 부분도 수정해 줍니다.

[원본소스]
if(sendstring != String.Empty)
{
    setbyte = Encoding.UTF7.GetBytes(sendstring);
    socket.Send(setbyte,0,setbyte.Length,SocketFlags.None);

    socket.Receive(getbyte,0,getbyte.Length,SocketFlags.None);
    getstring = Encoding.UTF7.GetString(getbyte);
    Console.WriteLine(">>수신된 데이터 :{0} | 길이{1}" , getstring , getstring.Length);
}
[수정후 소스]
if(sendstring != String.Empty)
{
    int getValueLength = 0;
    setbyte = Encoding.UTF7.GetBytes(sendstring);
    socket.Send(setbyte,0,setbyte.Length,SocketFlags.None);
    socket.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);
}

이제 수정된 소스로 실행해 보겠습니다.

수정되기전 과 보면 깔끔하게 송/수신 되는 모습을 보실 수 있을 것입니다.자 이제 모든 부분이 확인이 끝나고, 프로그램을 종료해 보겠습니다. 클라이언트측을 먼저 종료하게되면, 아래와 같은 예외를 만나게되는데, .Net 에서는 이러헌 오류에 대해모두 예외로 처리되며, 실제적으로 해당되는 예외클래스를 가지고 있습니다.

위에서 잠깐 설명드렸지만, 서버측 프로그램은 접속한 클라이언트의 소켓부분 무한루프를 돌면서폴링하게 됩니다. 즉, 서버측에서 무한 루프를 돌며, 클라이언트의 소켓을 폴링하고 있는 상태에서클라이언트측 프로그램이 종료가 되면서 SocketException 이 발생하고, line 37 에서 예외를 발생시켰습니다. 소스를 보면

line 37 : Client.Receive(getByte,0,getByte.Length,SocketFlags.None);

예상대로 클라이언트쪽을 보고 있던 부분입니다. 여러분들이 C# 을 조금 공부해 보셨다면 예외에 대해 들어보셨을 것이고 이부분에 대해서 쉽게 해결 하실 수 있으리라 생각이 됩니다. 그럼 이러한 갑작스런오류를 예외로 매끄럽게 처리해 보도록 하겠습니다.

서버측 소스를 아래와 같이 수정합니다. 전체 부분을 모두 감싼다고 생각하시면 됩니다.

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

    if(Client.Connected)
    {
        - 중간 생략 -
    }
}
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();
    Client.Close();
}

자~~ 다시 한번 복습하는 의미에서 try ~ catch 구문은 이와같이 예외가 발생하기 쉬운 부분에프로그래머가 예외를 핸들링을 하기 위해 사용됩니다. 위의 소스를 잠깐 살펴보면

2개의 Catch 구문을 볼 수 있습니다. 위에서 아래로 내려오는 catch 일수록 일반적이어야 합니다.반대로 말하면, 예외가 발생하기 쉬운 Exception부터 catch 잡아내줘야 그에대한 확실한 처리나메시지를 유저에게 보여줄 수 있습니다. 일단 저는 예외객체가 가지고 있는 일반적은 오류 메시지를 보여주도록 했습니다. 그리고 finally 부분에서 종료전에 소켓객체를 모두 close 하여 객체의 사용이끝나고 리소스를 반환합니다. GC 에 의해 관리 되겠지요.

자 이제 오류도 처리 깔끔하게 처리 했습니다.그런데 한가지 문제점이 또 남았습니다. 아래 그럼을 보시면..

처음 박스한 부분을 보시면 "가나다라마바" 라고 서버로 전송하고 수신까지 확인하였습니다.그다음 다시 "가나" 라고 전송하면 서버측으로부터 되돌아온 데이터의 가나뒷부분에(줄친부분)쓰레기 값 같은것이 포함되어저 나옵니다. 즉, 이미 전에 발송한 데이터가 byte 배열에 남아있어,먼저 보낸 값보가 짧아지면 겹쳐버려서 저런현상이 나옵니다. 힌트를 드렸으니 스스로 해결가능하신 분들은 한번 도전해 보시기 바랍니다.^^ 5강에서는 하위소켓으로 UDP 통신이 가능하도록 간단하게 작성해본 후 고수준 클래스인 TcpListener 와 TcpClient 등에 대해 알아보겠습니다.

대락 소켓에 대한 완벽하진 않지만, 어느정도 개념을 잡았으리라 생각이 듭니다. 다음 강좌 부터는 이러한 기본을 토대로 되기 때문에 중요한 내용이니 꼭 복습해보시고 코딩을 한번씩 해보시기 바랍니다.

갈길이 상당히 멉니다. 끝까지 강좌 읽어주셔서 감사합니다.


authored by


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

로딩 중입니다...

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