如何快速获取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地址的主要内容,如果未能解决你的问题,请参考以下文章

Delphi 7中快速获取本机IP地址

快速获取 IP 地址

如何使用CODESYS的OPCUA库获取IP地址

如何获取伪装ip下的真实ip地址

02收银中遇到IP地址冲突的处理方法

快速修改办公IP地址