如何快速获取IP地址
Posted
技术标签:
【中文标题】如何快速获取IP地址【英文标题】:How to get Ip address in swift 【发布时间】:2014-10-26 21:06:45 【问题描述】:我如何获得我的本地 IP 地址?
我尝试使用这个Obj C
示例:how to get ip address of iphone programmatically
当我到达函数getifaddrs()
时,我无法再进一步了。我无法使用该功能。
是否有其他方法可以做到这一点,还是我以错误的方式接近这个?
【问题讨论】:
Swift 不能直接与 C 通信,你必须使用 Objective-C 作为桥梁。 @akashivskyy:这通常不是真的。 许多 C 函数向 Swift 公开。getifaddrs()
然而似乎对 Swift 不可见,可能是使用的数据结构不兼容。
@akashivskyy 这并不完全正确,在很多情况下 swift 可以直接与 C 通信,例如。几乎所有的 CoreFoundation API 都可以访问。也就是说,在这种特定情况下,编写 Objective-C 粘合层可能会更容易,因为需要指针算术和结构解包。
【参考方案1】:
正如讨论中证明的那样,OP 需要 Mac 上的接口地址,而不是我最初认为的 ios 设备上的接口地址。问题中引用的代码检查 接口名称“en0”,即iPhone上的WiFi接口。在 Mac 上,它做得更多 而是检查任何“启动并运行”的接口。 因此,我重写了答案。它现在是代码的 Swift 翻译 Detect any connected network.
getifaddrs()
定义在<ifaddrs.h>
中,默认不包含。
因此,您必须创建一个桥接头并添加
#include <ifaddrs.h>
以下函数返回 一个包含所有本地“正在运行”的网络接口名称的数组。
func getIFAddresses() -> [String]
var addresses = [String]()
// Get list of all interfaces on the local machine:
var ifaddr : UnsafeMutablePointer<ifaddrs> = nil
if getifaddrs(&ifaddr) == 0
// For each interface ...
var ptr = ifaddr
while ptr != nil
defer ptr = ptr.memory.ifa_next
let flags = Int32(ptr.memory.ifa_flags)
let addr = ptr.memory.ifa_addr.memory
// Check for running IPv4, IPv6 interfaces. Skip the loopback interface.
if (flags & (IFF_UP|IFF_RUNNING|IFF_LOOPBACK)) == (IFF_UP|IFF_RUNNING)
if addr.sa_family == UInt8(AF_INET) || addr.sa_family == UInt8(AF_INET6)
// Convert interface address to a human readable string:
var hostname = [CChar](count: Int(NI_MAXHOST), repeatedValue: 0)
if (getnameinfo(ptr.memory.ifa_addr, socklen_t(addr.sa_len), &hostname, socklen_t(hostname.count),
nil, socklen_t(0), NI_NUMERICHOST) == 0)
if let address = String.fromCString(hostname)
addresses.append(address)
freeifaddrs(ifaddr)
return addresses
Swift 3 更新:除了采用代码
many changes in Swift 3,
遍历所有接口现在可以使用新的泛化
sequence()
函数:
func getIFAddresses() -> [String]
var addresses = [String]()
// Get list of all interfaces on the local machine:
var ifaddr : UnsafeMutablePointer<ifaddrs>?
guard getifaddrs(&ifaddr) == 0 else return []
guard let firstAddr = ifaddr else return []
// For each interface ...
for ptr in sequence(first: firstAddr, next: $0.pointee.ifa_next )
let flags = Int32(ptr.pointee.ifa_flags)
let addr = ptr.pointee.ifa_addr.pointee
// Check for running IPv4, IPv6 interfaces. Skip the loopback interface.
if (flags & (IFF_UP|IFF_RUNNING|IFF_LOOPBACK)) == (IFF_UP|IFF_RUNNING)
if addr.sa_family == UInt8(AF_INET) || addr.sa_family == UInt8(AF_INET6)
// Convert interface address to a human readable string:
var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
if (getnameinfo(ptr.pointee.ifa_addr, socklen_t(addr.sa_len), &hostname, socklen_t(hostname.count),
nil, socklen_t(0), NI_NUMERICHOST) == 0)
let address = String(cString: hostname)
addresses.append(address)
freeifaddrs(ifaddr)
return addresses
【讨论】:
当我运行它时,我发现 tmpAddr 中的所有实例变量的值都是 0。所以它们不指向任何东西。我认为这不正确? c 的 if 语句失败。 @MadsGadeberg:奇怪,我已经在设备和模拟器中测试了代码,它对我有用。 - 哪个 if 语句失败? 我们检查 sa_family 是否为 AF_INET 的地方。如果我在 wifi 上,这有关系吗? @MadsGadeberg:改用 AF_INET6。 @MadsGadeberg:我已经修改了函数,以便打印一些调试输出。【参考方案2】:回复Is there an alternative way to getifaddrs() for getting the ip-address?
是的,还有另一种方法。您还可以使用ioctl()
获取IP 地址。最干净的方法是用 C 语言完成,然后用 Swift 封装。所以考虑一下:
创建一个C Source File
(Xcode 应该与 .h 一起创建它)并记住将标头添加到项目的桥接头中,或者如果您有可可触摸框架,则添加到伞头中。
将以下内容添加到您的 .c 源代码中,并将方法的声明添加到 .h 中:
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <net/if.h>
int _interfaceAddressForName(char* interfaceName, struct sockaddr* interfaceAddress)
struct ifreq ifr;
int fd = socket(AF_INET, SOCK_DGRAM, 0);
ifr.ifr_addr.sa_family = AF_INET;
strncpy(ifr.ifr_name, interfaceName, IFNAMSIZ-1);
int ioctl_res;
if ( (ioctl_res = ioctl(fd, SIOCGIFADDR, &ifr)) < 0)
return ioctl_res;
close(fd);
memcpy(interfaceAddress, &ifr.ifr_addr, sizeof(struct sockaddr));
return 0;
您的 Swift 包装器可能类似于:
public enum Error:ErrorType
case IOCTLFailed(Int32)
case StringIsNotAnASCIIString
public func interfaceAddress(forInterfaceWithName interfaceName: String) throws -> sockaddr_in
guard let cString = interfaceName.cStringUsingEncoding(NSASCIIStringEncoding) else
throw Error.StringIsNotAnASCIIString
let addressPtr = UnsafeMutablePointer<sockaddr>.alloc(1)
let ioctl_res = _interfaceAddressForName(strdup(cString), addressPtr)
let address = addressPtr.move()
addressPtr.dealloc(1)
if ioctl_res < 0
throw Error.IOCTLFailed(errno)
else
return unsafeBitCast(address, sockaddr_in.self)
然后你可以在你的代码中使用它:
let interfaceName = "en0"
do
let wlanInterfaceAddress = try interfaceAddress(forInterfaceWithName: interfaceName)
print(String.fromCString(inet_ntoa(wlanInterfaceAddress.sin_addr))!)
catch
if case Error.IOCTLFailed(let errno) = error where errno == ENXIO
print("interface(\(interfaceName)) is not available")
else
print(error)
en0
通常是你需要的接口,代表WLAN
如果您还需要知道可用的接口名称,您可以使用if_indextoname()
:
public func interfaceNames() -> [String]
let MAX_INTERFACES = 128;
var interfaceNames = [String]()
let interfaceNamePtr = UnsafeMutablePointer<Int8>.alloc(Int(IF_NAMESIZE))
for interfaceIndex in 1...MAX_INTERFACES
if (if_indextoname(UInt32(interfaceIndex), interfaceNamePtr) != nil)
if let interfaceName = String.fromCString(interfaceNamePtr)
interfaceNames.append(interfaceName)
else
break
interfaceNamePtr.dealloc(Int(IF_NAMESIZE))
return interfaceNames
【讨论】:
【参考方案3】: func getIfConfigOutput() -> [String:String]
let cmd = "for i in $(ifconfig -lu); do if ifconfig $i | grep -q \"status: active\" ; then echo $i; fi; done"
let interfaceString = shell(cmd)
let interfaceArray = interfaceString.components(separatedBy: "\n")
var finalDictionary:[String:String] = [String:String]()
for (i,_) in interfaceArray.enumerated()
if (interfaceArray[i].hasPrefix("en"))
let sp = shell("ifconfig \(interfaceArray[i]) | grep \"inet \" | grep -v 127.0.0.1 | cut -d\\ -f2")
finalDictionary[interfaceArray[i]] = sp.replacingOccurrences(of: "\n", with: "")
print(finalDictionary)
return finalDictionary
func shell(_ args: String) -> String
var outstr = ""
let task = Process()
task.launchPath = "/bin/sh"
task.arguments = ["-c", args]
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
if let output = NSString(data: data, encoding: String.Encoding.utf8.rawValue)
outstr = output as String
task.waitUntilExit()
return outstr
这将对您有所帮助。代码返回一个字典,其中包含与之关联的接口和 IP 地址。
【讨论】:
【参考方案4】:在 Swift 5 中,您可以在 Swift 中直接使用 ioctl,而不是借助“C”来获取给定接口的 IP 地址详细信息。
像这样:
private enum AddressRequestType
case ipAddress
case netmask
// From BDS ioccom.h
// Macro to create ioctl request
private static func _IOC (_ io: UInt32, _ group: UInt32, _ num: UInt32, _ len: UInt32) -> UInt32
let rv = io | (( len & UInt32(IOCPARM_MASK)) << 16) | ((group << 8) | num)
return rv
// Macro to create read/write IOrequest
private static func _IOWR (_ group: Character , _ num : UInt32, _ size: UInt32) -> UInt32
return _IOC(IOC_INOUT, UInt32 (group.asciiValue!), num, size)
private static func _interfaceAddressForName (_ name: String, _ requestType: AddressRequestType) throws -> String
var ifr = ifreq ()
ifr.ifr_ifru.ifru_addr.sa_family = sa_family_t(AF_INET)
// Copy the name into a zero padded 16 CChar buffer
let ifNameSize = Int (IFNAMSIZ)
var b = [CChar] (repeating: 0, count: ifNameSize)
strncpy (&b, name, ifNameSize)
// Convert the buffer to a 16 CChar tuple - that's what ifreq needs
ifr.ifr_name = (b [0], b [1], b [2], b [3], b [4], b [5], b [6], b [7], b [8], b [9], b [10], b [11], b [12], b [13], b [14], b [15])
let ioRequest: UInt32 =
switch requestType
case .ipAddress: return _IOWR("i", 33, UInt32(MemoryLayout<ifreq>.size)) // Magic number SIOCGIFADDR - see sockio.h
case .netmask: return _IOWR("i", 37, UInt32(MemoryLayout<ifreq>.size)) // Magic number SIOCGIFNETMASK
()
if ioctl(socket(AF_INET, SOCK_DGRAM, 0), UInt(ioRequest), &ifr) < 0
throw POSIXError (POSIXErrorCode (rawValue: errno) ?? POSIXErrorCode.EINVAL)
let sin = unsafeBitCast(ifr.ifr_ifru.ifru_addr, to: sockaddr_in.self)
let rv = String (cString: inet_ntoa (sin.sin_addr))
return rv
public static func getInterfaceIPAddress (interfaceName: String) throws -> String
return try _interfaceAddressForName(interfaceName, .ipAddress)
public static func getInterfaceNetMask (interfaceName: String) throws -> String
return try _interfaceAddressForName(interfaceName, .netmask)
【讨论】:
以上是关于如何快速获取IP地址的主要内容,如果未能解决你的问题,请参考以下文章