[C# 강좌 30강] 소켓 통신 - 4

공부/C#  2012.10.09 10:49



안녕하세요 개발자 명월입니다.

이번 포스팅에서는 소켓통신을 하다보면 메시지 문제 해결방법에 대한 포스팅입니다.

TCP 전송을 하다 보면 메시지가 밀리는 현상이 발생합니다.

 

예제로 살펴 보도록 하겠습니다.

 

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Text;
  4 using System.Net;
  5 using System.Net.Sockets;
  6 using System.Threading;
  7
  8 namespace Blog20121009
  9 {
10     class Program
11     {
12         static void Main(string[] args)
13         {
14             new Program();
15         }
16         public Program() {
17             new Thread(new ThreadStart(Server)).Start();
18             new Thread(new ThreadStart(Client)).Start();
19
20             Console.ReadLine();
21         }
22         public void Server() {
23             IPEndPoint ipep = new IPEndPoint(IPAddress.Any,9999);
24             Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
25             server.Bind(ipep);
26             server.Listen(20);
27             Socket client = server.Accept();
28             Console.WriteLine("Server Message : 접속되었습니다.");
29             byte[] _data = new byte[1024];
30             client.Receive(_data);
31             Console.WriteLine("Server Message : {0}",Encoding.Default.GetString(_data).Trim('\0'));
32             for (int i = 0; i < 10; i++) {
33                 _data = Encoding.Default.GetBytes(i.ToString()+" 번째 데이터 전송");
34                 client.Send(_data);
35                 Thread.Sleep(5);
36             }
37             client.Close();
38             server.Close();
39         }
40         public void Client() {
41             IPEndPoint ipep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9999);
42             Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
43             client.Connect(ipep);
44             byte[] _data = Encoding.Default.GetBytes("Connection");
45             client.Send(_data);
46             for (int i = 0; i < 10; i++) {
47                 _data = new byte[1024];
48                 client.Receive(_data);
49                 Console.WriteLine("Client Message : {0}", Encoding.Default.GetString(_data).Trim('\0'));
50                 Thread.Sleep(10);
51             }
52             client.Close();
53         }
54     }
55 }
56

 

 

위 소스는 서버에서 클라이언트로 0.005초 단위로 패킷을 전송하고 클라이언트에서는 0.01초 단위로 메시지를 받습니다.

 

 

결과는 역시나 패킷이 밀리거나 합쳐져 버렸습니다. 지금은 한 프로세스 안에 두개의 스레드로 하여 스레드 타임의 차이를 준것 이지만 실제로 서로 다른 프로그램이라고 하면 또 다른 시스템간의 통신이라고 한다면 분명이 처리 속도 차이가 발생 합니다. 그때마다 이렇게 패킷이 밀리거나 합쳐져 버리면 문제가 발생하겠습니다.

 

보통 전문 송수신을 할때는 자리수를 정해 놓거나 마지막에 \r\n을 주어서 구분하는 방법도 있기는 합니다.

그러나 우리는 그런 방법이 아닌 조금 더 정확한 방법을 알아보도록 하겠습니다.

 

메시지를 보내기 전에 해더에 우리는 어느정도의 크기를 전송할 것이다 라는 것을 명시하면 이런 문제가 말끔히 해결 됩니다. 이런 문자 메시지는 큰 영향이 없으나 파일 다운, 업로드 할때는 이 차이가 커지니 꼭 알아두시길 바랍니다.

 

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Text;
  4 using System.Net;
  5 using System.Net.Sockets;
  6 using System.Threading;
  7
  8 namespace Blog20121009
  9 {
10     class Program
11     {
12         static void Main(string[] args)
13         {
14             new Program();
15         }
16         public Program() {
17             new Thread(new ThreadStart(Server)).Start();
18             new Thread(new ThreadStart(Client)).Start();
19
20             Console.ReadLine();
21         }
22         public void Server() {
23             IPEndPoint ipep = new IPEndPoint(IPAddress.Any,9999);
24             Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
25             server.Bind(ipep);
26             server.Listen(20);
27             Socket client = server.Accept();
28             Console.WriteLine("Server Message : 접속되었습니다.");
29             /*byte[] _data = new byte[1024];
30             client.Receive(_data);
31             Console.WriteLine("Server Message : {0}",Encoding.Default.GetString(_data).Trim('\0'));
32             for (int i = 0; i < 10; i++) {
33                 _data = Encoding.Default.GetBytes(i.ToString()+" 번째 데이터 전송");
34                 client.Send(_data);
35                 Thread.Sleep(5);
36             }*/

37             String _data = "";
38             Recieve(client, ref _data);
39             Console.WriteLine("Server Message : {0}",_data);
40             for (int i = 0; i < 10; i++) {
41                 Send(client, i.ToString() + " 번째 데이터 전송");
42                 Thread.Sleep(5);
43             }
44             client.Close();
45             server.Close();
46         }
47         public void Client() {
48             IPEndPoint ipep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9999);
49             Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
50             client.Connect(ipep);
51             /*byte[] _data = Encoding.Default.GetBytes("Connection");
52             client.Send(_data);
53             for (int i = 0; i < 10; i++) {
54                 _data = new byte[1024];
55                 client.Receive(_data);
56                 Console.WriteLine("Client Message : {0}", Encoding.Default.GetString(_data).Trim('\0'));
57                 Thread.Sleep(10);
58             }*/

59             Send(client, "Connection");
60             for (int i = 0; i < 10; i++)
61             {
62                 String _data = "";
63                 Recieve(client, ref _data);
64                 Console.WriteLine("Client Message : {0}",_data);
65             }
66             client.Close();
67         }
68         public bool Send(Socket sock, String msg) {
69             byte[] data = Encoding.Default.GetBytes(msg);
70             int size = data.Length;
71
72             byte[] data_size = new byte[4];
73             data_size = BitConverter.GetBytes(size);
74             sock.Send(data_size);
75
76             sock.Send(data, 0, size, SocketFlags.None);
77             return true;
78         }
79         public bool Recieve(Socket sock,ref String msg)
80         {
81             byte[] data_size = new byte[4];
82             sock.Receive(data_size, 0, 4, SocketFlags.None);
83             int size = BitConverter.ToInt32(data_size,0);
84             byte[] data = new byte[size];
85             sock.Receive(data, 0, size, SocketFlags.None);
86             msg = Encoding.Default.GetString(data);
87             return true;
88         }
89     }
90 }
91

 

 

 

데이터의 밀림이 없습니다.

이런 데이터 통신은 보통 채팅이나 간단한 문자열을 통한 통신보다는 데이터통신 즉, 파일을 전송할 때 유용하게 쓰입니다. 파일을 전송함에 있어서 한번이라도 데이터가 밀리거나 제대로 입력이 되지 않으면 그 파일은 에러가 나기 때문입니다. 관련 소스 첨부하겠습니다.

 

첨부 소스 : Blog20121009.zip

 


댓글 6개가 달렸습니다.
댓글쓰기
  1. 노승곤
    2013.06.21 02:43 신고 |  수정/삭제  댓글쓰기

    궁금한게 있습니다.
    한게임 테트리스같은 1:1이 아닌 다중 접속에 관한건데요.
    질문은 두가지 입니다.

    첫번째는 말그대로 하나의 서버에 여러명이 접속했을때 처리방식이 궁금하네요.
    두번째는 로그인했을때 부터 로그아웃할때까지 계속 접속상태를 유지하는건지.
    아니면 어떤 데이터를 보낼때만 연결을 하게되는지가 궁금하네요.

    • 明月 v명월v
      2013.07.06 01:04 신고 |  수정/삭제

      안녕하세요 방문감사합니다. 답변이 늦었네요..
      먼저 Listen 으로 대기 탄다음 다중 스레드(?)로 접속을 관리 하면 되겠습니다.
      게임은 접속상태를 유지하면서 처리를 하는 방식이 많습니다.( 안그런 형식도 있어요..) 왜냐하면 그 편이 개발하기 좀더 수월하기 때문입니다. IIS 처럼 접속 유지를 하지 않고 동기화를 시키려면 세션 유지라던가 여러가지 생각해야 될께 많아져요..

  2. 강현수
    2013.12.13 02:37 신고 |  수정/삭제  댓글쓰기

    질문이 있습니다.

    소켓프로그래밍이 거의 처음인 초보인데

    클라이언트의 폼이 2개인데 하나는 로그인 폼, 하나는 로그인 후

    사용하는 프로그램 폼입니다.

    로그인폼에서, 서버에 접속 후 id와 passwd를 확인하여 로그인을 하면,

    ex) new 새로운폼으로 생성을하게 되는데, 로그인이 된 소켓 등 현 로그인 상태를 어떻게

    다음 폼으로 넘겨주어야 하나요? ㅠㅠ

    • 明月 v명월v
      2013.12.16 17:12 신고 |  수정/삭제

      안녕하세요.. 먼저 블로그 방문 감사합니다....
      질문하신 로그인 상태를 넘겨줘야 한다는게 무슨 의미인지??
      모르겠네요.. 좀더 자세히 알려주세요..^^

  3. chobo
    2015.02.09 18:21 신고 |  수정/삭제  댓글쓰기

    좋은 강의 감사합니다.
    덕분에 개념이 좀 잡히는 듯 합니다.

  4. 히포
    2017.07.11 16:34 신고 |  수정/삭제  댓글쓰기

    궁금한 부분이 있습니다.
    소스에서 스레드를 사용하긴하지만
    send부분에서는 send대로 길이와 데이터를 넘겨주고
    receive부분에서는 receive대로 길이와 데이터를 받게 되는데
    두 함수가 동작 순서가 있는 것이 아니기때문에
    길이를 보내고 길이를 받고
    데이터를 보내고 데이터를 받고 이렇게 정확히 맞아 떨어지는 이유가 궁금합니다
    약간 받는 과정에서 버퍼같은 느낌이, 받는 데이터를 쌓아서 보관하는 듯한 느낌으로 이해하면 될까요??
    그러면 한번에 다 보내고 한번에 다 받아도 될지 궁금합니다.