본문 바로가기

C#

C# 멀티캐스트 안됨 해결법 두번째

 

 

C# 멀티캐스트 안됨 해결법 / 랜카드 NIC / C++도 참고가능

네트워크 프로그램이 필요해서 C#으로 Sender와 Receiver를 만들어 실행했는데, 처음에는 이상이 없었습니다. 이상이 있으면 MSDN 샘플이 잘못된 것일테니까요. 그런데 각기 다른 컴퓨터를 두고 실행

healp.tistory.com

해결법 첫 번째 먼저 보고 오시는 것을 추천드립니다.

 

 

첫 번째 방법으로 잘 사용하던 중, 공유기 하나 교체했을 뿐인데 아래와 같은 에러가 등장했습니다.

 

NullReferenceException: Object reference not set to an instance of an object
System.Net.NetworkInformation.Win32IPv3InterfaceProperties..ctor (System.Net.NetworkInformation.Win32_IP_ADAPATER_ADDRESSES addr, System.Net.NetworkInformation.Win32_MIB_IFROW mib)

 

해결하기 위해 방안을 찾던중에 C#으로 래핑된 NetInterface가 아닌 네이티브인 iphlpapi.dll을 직접 호출하여 사용해보기로 하였습니다.

 

코드입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Runtime.InteropServices;
 
public static class NetInterface
{
        [DllImport("iphlpapi.dll", CharSet = CharSet.Auto)]
        private static extern int GetBestInterface(UInt32 DestAddr, out UInt32 BestIfIndex);
        [DllImport("iphlpapi.dll", CharSet = CharSet.Auto)]
        private static extern int GetAdaptersInfo(IntPtr pAdapterInfo, ref Int64 pBufOutLen);
 
        const int MAX_ADAPTER_DESCRIPTION_LENGTH = 128;
        const int ERROR_BUFFER_OVERFLOW = 111;
        const int MAX_ADAPTER_NAME_LENGTH = 256;
        const int MAX_ADAPTER_ADDRESS_LENGTH = 8;
        const int MIB_IF_TYPE_OTHER = 1;
        const int MIB_IF_TYPE_ETHERNET = 6;
        const int MIB_IF_TYPE_TOKENRING = 9;
        const int MIB_IF_TYPE_FDDI = 15;
        const int MIB_IF_TYPE_PPP = 23;
        const int MIB_IF_TYPE_LOOPBACK = 24;
        const int MIB_IF_TYPE_SLIP = 28;
 
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct IP_ADAPTER_INFO
        {
            public IntPtr Next;
            public Int32 ComboIndex;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_ADAPTER_NAME_LENGTH + 4)]
            public string AdapterName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_ADAPTER_DESCRIPTION_LENGTH + 4)]
            public string AdapterDescription;
            public UInt32 AddressLength;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_ADAPTER_ADDRESS_LENGTH)]
            public byte[] Address;
            public Int32 Index;
            public UInt32 Type;
            public UInt32 DhcpEnabled;
            public IntPtr CurrentIpAddress;
            public IP_ADDR_STRING IpAddressList;
            public IP_ADDR_STRING GatewayList;
            public IP_ADDR_STRING DhcpServer;
            public bool HaveWins;
            public IP_ADDR_STRING PrimaryWinsServer;
            public IP_ADDR_STRING SecondaryWinsServer;
            public Int32 LeaseObtained;
            public Int32 LeaseExpires;
        }
 
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct IP_ADDR_STRING
        {
            public IntPtr Next;
            public IP_ADDRESS_STRING IpAddress;
            public IP_ADDRESS_STRING IpMask;
            public Int32 Context;
        }
 
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct IP_ADDRESS_STRING
        {
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
            public string Address;
        }
 
        static Dictionary<string, IP_ADAPTER_INFO> entrys = new Dictionary<string, IP_ADAPTER_INFO>();
        static Dictionary<int, IP_ADAPTER_INFO> entrysInt = new Dictionary<int, IP_ADAPTER_INFO>();
        static IP_ADAPTER_INFO entry;
        public static void GetAdapters()
        {
            long structSize = Marshal.SizeOf(typeof(IP_ADAPTER_INFO));
            IntPtr pArray = Marshal.AllocHGlobal(new IntPtr(structSize));
 
            int ret = GetAdaptersInfo(pArray, ref structSize);
 
            if (ret == ERROR_BUFFER_OVERFLOW) // ERROR_BUFFER_OVERFLOW == 111
            {
                // Buffer was too small, reallocate the correct size for the buffer.
                pArray = Marshal.ReAllocHGlobal(pArray, new IntPtr(structSize));
 
                ret = GetAdaptersInfo(pArray, ref structSize);
            } // if
 
            if (ret == 0)
            {
                // Call Succeeded
                IntPtr pEntry = pArray;
 
                do
                {
                    // Retrieve the adapter info from the memory address
                    entry = (IP_ADAPTER_INFO)Marshal.PtrToStructure(pEntry, typeof(IP_ADAPTER_INFO));
 
                    // ***Do something with the data HERE!***
                    Console.WriteLine("\n");
                    Console.WriteLine("Index: {0}", entry.Index.ToString());
 
                    // Adapter Type
                    string tmpString = string.Empty;
                    switch (entry.Type)
                    {
                        case MIB_IF_TYPE_ETHERNET: tmpString = "Ethernet"break;
                        case MIB_IF_TYPE_TOKENRING: tmpString = "Token Ring"break;
                        case MIB_IF_TYPE_FDDI: tmpString = "FDDI"break;
                        case MIB_IF_TYPE_PPP: tmpString = "PPP"break;
                        case MIB_IF_TYPE_LOOPBACK: tmpString = "Loopback"break;
                        case MIB_IF_TYPE_SLIP: tmpString = "Slip"break;
                        default: tmpString = "Other/Unknown"break;
                    } // switch
                    Console.WriteLine("Adapter Type: {0}", tmpString);
 
                    Console.WriteLine("Name: {0}", entry.AdapterName);
                    Console.WriteLine("Desc: {0}\n", entry.AdapterDescription);
 
                    Console.WriteLine("DHCP Enabled: {0}", (entry.DhcpEnabled == 1) ? "Yes" : "No");
 
                    if (entry.DhcpEnabled == 1)
                    {
                        Console.WriteLine("DHCP Server : {0}", entry.DhcpServer.IpAddress.Address);
 
                        // Lease Obtained (convert from "time_t" to C# DateTime)
                        DateTime pdatDate = new DateTime(197011).AddSeconds(entry.LeaseObtained).ToLocalTime();
                        Console.WriteLine("Lease Obtained: {0}", pdatDate.ToString());
 
                        // Lease Expires (convert from "time_t" to C# DateTime)
                        pdatDate = new DateTime(197011).AddSeconds(entry.LeaseExpires).ToLocalTime();
                        Console.WriteLine("Lease Expires : {0}\n", pdatDate.ToString());
                    } // if DhcpEnabled
 
                    Console.WriteLine("IP Address     : {0}", entry.IpAddressList.IpAddress.Address);
                    Console.WriteLine("Subnet Mask    : {0}", entry.IpAddressList.IpMask.Address);
                    Console.WriteLine("Default Gateway: {0}", entry.GatewayList.IpAddress.Address);
 
                    // MAC Address (data is in a byte[])
                    tmpString = string.Empty;
                    for (int i = 0; i < entry.Address.Length - 1; i++)
                    {
                        tmpString += string.Format("{0:X2}-", entry.Address[i]);
                    }
                    Console.WriteLine("MAC Address    : {0}{1:X2}\n", tmpString, entry.Address[entry.Address.Length - 1]);
 
                    Console.WriteLine("Has WINS: {0}", entry.HaveWins ? "Yes" : "No");
                    if (entry.HaveWins)
                    {
                        Console.WriteLine("Primary WINS Server  : {0}", entry.PrimaryWinsServer.IpAddress.Address);
                        Console.WriteLine("Secondary WINS Server: {0}", entry.SecondaryWinsServer.IpAddress.Address);
                    } // HaveWins
 
                    // Get next adapter (if any)
                    pEntry = entry.Next;
 
                    entrys.Add(entry.AdapterDescription, entry);
                    entrysInt.Add(entry.Index, entry);
                }
                while (pEntry != IntPtr.Zero);
 
                Marshal.FreeHGlobal(pArray);
 
            } // if
            else
            {
                Marshal.FreeHGlobal(pArray);
                throw new InvalidOperationException("GetAdaptersInfo failed: " + ret);
            }
 
        } // GetAdapters
 
        /// <summary>
        /// 어댑터 이름으로 판별해서 리턴 (풀네임을 넣지 않아도 됨)
        /// </summary>
        /// <param name="adpaterName"></param>
        public static int GetBestIF(string adpaterName)
        {
            foreach (var entry in entrys)
            {
                if (entry.Value.AdapterDescription.Contains(adpaterName))
                {
                    return entry.Value.Index;
                }
 
            }
            return 0;
        }
 
        /// <summary>
        /// 어댑터 Index값으로 IP가져오기
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public static string GetBestIF(int index)
        {
            foreach (var entry in entrysInt)
            {
                if (entry.Value.Index == index)
                {
                    return entry.Value.IpAddressList.IpAddress.Address;
                }
            }
            return "000.000.000.000";
        }
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="ip"></param>
        /// <returns></returns>
        public static int GetBestIF(uint ip)
        {
            uint index = 0;
            GetBestInterface(ip, out index);
            return (int)index;
        }
        
        /// <summary>
        /// string타입 IP를 uint타입 IP로 변경
        /// </summary>
        /// <param name="addr"></param>
        /// <returns></returns>
        static uint ToInt(string addr)
        {
            // careful of sign extension: convert to uint first;
            // unsigned NetworkToHostOrder ought to be provided.
            return (uint)IPAddress.NetworkToHostOrder((int)IPAddress.Parse(addr).Address);
        }
 
        /// <summary>
        /// long(int) 타입 IP를 string타입 IP로 변경
        /// </summary>
        /// <param name="address"></param>
        /// <returns></returns>
        static string ToAddr(long address)
        {
            return IPAddress.Parse(address.ToString()).ToString();
            // This also works:
            // return new IPAddress((uint) IPAddress.HostToNetworkOrder(
            //    (int) address)).ToString();
        }
}
cs

https://www.pinvoke.net/default.aspx/iphlpapi.getadaptersinfo

코드는 위 사이트를 참고하여 작성되었습니다.

 

C#으로 래핑된 NetInterface 코드가 아닌 C++로 작성된 DLL의 함수를 호출하여 에러를 해결할 수 있었습니다.

 

사용법

1) GetAdapters 함수를 한번 호출하여 현재 내 컴퓨터에 존재하는 어댑터들을 수집합니다.

 

2) GetBestIF(string) 함수를 호출하여 어댑터의 Index를 구합니다.

이때 멀티캐스트 패킷을 송수신할 어댑터의 이름을 매개변수를 통하여 전달해줍니다.

어댑터 이름을 전부 적을 필요는 없고 일부만 적어도 됩니다.

 

3) GetBestIF(int) 함수를 호출하여 IP를 알아냅니다.

 

4) 알아낸 IP를 필요한 상황에 맞게 사용하시면 됩니다.

UDP 멀티캐스트의 경우

1
SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(ipAddress, IPAddress.Parse(ip)));
cs

그룹에 가입할 때 매개변수중 new MulticastOption의 두번째 인수에 IPAddress.Parse(알아낸 ip)에 적어주면 됩니다.

 

 

이 방법은 멀티캐스트가 아니더라도 사용할 수는 있지만 TCP나 브로드캐스트의 경우 이렇게까지 안해도 잘만 됐던걸로..


여담

멀티캐스트 UDP가 저를 끝까지 괴롭혔는데요. 현재는 이 방법으로 아직까지 문제 없이 사용하고 있습니다.

재밌는 사실은 GetBestIF(uint)를 통해 멀티캐스트 대역 ip를 전달하면 에러를 뱉으며 어댑터를 찾지 못하는 경우가 발생했습니다. 일반적인 인터넷 용도로 사용하는 ip는 송수신할 어댑터를 dll이 알아서 잘 찾아줬는데 말입니다.

 

그래서 임시방편으로 어댑터 정보를 전부 저장한다음에 사용할 수 있게 작성되었습니다.

현재는 어댑터의 이름을 하드코딩해야하는 상황이지만, 추가 방안을 제시한다면

 

외부 인터넷망의 ip를 사용해서 어댑터를 알아낸 다음 그 어댑터의 ip를 알아내면 될 것 같습니다.

이때 외부 ip는 구글에서 제공하는 (1.1.1.1) (8.8.8.8)를 사용해도 괜찮을 것 같고 MS에서도 비슷한 목적으로 제공하는 것으로 알고 있습니다.