UDP 브로드캐스팅을 이용해 서비스 측의 IP 주소를 구하는 방법
프로그램을 만들다 보면, 사용자에게 IP 주소를 입력받는 경우가 생기는데요. 이 작업이 다소 귀찮은 것이 사실입니다. 다행히 (UDP 패킷이 전달되는) 같은 네트워크 내에 소켓 클라이언트/서버가 있다면 이 작업을 UDP 브로드캐스팅으로 간단하게 해결할 수 있습니다.
우선, 서비스를 제공하는 측의 프로그램에서는 다음과 같이 UDP 소켓을 열어 Receive로 대기해 놓아야 합니다.
// ============= 서버 측 프로그램 =============
private static void StartListener()
{
bool done = false;
UdpClient listener = new UdpClient(listenPort);
// 11000번 포트로 UDP 브로드캐스팅 패킷을 수신하기 위한 접점 정보 구성
IPEndPoint groupEP = new IPEndPoint(IPAddress.Broadcast, 11000);
while (!done)
{
// UDP 브로드캐스팅 패킷을 수신
byte[] bytes = listener.Receive(ref groupEP); // 블록킹 상태로 진입
// ... 생략 ...
}
listener.Close();
}
그럼, 해당 서비스를 찾는 클라이언트 측에서는 UDP 소켓을 이용해 패킷을 현재 망에 뿌려주면 됩니다.
// ============= 클라이언트 측 프로그램 =============
static void Main(string[] args)
{
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
s.EnableBroadcast = true;
byte[] sendbuf = Encoding.ASCII.GetBytes("HELLO");
EndPoint targetEndPoint = new IPEndPoint(IPAddress.Broadcast, 11000);
// UDP 브로드캐스팅 패킷을 네트워크에 전송
s.SendTo(sendbuf, targetEndPoint);
// ... 생략 ...
}
클라이언트 측의 SendTo로 브로드캐스팅 패킷이 네트워크에 뿌려지면 서버 측에서 대기 중인 listener.Receive가 이를 수신하게 됩니다. 그럼, 서버는 자신의 IP 주소를 상대방에게 전송함으로써 서비스가 대기 중인 IP 주소를 알릴 수 있습니다.
그런데, 여기서 한 가지 주의해야 할 사항이 있는데요. 서버에는 네트워크 어댑터의 수에 따라 다양한 IP 주소가 할당될 수 있고, 그 중에는 클라이언트 측 IP 주소와 동일한 네트워크를 공유하지 않는 경우도 발생할 수 있으므로 아무 IP나 전송해서는 안됩니다. 가능하다면 서브넷 마스크값을 확인해서 그에 대응되는 IP 주소 값을 보내주는 것이 좋은데, 이에 대해서는 다음과 같은 방식으로 구할 수 있습니다.
private static void StartListener()
{
// ... 생략 ...
byte[] bytes = listener.Receive(ref groupEP);
string txt = FindUdpEndpoint(groupEP);
// ... 생략 ...
}
private static string FindUdpEndpoint(IPEndPoint groupEP)
{
byte[] clntAddress = groupEP.Address.GetAddressBytes();
foreach (var item in GetInetAddress(AddressFamily.InterNetwork))
{
byte[] svrAddress = item.Item1.GetAddressBytes();
byte[] maskAddress = item.Item2.GetAddressBytes();
bool match = true;
for (int i = 0; i < maskAddress.Length; i++)
{
if (maskAddress[i] == 0xFF)
{
if (svrAddress[i] != clntAddress[i])
{
match = false;
break;
}
}
else
{
break;
}
}
if (match == true)
{
return item.Item1.ToString();
}
}
return null;
}
public static Tuple<IPAddress, IPAddress>[] GetInetAddress(System.Net.Sockets.AddressFamily family)
{
List<Tuple<IPAddress, IPAddress>> ipAddresses = new List<Tuple<IPAddress, IPAddress>>();
NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();
foreach (NetworkInterface nic in nics)
{
foreach (UnicastIPAddressInformation uni in nic.GetIPProperties().UnicastAddresses)
{
if (uni.Address.AddressFamily == family)
{
if (System.Net.IPAddress.Loopback.ToString() == uni.Address.ToString())
{
continue;
}
ipAddresses.Add(Tuple.Create(uni.Address, uni.IPv4Mask));
}
}
}
return ipAddresses.ToArray();
}
대응되는 IP 주소도 구했으니, 서버는 그 값을 클라이언트 측에 전송해 주는 것으로 모든 임무를 끝마칠 수 있습니다.
// ============= 서버 측 프로그램 =============
private static void StartListener()
{
// ... 생략 ...
while (!done)
{
byte[] bytes = listener.Receive(ref groupEP);
string txt = FindUdpEndpoint(groupEP);
// UDP 브로드캐스팅을 발생시킨 클라이언트 측으로 맞대응되는 IP 주소를 전송
byte[] buffer = Encoding.UTF8.GetBytes(txt);
listener.Send(buffer, buffer.Length, groupEP);
}
// ... 생략 ...
}
그럼, 클라이언트는 어떻게 응답을 수신하고 있어야 할까요? 단순하게 ReceiveFrom을 호출해도 되지만, 서비스로부터의 응답이 안 오는 경우도 발생할 수 있으므로 비동기 호출을 하는 것이 좋습니다.
// ============= 클라이언트 측 프로그램 =============
static void Main(string[] args)
{
// ... 생략 ...
s.SendTo(sendbuf, targetEndPoint);
var arg = new SocketAsyncEventArgs();
arg.Completed += ReceiveAsync_Completed;
arg.SetBuffer(new byte[4096], 0, 4096);
// 비동기 방식의 수신 메서드 호출
s.ReceiveAsync(arg);
Console.ReadLine();
}
static void ReceiveAsync_Completed(object sender, SocketAsyncEventArgs e)
{
if (e.Buffer.Length == 0)
{
return;
}
// 수신이 완료되었을 때 호출되는 메서드
string txt = Encoding.UTF8.GetString(e.Buffer);
Console.WriteLine("Echo from : " + txt.TrimEnd('\0'));
}
결과적으로, 사용자는 IP 주소를 입력할 필요가 없어졌으니 프로그램 사용이 보다 더 편해지게 되었습니다. ^^
(
첨부한 파일은 예제 프로젝트를 담고 있습니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]