# 方法一
通用的做法是使用 getifaddrs
方法获取到指向本机网络接口信息的一个链表,然后通过遍历该链表拿到当前的网卡对应的 IP
地址。
iPhone
上的无线网卡是 en0
,所以拿到 ifaddrs
结构体后去判断其 name
成员变量的时候,判定 name
是否等于 en0
。
如果是运行 iPhone
模拟器的话 en0
是代表当前电脑上 en0
对应的网卡地址。
#include <netinet/in.h>
#include <sys/socket.h>
#include <ifaddrs.h>
#include <arpa/inet.h>
...
struct ifaddrs *address = NULL;
struct ifaddrs *temp = NULL;
if (0 != getifaddrs(&address)) {
NSLog(@"getifaddrs error = %s",strerror(errno));
return;
}
temp = address;
while (temp->ifa_next != NULL) {
NSString *if_name = [NSString stringWithUTF8String:temp->ifa_name];
if ([if_name isEqualToString:@"en0"]) {
struct sockaddr *ifa_addr = temp->ifa_addr;
if (ifa_addr->sa_family == AF_INET) {
struct sockaddr_in *in_address = (struct sockaddr_in *)ifa_addr;
char *ip_str = inet_ntoa(in_address->sin_addr);
NSLog(@"ip %@",[NSString stringWithFormat:@"%s",ip_str]);
}
}
temp = temp->ifa_next;
}
freeifaddrs(address);
# 方法二
iOS
上还有一种方式来获取,拿到通过解析当前机器的 hostname
返回地址链表中的第一个地址作为主 IP
。但是这种方法并不适用于 macOS
.
+ (NSString *)hostname {
char baseHostName[256];
int success = gethostname(baseHostName, 255);
if (success != 0) return nil;
baseHostName[255] = '\0';
#if !TARGET_IPHONE_SIMULATOR
return [NSString stringWithFormat:@"%s.local", baseHostName];
#else
return [NSString stringWithFormat:@"%s", baseHostName];
#endif
}
// return IP Address
+ (NSString *)localIPAddress {
struct hostent *host = gethostbyname([[self hostname] UTF8String]);
if (!host) {herror("resolv"); return nil;}
struct in_addr **list = (struct in_addr **)host->h_addr_list;
return [NSString stringWithCString:inet_ntoa(*list[0]) encoding:NSUTF8StringEncoding];
}
# 方法三
macOS
不能使用第二种方法,但是可以使用第一种的方法,但是有一个问题,在带有网口的 Mac
电脑上,en0
是代表以太网网卡地址,你获取到的 IP
也是该网卡地址,若想要只获取 WIFI
的对应网卡地址,则需要使用别的关键词,通常是 en1
去筛选。
可以使用 networksetup -listallhardwareports
命令来查看当前的网络硬件配置。
Hardware Port: Ethernet
Device: en0
Ethernet Address: 68:5b:35:a5:a2:d5
Hardware Port: Wi-Fi
Device: en1
Ethernet Address: c8:e0:eb:4c:f9:bf
....
所以想获取 WIFI
的网卡地址话需要将 en0
替换为 en1
,但是这并不是通用的方案,因为在 Mac Air 上 en0
再次代表了 WIFI
,enX
这种判断方式不够靠谱,我们想要一个更加通用的解决方案。
通用的解决方案如下,获取系统配置,通过匹配 AirPort
关键字来进行匹配,里面的关键字参考 System Configuration Programming Guidelines (opens new window)
//1. 创建 dynamic store.
SCDynamicStoreRef store = SCDynamicStoreCreate(NULL, (__bridge CFStringRef)@"example", NULL, NULL);
//2. 通过 keystore 从 dynamic store 中获取数据.
NSString *keyStr = @"Setup:/Network/Global/IPv4";
NSDictionary *global = (__bridge NSDictionary *)SCDynamicStoreCopyValue(store, (__bridge CFStringRef)keyStr);
//3. 根据 IPv4 的全局数据拿所有 services
NSArray *services = [global objectForKey:@"ServiceOrder"];
//4. 取出 wifi 相关 service.
//Note: wifi serviceId 和 '/Library/Preferences/SystemConfiguration/preferences.plist' 里 wifi serviceID 一样.
for (NSString *serviceID in services) {
NSString *serviceKeyStr = [NSString stringWithFormat:@"State:/Network/Service/%@/IPv4",serviceID];
NSDictionary *serviceInfo = (__bridge NSDictionary *)SCDynamicStoreCopyValue(store, (__bridge CFStringRef)serviceKeyStr);
if (serviceInfo) {
NSString *interfaceKeyStr = [NSString stringWithFormat:@"Setup:/Network/Service/%@/Interface",serviceID];
NSDictionary *globalInterface = (__bridge NSDictionary *)SCDynamicStoreCopyValue(store, (__bridge CFStringRef)interfaceKeyStr);
if ([[globalInterface objectForKey:@"Hardware"] isEqualToString:@"AirPort"] ) {
NSString *wifiAddress = [[serviceInfo objectForKey:@"Addresses"] objectAtIndex:0];
return wifiAddress;
}
}
}
但是这个方法并不适用于 iOS
,因为 iOS
不支持上面的 API
# 获取空闲端口
大概思路:
创建套接字。
int local_sock = socket(temp_addr->ifa_addr->sa_family,SOCK_DGRAM, 0);
构建本地
sockaddr
的时候sin_port
变量传入 0 。struct sockaddr_in local_addr; bzero(&local_addr, sizeof(local_addr)); local_addr.sin_family = AF_INET; local_addr.sin_port = 0; local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
然后
bind
这个套接字到本地sockaddr
地址。result = bind(local_sock, (struct sockaddr *)&local_addr, sizeof(local_addr));
如果
bind
成功,接下来通过getsockname
方法来获取sockaddr
地址。这时候sockaddr
里的sin_port
即为没有被占用的端口。getsockname(local_sock, (struct sockaddr *)&sin, &len)
有点空手套白狼的意思。
# macOS 上监听 WIFI 切换引起 IP 变化的方法
之前使用的 AFNetworkReachabilityManager
不太符合要求,有的时候切换了 WIFI 也不能及时进行变化。 CoreWLAN
框架提供了一个监听的方法
- (BOOL)startMonitoringEventWithType:(CWEventType)type error:(out NSError * _Nullable *)error;
不幸的是,这个方法不能再沙盒之外使用。
经过搜索发现一个方法能完整实现改功能,其实就是上面的获取 IP 的第三个方法,只不过需要增加点东西。具体可以参考下面代码。
static NSString *wifiServiceKeyStr;
//监听 WIFI 变化的回调方法
void dynamicStoreChange(SCDynamicStoreRef store,CFArrayRef changedKeys, void * __nullable info) {
NSLog(@"store %@,changedKeys %@,info %s",store,changedKeys,info);
for (NSString *changeKey in (__bridge NSArray *)changedKeys) {
if ([changeKey isEqualToString:wifiServiceKeyStr]) {
NSDictionary *serviceInfo = (__bridge NSDictionary *)SCDynamicStoreCopyValue(store, (__bridge CFStringRef)changeKey);
NSLog(@"now server info %@",serviceInfo);
}
}
}
//1. 创建 dynamic store.
SCDynamicStoreRef store = SCDynamicStoreCreate(NULL, (__bridge CFStringRef)@"example", dynamicStoreChange, NULL);
NSString *interfaceKey = @"State:/Network/Interface";
NSDictionary *interfaces = (__bridge NSDictionary *)SCDynamicStoreCopyValue(store, (__bridge CFStringRef)interfaceKey);
NSLog(@"interfaces = %@",interfaces);
//2. 通过 keystore 从 dynamic store 中获取数据.
NSString *keyStr = @"Setup:/Network/Global/IPv4";
NSDictionary *global = (__bridge NSDictionary *)SCDynamicStoreCopyValue(store, (__bridge CFStringRef)keyStr);
//3. 根据 IPv4 的全局数据拿所有 services
NSArray *services = [global objectForKey:@"ServiceOrder"];
//4. 取出 wifi 相关 service.
//Note: wifi serviceId 和 '/Library/Preferences/SystemConfiguration/preferences.plist' 里 wifi serviceID 一样.
//注意不能用 en0 和 en1 进行判断。en0 和 en1 在 iMac 和 macAir 上有对应不同的网卡。
for (NSString *service in services) {
NSString *serviceKeyStr = [NSString stringWithFormat:@"State:/Network/Service/%@/IPv4",service];
NSDictionary *serviceInfo = (__bridge NSDictionary *)SCDynamicStoreCopyValue(store, (__bridge CFStringRef)serviceKeyStr);
if (serviceInfo) {
NSString *interfaceKeyStr = [NSString stringWithFormat:@"Setup:/Network/Service/%@/Interface",service];
NSDictionary *globalInterface = (__bridge NSDictionary *)SCDynamicStoreCopyValue(store, (__bridge CFStringRef)interfaceKeyStr);
if ([[globalInterface objectForKey:@"Hardware"] isEqualToString:@"AirPort"] ) {
NSLog(@"service info = %@",serviceInfo);
wifiServiceKeyStr = serviceKeyStr;
}
}
}
//5. 监听 wifi 相关 service 的变化.
if (wifiServiceKeyStr) {
SCDynamicStoreSetNotificationKeys(store, NULL,(__bridge CFArrayRef)@[wifiServiceKeyStr]);
CFRunLoopAddSource(CFRunLoopGetCurrent(),
SCDynamicStoreCreateRunLoopSource(NULL, store, 0),
kCFRunLoopCommonModes);
}