System.Net.IPEndpoint
접점, 종점, 종단점. TCP/IP에서 접점이란 IP조소 + 포트를 일컬으며 이 정보를 IPEndPoint 타입을 제공한다.
예를 들어 주소가 192.168.1.10이 할당되어 있고 9000번 포트를 지정한 IPEndPoint는 다음과 같이 만든다.
IPAddress ipAddr = IPAddress.Parse("192.168.1.10");
IPEndPoint endPoint = new IPEndPoint(ipAddr, 9000);
System.Net.Dns
웹 브라우저를 만든다고 가정해 보면, 사용자로부터 "http://www.microsoft.com"라는
문자열을 입력받으면 TCP/IP 통신을 위해 대응되는 IP 주소로 바꿔야 한다.
이때 사용할 수 있는 방법이 Dns 타입이다.
using System;
using System.Net;
namespace Week2_5
{
class Program
{
static void Main(string[] args)
{
IPHostEntry entry = Dns.GetHostEntry("www.microsoft.com");
foreach(IPAddress iPAddress in entry.AddressList)
{
Console.WriteLine(iPAddress);
}
}
}
}
----------------출력 결과-------------------
23.210.37.58
하나의 도메인 이름에는 여러 개의 IP 주소가 있을 수 있다. 이는 일종의 부하 분산 역할을 하기도 한다.
System.Net.Sockets.Socket
이제 다른 컴퓨터와 통신하는 단계이다. 운영체제는 TCP/IP 통신을 위해 소켓이라는 기능을 만들어 두고 있으며,
BCL에서도 Socket 타입을 제공한다.
Socket 생성자는 3개의 인자를 받는다.
public Socket
{
AddressFamily addressFamily,
SocketType socketType,
ProtocolType protocolType
}
모든 인자가 enum형식인데, 각 정의에 따르면 ADdressFamily는 31개 SocketType은 6개 ProtocolType은 25개로 조합 수는 4650가지인데 현실적으로 사용하는 방법은 딱 두 가지 조합이다.
Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Socket (AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
* IPv6용 소켓을 생성하려면 첫 번째 인자에 AddressFamily.InterNetworkV6 값을 주면 된다.
첫 번째 조합은 스트림 소켓 또는 TCP소켓이라고 한다.
두 번째 조합은 데이터그램 소켓 또는 UDP 소켓이라 한다.
모두 IP 프로토콜을 기반으로 동작한다. 어떤 유형의 소켓을 생성했느냐에 따라 이후의 프로그래밍 방식도 달라진다.
따라서 만들려는 프로그램에 적합한 방식의 소켓을 미리 정해놓고 사용해야 한다.
| 기준 | 소켓 | |
| TCP | UDP | |
| 연결성 | 통신 전에 반드시 서버로 연결 (연결 지향성: connection-oriented) |
연결되지 않고 동작 가능 (비연결 지향성: connectionless) |
| 신뢰성 | 데이터를 보냈을 때 반드시 상대방은 받았다는 신호를 보내줌 (신뢰성 보장) |
데이터를 보낸 측은 상대방이 정상적으로 데이터를 받았는지 알 수 없음 |
| 순서 | 데이터를 보낸 순서대로 받게 됨 | 데이터를 보낸 순서와 상관없이 먼저 도착한 데이터를 받을 수 있음 |
| 속도 | 신뢰성 및 순서를 확보하기 위한 부가적인 통신이 필요하므로 UDP에 비해 다소 느림 |
부가적인 작업을 하지 않으므로 TCP보다 빠름 |
Socket 타입은 IDisposable을 상속받았다. 따라서 소켓을 생성한 후 필요가 없어지면 반드시 자원을 해제해야 한다.
// 소켓 자원 생성
Socket socket =
new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
//............[소켓을 사용해 통신]...........
// 반드시 소켓 자원 해제
socket.Close();
클라이언트 소켓은 데이터가 전송돼야 할 대상을 지정하기 위해 접점 정보가 필요하다. 따라서 서버 소켓은 컴퓨터에 할당된 IP주소 중에서 어떤 것과 연결될지 결정해야 하고 이 과정을 바인딩이라 한다. 간단히 말해서 소켓이 특정 IP와 포트에 묶이면 바인딩이 성공했다고 볼 수 있다. 일단 이렇게 바인딩되고 나면 다른 소켓에서는 절대로 동일한 접점 정보로
바인딩할 수 없다.
소켓은 모든 IP에 대해 바인딩할 수 있는 방법도 제공한다. "0.0.0.0" 주소를 사용하면 된다.
0.0.0.0은 흔히 사용되기 때문에 IPAddress타입은 정적 공용 속성으로 Any 값을 가지고 있다.
TCP 소켓
우선, 서버 소켓의 경우 다음과 같은 생명 주기를 갖는다.

Bind 단계에서는 Stream 유형의 소켓을 생성한 후 TCP 통신을 위한 접점으로 Bind메서드를 호출하면 된다.
Socket srvSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, 11200);
srvSocket.Bind(endPoint);
바인딩이 완료된 TCP 서버 소켓은 Listen 메서드를 호출하면서 클라이언트로부터의 접속을 허용한다.
srvSocket.Listen(10);
이때 Listen메서드에 전달된 숫자는 클라이언트의 접속을 보관할 수 있는 큐의 길이이다.
위에서는 10으로 지정했으므로 최대 10개의 클라이언트 접속을 큐에 보관할 수 있다.
보관된 클라이언트 연결을 꺼내는 것은 Accept 메서드를 호출하면 된다.
Socket clntSocket = srvSocket.Accept();
이 부분이 UDP와 TCP의 가장 큰 차이점이다. TCP 서버용 소켓 인스턴스는 클라이언트와 직접 통신할 수 없고 오직
새로운 연결을 맺는 역할만 한다. 클라이언트와의 직접적인 통신은 서버 소켓의 Accept에서 반환된 소켓 인스턴스로만
할 수 있다.
TCP 통신에서는 반드시 Send/Receive 메서드를 사용해야 한다. 따라서 전체적인 서버 측의 소켓 사용은 다음과 같이
마무리된다.
byte[] recvBytes = new byte[1024];
int nRecv = clntSocket.Receive(recvBytes);
string txt = Encoding.UTF8.GetString(recvBytes, 0, nRecv);
byte[] sendBytes = Encoding.UTF8.GetBytes("Hello: " + txt);
clntSocket.Send(sendBytes);
clntSocket.Close(); // 클라이언트 대응 소켓을 종료하고,
srvSocket.Close(); // 서버용 소켓을 종료한다.
Accept로 반환된 소켓은 클라이언트 측 TCP 소켓과 일대일 대응하므로 Send/Receive 메서드를 호출할 때 굳이 접점
정볼르 알아내기 위한 IPEndPoint 인자를 전달할 필요가 없다.
단순히 Send는 TCP연결을 맺은 상대측에 데이터를 전송하고, Receive는 상대편으로부터 전송된 데이터를 수신하는
역할만 한다.
TCP 서버 측 소켓 구현
private static void serverFunc(object obj)
{
using (Socket srvSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, 11200);
srvSocket.Bind(endPoint);
srvSocket.Listen(10);
while (true)
{
Socket clntSocket = srvSocket.Accept();
byte[] recvBytes = new byte[1024];
int nRecv = clntSocket.Receive(recvBytes);
string txt = Encoding.UTF8.GetString(recvBytes, 0, nRecv);
byte[] sendBytes = Encoding.UTF8.GetBytes("Hello: " + txt);
clntSocket.Send(sendBytes);
clntSocket.Close();
}
}
}
TCP 클라이언트 측 소켓 구현
private static void clientFunc(object obj)
{
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
EndPoint serverEP = new IPEndPoint(IPAddress.Loopback, 11200);
socket.Connect(serverEP);
byte[] buf = Encoding.UTF8.GetBytes(DateTime.Now.ToString());
socket.Send(buf);
byte[] recvBytes = new byte[1024];
int nRecv = socket.Receive(recvBytes);
string txt = Encoding.UTF8.GetString(recvBytes, 0, nRecv);
Console.WriteLine(txt);
socket.Close();
Console.WriteLine("TCP Client socket: Closed");
}
TCP 클라이언트 측에서 Connect를 호출하는 시점에 TCP 서버는 반드시 Listen을 호출한 상태여야 한다. 그렇지 않으면
TCP 클라이언트의 Connect 호출은 예외를 일으키게 된다.
연결이 정상적으로 이뤄지면 현재 시간 데이터를 UTF-8로 인코딩해 서버로 전송하고 결과를 받기 위해 Reciever를
호출한다. UDP 서버와 마찬가지로 TCP 서버도 다중 클라이언트 연결을 허용하므로
clientFunc을 수행하는 스레드를 다음과 같이 작성하면 된다
// 클라이언트 소켓이 동작하는 스레드
for (int i = 0; i < 3; i++)
{
Thread clientThread = new Thread(clientFunc);
clientThread.IsBackground = true;
clientThread.Start();
}
- 연결성 (connection-oriented)
TCP 통신은 서버 측의 Listen/Accept와 클라이언트 측의 Connect를 이용해 반드시 연결이 이뤄진 다음
통신할 수 있다. - 신뢰성
Send 메서드를 통해수신 측에 데이터가 전달되면 수신 측은 내부적으로 그에 대한 확인 (ACK) 신호를 송신 측에
전달. 따라서 TCP통신은 수신 측에 정상적으로 전달됐는지 알 수 있고, ACK 신호가 오지 않으면 자동적으로
데이터를 재전송함으로 써 신뢰성을 확보한다. - 스트림 중심(stream-oriented)
메시지 간의 경계가 없다. 1번 메서드가 실행되어서 데이터를 전송하고 받는다. - 순서 보장
데이터를 보낸 순서대로 수신 측에 전달된다.
J & J - 정성태의 닷넷 이야기: Digital Stories
www.sysnet.pe.kr
https://ridibooks.com/books/1160000100
시작하세요! C# 10 프로그래밍
시작하세요! C# 10 프로그래밍 작품소개: 이 책의 목표는 여러분이 C#을 이용해 프로그래밍 기초를 탄탄하게 다질 수 있게 하는 것이다. 이를 위해 C# 언어의 최신 버전인 C# 10의 문법까지 구체적인
ridibooks.com
'C#' 카테고리의 다른 글
| C# 교육 2일차 (0) | 2023.10.17 |
|---|---|
| C# 교육 1일차 (0) | 2023.10.17 |
| 2023.09.14 BCL - 파일, 스레드, 네트워크 통신 (포트 까지) (0) | 2023.09.14 |
| 2023.09.13 BCL (시간, 문자열, StringBuilder, 정규 표현식, 컬렉션, ArrayList, Hashtable) (0) | 2023.09.13 |
| 2023.09.12 ref, out ,enum, readonly, const, event, index (0) | 2023.09.12 |