[ 공 부 ]/[ 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 ... )
: 각 응용 프로그램 나름의 프로토콜들이 정의되는 곳.
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에 의해 발행되는 메모.
패킷
: 네트워크를 통해 오가는 데이터 ( 내용물 + 포장지 )