조금 더 자세하게 분석을 하고 글을 작성하고 싶었으나, 나의 실력의 부족인 문제로...
못하게 되었다.
UdpClient..! 이름만 들으면 네트워크 프로토콜 중 UDP 를 사용 할 수 있게 해주는 클래스로 보인다.
맞다. UdpClient는 Udp 통신을 통괄 하는, 사용이 가능하게 해주는 객체이다.
Udp 통신을 해주는 클래스는 UdpClient이름만 보고 UdpServer도 있을것 같지만 Server기능 또한 Client에 들어가 있다.
그래서 C#에서 Udp 서버를 만들려면 UdpClient를 사용한다.
본 글은 UdpClient를 아무 생각 없이 사용 했을 때 발생할 수 있는 버그를, 오류를 작성 하였다.
특정 장비에서 인바운드, 아웃바운드 포트를 맞춰 줘야지, 데이터를 읽을 수 있는 특수 장비가 있었고.
class Program
{
static void Main(string[] args)
{
UdpClient random = new UdpClient();
UdpClient notRandom = new UdpClient(15000);
IPEndPoint endPoint = new IPEndPoint(IPAddress.Loopback, 17000);
while (true)
{
byte[] sendData = Encoding.UTF8.GetBytes("data");
random.Send(sendData, sendData.Length, endPoint);
notRandom.Send(sendData, sendData.Length, endPoint);
Task.Delay(100).Wait();
}
}
}
해당 코드를 돌리게 될 경우에 어떤 일이 일어날까?
놀랍게도 random 이란 UdpClient는 랜덤으로 열 수 있는 포트를 연 뒤, 17000포트로 데이터를 전송한다.
notRandom은 최초 15000이란 값을 주었기 때문에 15000포트를 연 뒤 17000포트로 데이터를 보낸다.
이 처럼 데이터가 52061 포트에서 17000포트로 보내지는 것을 Wireshark로 확인이 가능하다.
그리고 15000포트에서 17000포트로 가는것 또한 볼 수 있다.
class Program
{
static void Main(string[] args)
{
IPEndPoint endPoint = new IPEndPoint(IPAddress.Loopback, 17000);
while (true)
{
byte[] sendData = Encoding.UTF8.GetBytes("data");
UdpClient random = new UdpClient();
random.Send(sendData, sendData.Length, endPoint);
random.Close();
random.Dispose();
Task.Delay(100).Wait();
}
}
}
자 만약 random이란 UdpClient가 while문 안에 있다면 어떤 일이 발생 할 까?
random 생성자에서 랜덤으로 포트를 열고, 데이터를 보내고, 포트를 닫고, UdpClient를 정리 한다.
해당 행위가 무한 루프 이므로 게속 랜덤으로 포트를 열고 보내는 것을 확인이 가능하다.
똑같은 포트를 쓰지 않고 랜덤으로 지정해서 보내지는걸 확인이 가능하다.
이런 현상이 발생하는지, 자세하게 들어가 보았다.
아래의 링크에서 C#의 내부 로직이 어떻게 작성이 되어있는지 오픈 소스로 공개가 되어있으므로 확인을 해보고, 체크를 했다.
https://referencesource.microsoft.com
Reference Source
referencesource.microsoft.com
UdpClient내용을 확인을 하였을 때, 생성자 부터 보면
UdpClient 생성자, 즉 아무것도 안받는 생성자는 UdpClient(AddressFamily.InterNetwork) 생성자를 호출 한 것과 같다.
// bind to arbitrary IP+Port
/// <devdoc>
/// <para>
/// Initializes a new instance of the <see cref='System.Net.Sockets.UdpClient'/>class.
/// </para>
/// </devdoc>
public UdpClient() : this(AddressFamily.InterNetwork)
{
}
그러면 이제 다시 AddressFamily.InterNetwork를 인자를 전달 받는 생성자를 가보면.
/// <devdoc>
/// <para>
/// Initializes a new instance of the <see cref='System.Net.Sockets.UdpClient'/>class.
/// </para>
/// </devdoc>
public UdpClient(AddressFamily family)
{
//
// Validate the address family
//
if (family != AddressFamily.InterNetwork && family != AddressFamily.InterNetworkV6)
{
throw new ArgumentException(SR.GetString(SR.net_protocol_invalid_family, "UDP"), "family");
}
m_Family = family;
createClientSocket();
}
family 의 값이 InterNetwork, InterNetworkV6 가 아니면 예외를 발생 시키는데,
해당 코드는 family의 값이 InterNetwork이므로 무조건 거짓이라고 보면.
// private AddressFamily m_Family = AddressFamily.InterNetwork;
// m_Family는 UdpClient 클래스 안에 위 처럼 정의가 되어 있다.
public UdpClient(AddressFamily family)
{
m_Family = family;
createClientSocket();
}
로 보인다.
아직 까진 Port와 관련 되어서 초기화나 바인드를 걸지 않았으므로 포트는 열릴 가능성이 적다.
createClientSocket() 함수는 내부 함수로 정의가 되어 있으며, 다시 확인을 해 보았다.
createClientSocket() 함수는 아래 처럼 Client를 초기화 해주는 코드이다.
public Socket Client
{
get
{
return m_ClientSocket;
}
set
{
m_ClientSocket = value;
}
}
먼저 createClientSocket() 함수는 내부적으로 Client라는 프로퍼티를 초기화 하는데 Client 정의 부터 보고 간다.
줄여서 public Socket Client { get; set; }; 이다.
private void createClientSocket()
{
//
// common initialization code
//
// IPv6 Changes: Use the AddressFamily of this class rather than hardcode.
//
Client = new Socket(m_Family, SocketType.Dgram, ProtocolType.Udp);
}
전달 해주는 값을
m_Family : AddressFamily.InterNetwork
SocketType.Dgram,
ProtocolType.Udp 이다.
다음 내용을 확인을 한다면 Socket 클래스 이다.
Socket 클래스의 생성자에서 인자 값을 3개 받는 것은 1개 이므로. 해당 코드가 정답일것 같다.
public Socket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType)
{
s_LoggingEnabled = Logging.On;
if (s_LoggingEnabled) Logging.Enter(Logging.Sockets, this, "Socket", addressFamily);
InitializeSockets();
m_Handle = SafeCloseSocket.CreateWSASocket(
addressFamily,
socketType,
protocolType);
if (m_Handle.IsInvalid)
{
//
// failed to create the win32 socket, throw
//
throw new SocketException();
}
this.addressFamily = addressFamily;
this.socketType = socketType;
this.protocolType = protocolType;
IPProtectionLevel defaultProtectionLevel = SettingsSectionInternal.Section.IPProtectionLevel;
if (defaultProtectionLevel != IPProtectionLevel.Unspecified)
{
SetIPProtectionLevel(defaultProtectionLevel);
}
if (s_LoggingEnabled) Logging.Exit(Logging.Sockets, this, "Socket", null);
}
내부적인 코드를 계속 들어가다 보니까... 이해를 했는데..
SafeCloseSocket.CreateWSASocket안을 계속 들어가다 보면. 결국엔..
Microsoft가 만든 외부 dll 파일을 호출 하는 내용이 나온다... 아마 Wrapper 형인것 같다...
결국엔 포트 랜덤 지정은 OS가 정하는것 같다.
여기까지 들어가 보면서 현타가 왔다...
결론 : 포트 지정 안하면 OS가 정한다.
'차장님의 이야기' 카테고리의 다른 글
macOS용 음원 파일 관리 프로그램 제작 #1 (0) | 2020.06.23 |
---|---|
C++의 offsetof 매크로 뜯어보기 (0) | 2020.06.19 |
유니티의 CBD(Component-Based Development) 구현 해보기 (0) | 2020.06.15 |
Swift에서 Udp 사용하기 성공! (0) | 2020.06.04 |
IOS에서 Itunes에 파일 목록에 나오게 하기. (0) | 2020.06.03 |