[네트워크/C#] .NET TCP/IP 네트워크 기초
# IPAdress 클래스
네트워크에 기본적으로 사용되는 식별자로
- IP 주소 - IPAddress 클래스로 관리합니다.
- 도메인 - Dns 클래스로 관리합니다.
- 포트번호 - IPEndPoint 클래스로 관리합니다.
가 있습니다.
그 중 IPAddress 클래스의 간단한 기능들입니다.
using System.Net;
// 문자열 ip주소를 저장합니다.
IPAddress ip = IPAddress.Parse("192.168.0.1");
Console.WriteLine(ip.ToString());
// ip주소를 byte배열로 변환합니다.
byte[] buff = ip.GetAddressBytes();
Console.WriteLine(buff.Length);
// ipv4 주소를 ipv6로 변환합니다.
IPAddress ipv6 = ip.MapToIPv6();
Console.WriteLine(ipv6.ToString());
# Dns 클래스
ip주소를 기억하기 어렵기 때문에 호스트명을 대신 사용합니다.
로컬 네트워크에서는 컴퓨터 이름으로,
인터넷에서는 호스트 이름, 도메인 이름으로 사용합니다.
- IPHostEntry 클래스
호스트명과 ip주소 등 호스트 정보를 저장합니다.
using System.Net;
// 호스트정보를 저장하는 클래스입니다.
IPHostEntry hostEntry = Dns.GetHostEntry("www.naver.com");
Console.WriteLine(hostEntry.HostName);
//IPHostEntry 안에 ip주소 리스트가 있습니다.
foreach (IPAddress ip in hostEntry.AddressList)
{
Console.WriteLine(ip);
}
// 로컬 호스트명을 확인합니다.
string hostname = Dns.GetHostName();
IPHostEntry localhost = Dns.GetHostEntry(hostname);
Console.WriteLine(localhost.HostName);
// ip주소로 호스트명을 확인합니다.
IPAddress ipAdd = IPAddress.Parse("172.30.1.10");
IPHostEntry he = Dns.GetHostEntry(ipAdd);
Console.WriteLine(he.HostName);
출력 결과
www.naver.com.nheos.com
223.130.200.107
223.130.200.104
내 컴퓨터 이름
내 컴퓨터 이름
# IPEndPoint 클래스
IP주소로 컴퓨터를 식별하고 포트번호로 응용프로그램을 식별합니다.
IP 주소의 종단점이라해서 IPEndPoint 입니다.
IPEndPoint 클래스는 ip주소와 함께 0 ~ 65535 사이의 포트번호를 가질 수 있습니다.
using System.Net;
IPAddress ip = IPAddress.Parse("172.217.161.228");
IPEndPoint ep = new(ip, 80);
Console.WriteLine(ep.ToString());
출력결과
172.217.161.228:80
# TCP 클라이언트
TcpClient 클래스와 NetworkStream 클래스를 사용하기 위해서
System.Net.Sockets 가 필요합니다.
TCP 클라이언트 데이터 송신의 순서는
- 클라이언트 생성
- 클라이언트의 stream 생성
- 데이터를 stream에 담을 수 있도록 인코딩
- stream에 데이터 입력
- 송수신
- stream 닫기
- 클라이언트 닫기
의 순서로 이루어집니다.
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace TcpCli
{
class Program
{
static void Main(string[] args)
{
TcpClient tc = new TcpClient("localhost", 7000); // 1. 클라이언트 생성
string msg = "Hello arok";
byte[] buff = Encoding.UTF8.GetBytes(msg); // 2. 보낼 데이터를 바이트로 인코딩
NetworkStream stream = tc.GetStream(); // 3. 클라이언트의 스트림 생성
stream.Write(buff, 0, buff.Length); // 4. 스트림에 담기
// 여기서부터는 데이터 수신 관련입니다.
byte[] outbuf = new byte[1024];
int nbytes;
MemoryStream mem = new MemoryStream();
// 데이터가 끝날때까지 모두 수신합니다.
while ((nbytes = stream.Read(outbuf, 0, outbuf.Length)) > 0)
{
mem.Write(outbuf, 0, nbytes);
}
byte[] outbytes = mem.ToArray();
mem.Close();
// 송수신 후 반드시 stream을 닫은 후 client까지 닫아야합니다.
stream.Close();
tc.Close();
}
}
}
이 예제코드는 제대로 동작하는 코드가 아닙니다.
간단하게 클라이언트의 동작 흐름을 파악하기 위한 코드입니다.
# TCP 서버
TCP 서버의 구동 순서는
- 서버 생성
- 연결 대기
- 클라이언트 연결 요청 수신 및 새로운 클라이언트와 매핑
- 클라이언트 닫기
- 서버 닫기
입니다.
TCP 서버는 클라이언트의 연결 요청을 받아 연결시켜주고, 닫는 역할만 합니다.
데이터 송수신은 새로운 TCP 클라이언트를 생성하여 수행하게 합니다.
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace TcpServer
{
class Program
{
static void Main(string[] args)
{
TcpListener listener = new TcpListener(IPAddress.Any, 7000);
listener.Start();
byte[] buff = new byte[1024];
while (true)
{
// 리스너가 연결 승인 후 새롭게 생성된 클라이언트가
// 스트림을 생성하여 송수신 활동을 합니다.
TcpClient client = listener.AcceptTcpClient();
NetworkStream stream = client.GetStream();
// ... 데이터 송수신 부분 ...
stream.Close();
client.Close();
}
listener.Stop();
}
}
}
이 예제코드는 제대로 동작하는 코드가 아닙니다.
간단하게 서버의 동작 흐름을 파악하기 위한 코드입니다.
# 비동기 TCP 서버
위 TCP 서버는 동기적 TCP 서버입니다.
동시에 여러 클라이언트들이 접속하면 순서대로 처리해야하는 단점이 있습니다.
다중 클라이언트 접속을 처리하기 위해서는 TCP서버를 비동기적으로 만들어야 합니다.
TCP 서버를 비동기로 만드는 방법
- 고전적인 APM ( Asynchronous Programming Model ) 방식
` BeginAcceptTcpClient(), EndAcceptTcpClient() 와 같이
Begin과 End 2개의 메서드쌍으로 사용하는 방식
- 요즘 사용하는 TAP ( Task-based Asynchronous Pattern ) 방식이 있습니다.
` AcceptTcpClientAsync()와 같이 끝에 async가 붙는 메서드를 C# await와 함께 사용합니다.
using System.Net.Sockets;
using System.Net;
using System;
using System.Threading.Tasks;
using System.Text;
namespace TcpSrvAsync
{
class Program
{
static void Main(string[] args)
{
AysncServer().Wait();
}
async static Task AysncServer()
{
TcpListener listener = new TcpListener(IPAddress.Any, 7000);
listener.Start();
while (true)
{
// 비동기 Accept
TcpClient tc = await listener.AcceptTcpClientAsync().ConfigureAwait(false);
// 새로운 쓰레드에서 처리합니다.
Task.Factory.StartNew(AsyncTcpProcess, tc);
}
}
async static void AsyncTcpProcess(object o)
{
TcpClient tc = (TcpClient)o;
int MAX_SIZE = 1024;
NetworkStream stream = tc.GetStream();
// 비동기 수신
var buff = new byte[MAX_SIZE];
var nbytes = await stream.ReadAsync(buff, 0, buff.Length).ConfigureAwait(false);
if (nbytes > 0)
{
string msg = Encoding.ASCII.GetString(buff, 0, nbytes);
Console.WriteLine($"{msg} at {DateTime.Now}");
// 비동기 송신
await stream.WriteAsync(buff, 0, nbytes).ConfigureAwait(false);
}
stream.Close();
tc.Close();
}
}
}
이 예제코드는 제대로 동작하는 코드가 아닙니다.
간단하게 비동기 tcp 서버의 동작 흐름을 파악하기 위한 코드입니다.