快速将NSData转换为sockaddr结构 [英] Convert NSData to sockaddr struct in swift
问题描述
我正在尝试快速进行简单的DNS查找.到目前为止,这是我拥有的代码:
I'm trying to do a simple DNS lookup in swift. So far, here is the code that I have:
let hostRef = CFHostCreateWithName(kCFAllocatorDefault, "google.com").takeRetainedValue()
var resolved = CFHostStartInfoResolution(hostRef, CFHostInfoType.Addresses, nil)
let addresses = CFHostGetAddressing(hostRef, &resolved).takeRetainedValue() as NSArray
这时,地址" NSArray中的每个元素都是一个CFDataRef对象,该对象包装了一个sockaddr结构.
At this point, each element in the "addresses" NSArray is a CFDataRef object wrapping a sockaddr struct.
由于CFDataRef可以免费桥接到NSData,所以我可以像这样遍历它们:
Since CFDataRef can be toll-free bridged to NSData, I can loop through them like so:
for address: AnyObject in addresses {
println(address) // address is of type NSData.
}
到目前为止,一切都很好(我认为).当我在单元测试中运行时,这会打印出有效的外观数据.这是我被卡住的地方.为了我的一生,我不知道如何将NSData对象中的字节转换为sockaddr结构.
So far so good (I think). This prints out valid looking data when I run it in a unit test. Here is where I get stuck though. For the life of me, I can't figure out how to convert the bytes in the NSData object into a sockaddr struct.
如何将COpaquePointer类型的address.bytes转换为c结构?任何帮助表示赞赏.我把头撞在墙上,试图解决这个问题.
How can I convert address.bytes, which is of type COpaquePointer?, into a c struct? Any help appreciated. I'm banging my head against the wall trying to figure this out.
推荐答案
有关使用getnameinfo
的更简单解决方案,请参见此处的马丁回答:
For a simpler solution using getnameinfo
, see Martin's answer here: How can I get a real IP address from DNS query in Swift?
已针对Swift 5/IPv6更新:
CFHostGetAddressing
返回的对象可以作为数据桥接到Swift,并使用 withUnsafeBytes
in_addr/in6_addr
>和 assumingMemoryBound(to:)
.
The objects returned by CFHostGetAddressing
can be bridged to Swift as Data, and cast to in_addr
/in6_addr
by using withUnsafeBytes
and assumingMemoryBound(to:)
.
这是使用 inet_ntop
的完整示例将IPv4/IPv6地址转换为字符串:
Here's a complete example that uses inet_ntop
to convert IPv4/IPv6 addresses to strings:
import CFNetwork
import Foundation
protocol NetworkAddress {
static var family: Int32 { get }
static var maxStringLength: Int32 { get }
}
extension in_addr: NetworkAddress {
static let family = AF_INET
static let maxStringLength = INET_ADDRSTRLEN
}
extension in6_addr: NetworkAddress {
static let family = AF_INET6
static let maxStringLength = INET6_ADDRSTRLEN
}
extension String {
init<A: NetworkAddress>(address: A) {
// allocate a temporary buffer large enough to hold the string
var buf = ContiguousArray<Int8>(repeating: 0, count: Int(A.maxStringLength))
self = withUnsafePointer(to: address) { rawAddr in
buf.withUnsafeMutableBufferPointer {
String(cString: inet_ntop(A.family, rawAddr, $0.baseAddress, UInt32($0.count)))
}
}
}
}
func addressToString(data: Data) -> String? {
return data.withUnsafeBytes {
let family = $0.baseAddress!.assumingMemoryBound(to: sockaddr_storage.self).pointee.ss_family
// family determines which address type to cast to (IPv4 vs IPv6)
if family == numericCast(AF_INET) {
return String(address: $0.baseAddress!.assumingMemoryBound(to: sockaddr_in.self).pointee.sin_addr)
} else if family == numericCast(AF_INET6) {
return String(address: $0.baseAddress!.assumingMemoryBound(to: sockaddr_in6.self).pointee.sin6_addr)
}
return nil
}
}
let host = CFHostCreateWithName(kCFAllocatorDefault, "google.com" as CFString).takeRetainedValue()
var resolved = DarwinBoolean(CFHostStartInfoResolution(host, .addresses, nil))
let addresses = CFHostGetAddressing(host, &resolved)?.takeUnretainedValue() as! [Data]?
print(addresses?.compactMap(addressToString))
<罢工>
您可以使用NSData方法getBytes(_, length:)
方法,并使用前缀&
运算符将sockaddr结构传递给inout参数:
You can use the NSData method getBytes(_, length:)
method and pass the sockaddr struct to the inout parameter using the prefix &
operator:
var data: NSData ...
var address: sockaddr ...
data.getBytes(&address, length: MemoryLayout<sockaddr>.size)
已针对Swift 3更新:
let host = CFHostCreateWithName(kCFAllocatorDefault, "google.com" as CFString).takeRetainedValue()
var resolved = DarwinBoolean(CFHostStartInfoResolution(host, .addresses, nil))
let addresses = CFHostGetAddressing(host, &resolved)?.takeUnretainedValue() as! [NSData]?
if let data = addresses?.first {
var storage = sockaddr_storage()
data.getBytes(&storage, length: MemoryLayout<sockaddr_storage>.size)
if Int32(storage.ss_family) == AF_INET {
let addr4 = withUnsafePointer(to: &storage) {
$0.withMemoryRebound(to: sockaddr_in.self, capacity: 1) {
$0.pointee
}
}
// prints 74.125.239.132
print(String(cString: inet_ntoa(addr4.sin_addr), encoding: .ascii))
}
}
<罢工> 2015年6月3日更新: 现在,可以轻松地对C结构进行零初始化了,这变得更加简单:
Updated 6/3/2015:
Now that C structs can be easily zero-initialized, this becomes much simpler:
let host = CFHostCreateWithName(kCFAllocatorDefault, "google.com").takeRetainedValue()
var resolved = CFHostStartInfoResolution(host, .Addresses, nil)
let addresses = CFHostGetAddressing(host, &resolved)?.takeUnretainedValue() as! [NSData]?
if let data = addresses?.first {
var storage = sockaddr_storage()
data.getBytes(&storage, length: sizeof(sockaddr_storage))
if Int32(storage.ss_family) == AF_INET {
let addr4 = withUnsafePointer(&storage) { UnsafePointer<sockaddr_in>($0).memory }
// prints 74.125.239.132
println(String(CString: inet_ntoa(addr4.sin_addr), encoding: NSASCIIStringEncoding))
}
}
不幸的是,这要求先初始化sockaddr
.为了避免这种情况,您可以执行以下操作:
Unfortunately this requires sockaddr
to be initialized first. To avoid that, you could do something like this:
func makeWithUnsafePointer<T>(body: UnsafePointer<T> -> ()) -> T {
let ptr = UnsafePointer<T>.alloc(sizeof(T))
body(ptr)
return ptr.move()
}
let addr: sockaddr = makeWithUnsafePointer {
data.getBytes($0 as UnsafePointer<sockaddr>, length: sizeof(sockaddr))
}
或者这个:
func makeWithUninitialized<T>(body: inout T -> ()) -> T {
let ptr = UnsafePointer<T>.alloc(sizeof(T))
body(&ptr.memory)
return ptr.move()
}
let addr = makeWithUninitialized { (inout addr: sockaddr) in
data.getBytes(&addr, length: sizeof(sockaddr))
}
有关更多讨论,请参见 Swift:传递未初始化的C结构导入的C函数
For more discussion, see Swift: Pass Uninitialized C Structure to Imported C function
这篇关于快速将NSData转换为sockaddr结构的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!