[ 공 부 ]/[ C# ]

21장. 네트워크 프로그래밍

HiStar__ 2025. 1. 31. 17:34

[2025-01-31] - 게시글 최초 작성


1. 네트워크 프로그래밍

** TCP/IP 스택
[ 1층 ] 링크 계층 ( 물리 계층, 네트워크 접속 계층, 미디어 접근 계층 등으로도 불림 )
: 네트워크의 물리적인 구성으로부터 독립적인 프로토콜 
 ( 컴퓨터가 네트워크에 전화선의 모뎀으로 연결되어 있던, LAN에 이더넷 케이블로 연결되어 있던,
   WiFi에 연결되어 있던 간에 신경 쓰지 않음 )

[ 2층 ] 인터넷 계층 ( IP : 인터넷 프로토콜 )
: 패킷을 수신해야 할 상대의 주소를 저장하고, 나가는 패킷에 대해서는 적절한 크기로 분할하며,
  들어오는 패킷에 대해서는 재조립을 수행
  ( 내보낸 패킷을 상대방이 잘 수령했는지에 대해서 전혀 보장하지 않는다 )

[ 3층 ] 전송 계층 ( TCP / UDP )
: 패킷의 운송을 담당하는 프로토콜들이 정의.

[ 4층 ] 어플리케이션 계층 ( HTTP / FTP / SNMP ... )
: 각 응용 프로그램 나름의 프로토콜들이 정의되는 곳.

 

 [msdn] Sytem.Net.Sockets

 

Socket 클래스 (System.Net.Sockets)

Berkeley 소켓 인터페이스를 구현합니다.

learn.microsoft.com


2. 네트 워크프로그래밍

2.1 IP 주소

 IPv4의 IP 고갈로 인하여 IPv6 체계로 전환될 것 같다.


2.2 포트

 부호가 없는 16비트 정수 ( 0 ~ 65535 )

잘 알려진 포트 번호 ( 1 ~ 1023 )
HTTP : 80
HTTPS : 443
FTP : 21
Telnet : 23
SMTP : 25
IRC : 194
IIOP : 535


2.3 TcpListener 와 TcpClient

 System.Net.Sockets 네임스페이스에 다양한 옵션과 메소드를 제공하는 Socket 클래스가 존재.

 TcpListener 클래스 ( 서버 )
: 클라이언트의 연결 요청을 기다리는 역할.
  클라이언트의 요청을 수락하면 클라이언트와의 통신에 사용할 수 있는 TcpClient 인스턴스 반환
  Start() : 연결 요청 수신 대기를 시작
  AcceptTcpClient() : 클라이언트의 연결 요청을 수락
  Stop() : 연결 요청 수신 대기를 종료
TcpClient 클래스 ( 서버, 클라이언트 )
: GetStream() 메소드를 통해서, NetworkStream 객체를 통해 데이터를 주고 받는다.
 Connect() : 서버에 연결 요청
 GetStream()
: 데이터를 주고받는 데 사용하는 매개체인 NetworkStream
 Close()
: 연결을 종료
// Client
using System;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace Client
{
    class MainApp
    {
        static void Main(string[] args)
        {
            Console.WriteLine($"{Process.GetCurrentProcess().ProcessName}");

            string bindIP = "127.0.0.1";

            string serverIP = "127.0.0.1";
            const int serverPort = 5425;
            
            try
            {
                // 마지막 값을 0으로 지정하면 OS에서 임의의 번호로 포트 할당.
                IPEndPoint clientAddress = new IPEndPoint(IPAddress.Parse(bindIP), 0);
                IPEndPoint serverAddress = new IPEndPoint(IPAddress.Parse(serverIP), serverPort);

                Console.WriteLine($"클라이언트 : {clientAddress.ToString()}, 서버 : {serverAddress.ToString()}");

                // 클라이언트도 Ip랑 포트를 가지고 있어야 한다.
                TcpClient client = new TcpClient(clientAddress);

                // 수신 대기하고 있는 서버에 연결 요청을 전송.
                client.Connect(serverAddress);
                NetworkStream stream = client.GetStream();

                while (true)
                {
                    Console.Write("Message : ");
                    string message = Console.ReadLine();

                    if (message?.Length == 0)
                    {
                        client.Close();
                        stream.Close();
                        break;
                    }

                    byte[] data = System.Text.Encoding.Default.GetBytes(message??"");

                    
                    stream.Write(data, 0, data.Length);

                    Console.WriteLine(String.Format($"송신 : {message}"));

                    data = new byte[256];

                    string responseData = "";

                    int bytes = stream.Read(data, 0, data.Length);
                    responseData = Encoding.Default.GetString(data, 0, bytes);
                    Console.WriteLine($"수신 : {responseData}");
                    
                }

                stream.Close();
                client.Close();

            }
            catch (SocketException e)
            {
                Console.WriteLine(e);
            }
            finally
            {
                
            }

            Console.WriteLine("클라이언트 종료");
        }

    }

}

// Server
using System;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace Server
{
    class MainApp
    {
        static void Main(string[] args)
        {
            Console.WriteLine($"{Process.GetCurrentProcess().ProcessName}");

            string bindIP = "127.0.0.1";
            const int bindPort = 5425;

            TcpListener server = null;
            try
            {
                // 통신에 필요한 IP 주소와 포트를 가지고 있는 객체
                IPEndPoint localAddress = new IPEndPoint(IPAddress.Parse(bindIP), bindPort);

                server = new TcpListener(localAddress);

                // TcpClient.Connect()의 요청을 받을 동안 연결 대기
                server.Start();
                Console.WriteLine(" Server Start ...");

                while (true)
                {
                    // 연결 완료시 TcpClient 객체를 반환
                    TcpClient client = server.AcceptTcpClient();
                    Console.WriteLine($"클라이언트 접속", ((IPEndPoint)client.Client.RemoteEndPoint).ToString());

                    NetworkStream stream = client.GetStream();

                    int length;
                    string data = null;
                    byte[] bytes = new byte[256];

                    while ((length = stream.Read(bytes, 0, bytes.Length)) != 0)
                    {
                        data = Encoding.Default.GetString(bytes, 0, length);
                        Console.WriteLine(String.Format($"수신 : {data}"));

                        byte[] msg = Encoding.Default.GetBytes(data);

                        stream.Write(msg, 0, msg.Length);
                        Console.WriteLine(String.Format($"송신 : {data}"));
                    }

                    stream.Close();
                    client.Close();
                }

            }
            catch (SocketException e)
            {
                Console.WriteLine(e);
            }
            finally
            {
                server.Stop();
            }

            Console.WriteLine("서버 종료");
        }

    }
    
}

2.4 흐르는 패킷

 버퍼
: TCP 통신 애플리케이션에 댐과 같은 역할.
 네트워크를 향해 내보내는 데이터, 들어오는 데이터도 해당 '버퍼'를 거친다.

 

 패킷의 통신의 경우, 내가 데이터를 1씩 여러번을 보낸다고 했을 때, 
 데이터가 실제로 한 개씩 보낸 횟수만큼 전송되는 것이 아니다!
 버퍼에 데이터를 쌓아 놓고 OS에서 일정 양 혹은 일정 시간이 다 되었을때 뭉탱이로 데이터를 보낼 수 있다.


2.5 예제 구현

예제 구현 내용
 1. 파일 업로드 프로토콜 ( 헤더 | 바디 )

 

고정 길이 
: 모든 메세지가 같은 길이를 갖는다.

가변 길이
: 1. 메세지를 두 부분으로 나눠서 길이가 고정된 앞부분에 뒷부분의 길이를 기입하는 방식 ( 바이너리 통신 )
  2. 메세지를 구분하는 특정 값을 이용하는 방식. ( 텍스트 방식의 통신 )

 

클래스 라이브러리
서버, 클라이언트 동시에 사용할 것들은 클래스 라이브러리로 생성.

3. 추가 자료

 RFC
: Request For Comment의 약자로 IETF에 의해 발행되는 메모.

 

패킷
: 네트워크를 통해 오가는 데이터 ( 내용물 + 포장지 )

4. 참고 자료