ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 결국. MemoryStream을 이용해서 BinaryFormatter로 Serialize한 후 Socket으로 전송 Socket으로 받고 Deseralize한 후 사용 하는 방법을 택했습니다. 이렇게 방법을 정하고도 소켓 사용은 거의 처음이다시피하니 또 하루 삽질했습니다.
    .NET/NetWork 2009. 2. 4. 01:32
    반응형

    거의 데브피아에서 찾은 자료를 바탕으로 작성한 코드이지만 새로 시도를 해보려는 분을 위해 중요부분의 소스를 올려봅니다.


    본 강좌란의 DataSet Socket으로 전송하기를 보고 시도해 봤지만 실력이 허접한 관계로 쓰디쓴 실패와 코드써핑 삽질과 Msdn삽질을 해본 결과 헤메기 쉬운 부분을 여러부분 발견... 동병상련인 분을 생각해보니 글을 쓰지 않을 수가 없더군요.. ^^;


    제가 나름대로 좋은 방법이라고 생각된 것을 올려봤습니다. 이유는.


    1. XmlSerializer를 이용한 자료가 있었는데, 이것은 IO를 사용하여 파일을 생성하고 보내는 방식이라서 HDD의 IO를 발생시킵니다. 쉽기는 했지만 웬지 마음에 들지 않더군요 -_-;


    2. 본 강좌란의 DataSet Socket.. 가장 편하고 좋은 방법인것 같은데, 원인을 알 수 없는 이상 동작으로 인해 포기 -_-;


    3. [DllImport("Kernel32.Dll")]로 copyMemory를 이용하는 방법... 좋은 방법인것 같은데... 웬지 불안하고 자료 부족으로 포기 -_-;


    4. .NET Remoting은 보안 문제로 델리게이트를 사용한 이벤트 모델이 작동하지 않았습니다. 외부 채널 컴포넌트를 사용해서 극복할 수는 있었지만 이벤트가 글로벌하게 발생해서 엄청난 네트웍 트래픽을 야기할것 같아서 포기.. -_-; 같은 이유에서 클라이언트로 쓰레드를 돌려서 리모팅 서버의 객체 체크를 해보는 방법도 포기 -_-;


    결국. MemoryStream을 이용해서 BinaryFormatter로 Serialize한 후 Socket으로 전송 Socket으로 받고 Deseralize한 후 사용 하는 방법을 택했습니다. 이렇게 방법을 정하고도 소켓 사용은 거의 처음이다시피하니 또 하루 삽질했습니다.


    아래의 코드는 제가 빠졌던 상황과 비슷한 분에게 도움이 되는 코드일 것이빈다.


    코드의 상황은 이러합니다. 기존 소켓은 1024바이트로 기본적인 메시지를 서버와 클라이언트로 주고 받고 있으며, 클라이언트에서 서버가 알아들을 수 있는 특별한 Byte를 날려주면 서버에서 미리 지정된 해당 작업을 수행합니다.

     따라서 서버에서는 두개의 쓰레드를 돌리고 있습니다. 하나는 클라이언트의 신규 접속을 받아들이는 부분이고(첫번째 쓰레드), 하나는(두번째 쓰레드) 클라이언트가 보내는 데이터를 받아들여서 적절해 처리하는 코드입니다.

     이런 방식을 생각한 이유는 클라이언트가 특정 포트를 열어주면 접속된 클라이언트의 IP주소만 따로 받아서 관리해 주면 쉽지만 요즘 포트 열어주는 클라이언트 어디있나요? 당연 서버에서 접속이 유지되는가 관리를 해야 한다고 생각했기 때문입니다. 더 좋은 방법이 있을 수 있지만 지금 저의 현재 실력으로는 여기가 한계이네요. 더 잘 아시는 분은 조언 부탁드립니다.


    server나 client 소켓 셋팅은 조금만 삽질 해보시면 금방 나오니 생략합니다.


    어쨌든 첫번째 쓰레드(신규 접속추가)는 이러합니다.


    //첫번째 서버 쓰레드


            private TcpListener Listener;

            protected ArrayList ClientList = new ArrayList();

            protected ArrayList socList = new ArrayList();


            private void StartListener()

            {

                while (true)

                {

                    Thread.Sleep(30);

                   

                    Socket soc = Listener.AcceptSocket();


                    if (soc.Connected)

                    {

                        socList.Add(soc);                 //새로운 클라이언트가 접속했을때 클라이언트의 소켓을 ArrayList에 저장합니다.

                        NetworkStream _stream = new NetworkStream(soc,FileAccess.Write);    

                        ClientList.Add(_stream);        //일반적인 메시지를 주고 받을 때는 NetworkStream이 쉬우므로 별도의 ArrayList에 저장합니다.


                        buff = null;

                        _stream = null;

                    }

                }

            }




    두번째 서버 쓰레드 중의 일부입니다. 윗 부분에서 if로 설정되어서 그냥 else if로 사용했습니다.

    이 쓰레드에는 평소에는 간단한 채팅을 구현하고, 클라이언트의 전송 정보에 따라서 클라이언트의 신규접속, 접속해제, 데이터셋 등의 사용자 정의형 요청하면 적절히 처리하도록 구서하였습니다.

    현재 string을 비교하게 해놨는데, 별도의 구조체를 사용하시면 더 좋을 것 같습니다.


    전체 소스는 아니고 중요한 부분만 붙여넣었습니다.



    //두번째 서버 쓰레드


            private void StartRead()

            {

                Encoding uni = Encoding.Unicode;

                byte [] buff = new Byte[1024];

                int nIndex = 0;


                while(true)

                {

                    Thread.Sleep(30);

                    if (ClientList.Count > 0)

                    {

                        while (nIndex < ClientList.Count)

                        {

                            NetworkStream _stream = (NetworkStream)ClientList[nIndex];

                            Socket soc = (Socket)socList[nIndex];

                            if (_stream.DataAvailable)

                            {

                                int nRead = _stream.Read(buff, 0, 1024);

                                if (nRead != 0)

                                {

                                    string strRecieve = uni.GetString(buff, 0, nRead);

                                    if (strRecieve.Length > 5 && strRecieve.Substring(0, 5).Equals("<OFF>"))

                                    {

                                        string strTemp = strRecieve.Substring(5) + "님께서 퇴장하셨습니다.";

                                        this.AddList(strTemp);

                                        this.SendList(strTemp);

                                        socList.Remove(soc);

                                        ClientList.Remove(_stream);

                                    }

                                    else if (strRecieve.Length > 4 && strRecieve.Substring(0, 4) == "<ON>")

                                    {

                                        string strTemp = strRecieve.Substring(4) + "님께서 입장하셨습니다.";

                                        listServer.Items.Insert(0, strTemp);

                                        this.SendList(strTemp);


                                    }

                                    else if (strRecieve.Length == 8 && strRecieve.Substring(0, 8) == "<GIVEDS>")  //이 부분이 데이터셋을 요청할때 처리

                                    {

                                        if(sqlConn.State == System.Data.ConnectionState.Closed)sqlConn.Open();

     

                                        DataSet ds = new DataSet();

                                        adpxxx.Fill(ds);  //데이터 어뎁터를 이용 Fill

                                       

                                        sqlConn.Close();

     

     

                                        MemoryStream ms = new MemoryStream();  //메모리에 Stream함. (파일 생성하지 않고 전송하기 위해)

                                        BinaryFormatter bf = new BinaryFormatter();   //바이너리를 이용할 것이기 때문에 Socket으로 전송해야한다고 생각.

     

                                        bf.Serialize(ms, ds); //Serialize... 이 코드로 ms에 ds가 Serialize됨.

     

                                        ms.Position = 0;       //ms의 위치를 원점으로??? 레코드셋의 MoveFirst로 생각하였음 ^^;

     

                                        //아래 부분이 소켓으로 ms의 내용을 전송하는 부분임.

                                        const int LENGTH = 1024;

                                        const int OFFSET = 0;

                                        byte[] buffer = new byte[LENGTH];

     

                                        while (true)

                                        {

                                            int rcvd = ms.Read(buffer, OFFSET, LENGTH);

     

                                            if (rcvd > 0)

                                            {

                                                soc.Send(buffer,0,rcvd,0);

                                            }

                                            else

                                            {

                                                ms.Close(); //소멸.

                                                bf = null;

     

                                                break;  //혹시 에러가 난다면 이부분이 의심... ^^;

                                            }

                                        }

                                       ds.Dispose(); //목적 달성한 데이터셋을 소멸.



                                    }

                                    else

                                    {

                                        this.AddList(strRecieve); //평소에는 채팅으로 동작

                                        this.SendList(strRecieve);

                                    }

                                }

                            }

                            nIndex ++;

                        }

                    }

                    nIndex = 0;

                }

            }


    .... ㅎㅎ~ 많이 허접합니다. 코드를 여기저기 짜집기 하다보니... 이텔릭 처리한 부분만 보셔도 상관없을듯...

    그런데, 중요한 부분은 클라이어트 에서 받아들이는 부분인 것 같습니다.


    클라이언트 샛팅하는 부분은 다른 예제 코드에서도 쉽게 찾아 보실 수 있을 테니 해당 부분만 올려 보겠습니다.


    //클라이언트 수신 쓰레드


            private void RecieveData()

            {

                byte [] rbuff = new byte[1024];

                Encoding uni = Encoding.Unicode;

                string recvtxt = null;


                while(true)

                {

                    Thread.Sleep(30);

                    if (socSocket != null && socSocket.Connected)

                    {

                       

                        NetworkStream _stream = new NetworkStream(socSocket,FileAccess.Read);

                        if (_stream.DataAvailable && _stream.CanRead)

                        {

                            if(dsbool)  //DataSet을 요청할때 true가 되도록 설정해 놓았습니다.

                            {

                                MemoryStream ms = new MemoryStream(); //파일 이용하기 싫어서 MemoryStream사용

                                BinaryFormatter bf = new BinaryFormatter();


                                const int LENGTH = 1024;

                                const int OFFSET = 0;

                                byte[] buffer = new byte[LENGTH];


                                bool inwh = true;


                                while (inwh)

                                {

                                    int rcvd = socSocket.Receive(buffer, OFFSET, LENGTH,0);


                                    if (rcvd > 0)

                                    {

                                        ms.Write(buffer,0,rcvd);


                                       //이부분이 매우 중요!!! socSocket에서 모두 전송 받고 나서 while를 한번 더 돌까요? 헤헤~ 답은 전혀 아나올씨다~

                                       // 입니다. 즉 <int rcvd = socSocket.Receive(buffer, OFFSET, LENGTH,0);> 부분에서 다음으로 못 넘어가고 쭉~

                                       // 뻗어버리거나 아래의 코드를 실행하지 못합니다. 버그인지도 모르지요. 어쨌든 마지막 받았을 때 LENGTH미만일

                                       //경우 모두 받았음을 의미하므로 while문을 종료시켜줘야합니다. 이것때문에 4시간 삽질 -_-; 앗 그런데, 마지막

                                       // 받았을 때의 byte가 1024라면? !!! 큰일입니다!! 어떻게 하죠?? 좋은 방법 있으시면 알려주세요~ ^^;

                                        if(rcvd < LENGTH)

                                        {


                                            inwh=false;

                                        }

                                    }

                                    else

                                    {

                                        inwh=false;

                                    }

                                }


                                ms.Position =0;  //처음부터 다시 읽기 위해서.


                                ds = (DataSet)bf.Deserialize(ms);  //DataSet형으로 Deserialize해서 ds에 넘겨줌.


                                ms.Close(); //해제

                                bf = null;    //해제

           

                                System.Data.DataView dvtmp = new System.Data.DataView();

                                dvtmp.Table = ds.Tables[0];

                                 int sfasf = dvtmp.Count; //여기에 중지시켜보고 적절한 Count가 나오면 성공!!!

                               

                                //this.dataGrid1.DataSource = dvtmp;  //요거하면 에러나겠죠? ^^; 쓰레드라면요...


                                dsbool = false; //다시 채팅상태로 되돌리기 위해서.

                            }

                            else

                            {

                                int nBuff = _stream.Read(rbuff, 0, 1024);

                                recvtxt = uni.GetString(rbuff, 0, nBuff);

                                lvList.Items.Insert(0, recvtxt);

                            }

                        }

                    }

                }

            }


    결론

     소켓은 쉬운듯 하지만 막상 시도해보면 어려운 것이 많습니다. 개인적은 소견으로는 웹서비스라든지 리모팅 기타 비주얼한 컴포넌트에 너무 익숙해져서인지? 하는 생각을 해봅니다.

     하지만 어떤 분이 말씀하신 것처럼 조금만 깊이 들어가보면 소켓을 사용하는 때가 오는 군요. .Net리모팅을 대안으로 생각해 봤지만, 서버에서 클라이언트에게 메시지를 전송하는 것에는 어려움이 많더군요.(보안 문제로 쉽게 델리게이트를 사용한 이벤트 모델을 사용할 수가 없었습니다.)

     하하.. 써놓고 보니 지난 일주일간의 삽질은 서버에서 능동적으로 클라이언트에게 메시지나 자료를 전송해주기 위한 방법을 찾고 있었던 것 같습니다. 클라이언트에서 서버에게 요청하고 서버가 보내주는 것은 쉽지만 if(방화벽 떡칠 && (유저 컴맹 or 공유기 접속환경))에서는 서버가 능동적으로 클라이언트를 다루는 것이 결코 쉽지 않았습니다. 여기서는 간단히 DataSet을 전송해 보았지만 다른 구조체나 클래스도 시리얼라이즈를 사용한다면 쉽게 전송될거 같습니다.


     부디 저와 같은 삽질을 하실 분들에게 도움이 되기를 바라며, 고수님들이 이 허접한 글을 보셨다면 클라이언트측 소스를 좀 더 보완 해주시면 감사하겠습니다.


    < 출처 : 데브피아

    반응형

    댓글

Designed by Tistory.