Coding Memo

[C#] TCP 흐름제어 확인해보기 본문

Language/C#

[C#] TCP 흐름제어 확인해보기

minttea25 2023. 7. 17. 16:04

흐름 제어란?

 

발신자가 수신자가 처리할 수 있는 것보다 더 빨리, 많이 데이터를 보낼 경우에 수신자는 일부 데이터(패킷)에 대해 삭제가 되거나 손실이 발생 할 수 있다. 이 문제를 방지하기 위해 TCP 연결에서는 흐름제어를 사용한다.

 

흐름제어(flow-control)는 수신자의 상태를 보고 전송을 제어하여 데이터의 손실이나 정체 없이 효율적이고 안정적으로 통신을 가능하게 한다.

 

예를 들어, 수신자가 100바이트의 버퍼를 가지고 있는데, 발신자가 150바이트의 데이터를 보냈다고 가정하자.

TCP 연결에서 발신자는 수신자가 처리 할 수 있는 데이터의 양 (Sliding Window)을 미리 정한다.

따라서 발신자는 먼저 100바이트를 송신하고, 수신자가 먼저 100바이트를 받고 처리 후, 발신자에게 추가 데이터를 위한 공간이 있다는 것을 알려준다.

그 이후, 발신자는 나머지 50바이트를 송신하고, 마찬가지로 수신자가 50바이트의 데이터를 받는다. (물론 TCP이기 때문에 여기서도 나머지 50바이트도 잘 받았는지 확인한다.)

 

Note: TCP뿐만 아니라 UDP 등의 다른 프로토콜에서도 사용될 수 있다.


흐름제어 확인해보기

 

서버 코어 만들어보던 중에 여러 테스트를 해보다가 우연히(?) 발견한 내용이다.

만약 송신하는 데이터의 크기보다 Receive Buffer 사이즈가 더 작으면 어떻게 될까?

라는 테스트 중에 발견했다.

 

아래 코드는 데이터를 2번에 걸쳐 받는 것을 확인하기 위해 간단하게 동기 코드로 작성한 것이다. 

receive 시에 16바이트만 받도록 하였다. (송신된 데이터는 16바이트를 초과한다.)

 

Server (수신자)

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

namespace Server
{
    class Program
    {
        static void Main(string[] args)
        {
            string host = Dns.GetHostName(); // local host name of my pc
            IPHostEntry ipHost = Dns.GetHostEntry(host);
            IPAddress ipAddr = ipHost.AddressList[0];
            IPEndPoint endPoint = new(address: ipAddr, port: 8888);

            Socket listenSocket = new(
                addressFamily: ipAddr.AddressFamily,
                socketType: SocketType.Stream,
                protocolType: ProtocolType.Tcp);

            try
            {
                listenSocket.Bind(endPoint);
                listenSocket.Listen(backlog: 10);

                Console.WriteLine($"Listening on port: {endPoint.Port}....");

                while (true)
                {
                    Socket clientSocket = listenSocket.Accept(); // blocking method
                    Console.WriteLine($"Client is connected {clientSocket.RemoteEndPoint}");

                    while (true)
                    {
                        // Read received data
                        byte[] data = new byte[16];
                        int bytesRead = clientSocket.Receive(data);
                        string msg = Encoding.UTF8.GetString(data, 0, bytesRead);
                        Console.WriteLine($"Received data(size: {bytesRead}): {msg}");
                    }

                    clientSocket.Close();
                }
            }
            catch (Exception e) { Console.WriteLine(e); }
            finally
            {
                listenSocket.Close();
            }
            
        }
    }
}

 

Client (발신자)

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

namespace Client
{
    class Program
    {
        static void Main(string[] args)
        {
            string host = Dns.GetHostName(); // local host name of my pc
            IPHostEntry ipHost = Dns.GetHostEntry(host);
            IPAddress ipAddr = ipHost.AddressList[0];
            IPEndPoint endPoint = new(address: ipAddr, port: 8888);

            Socket socket = new(
                addressFamily: ipAddr.AddressFamily,
                socketType: SocketType.Stream,
                protocolType: ProtocolType.Tcp);

            try
            {
                socket.Connect(endPoint); // blocking
                Console.WriteLine("Connected to Server.");

                // Send msg to Server
                string msg = @"Hello, it's C# Client!";
                byte[] msgBytes = Encoding.UTF8.GetBytes(msg);
                ReadOnlySpan<byte> span = new(msgBytes);
                socket.Send(span);
                Console.WriteLine($"Sent: {span.Length} bytes");

                while (true) { }

                
            }
            catch (Exception e) { Console.WriteLine(e); }
            finally
            {
                socket.Close();
            }
        }
    }
}

 

 

결과

발신자
수신자

발신자 측에서는 22바이트를 송신했고, 수신자 측에서는 받을 수 있는 버퍼 사이즈를 16으로 제한했었으므로, 처음에는 16바이트를, 다음으로 나머지 6바이트를 받았다.