【问题标题】:How to get Ip address in swift如何快速获取IP地址
【发布时间】:2023-04-09 21:38:01
【问题描述】:

我如何获得我的本地 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 粘合层可能会更容易,因为需要指针算术和结构解包。

标签: swift macos networking ip


【解决方案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)
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-26
        • 1970-01-01
        • 2014-12-30
        • 2021-01-28
        • 2010-11-04
        相关资源
        最近更新 更多