Swift 获取地址信息
Swift getaddrinfo
POSIX getaddrinfo
分配稍后必须使用 freeaddrinfo
释放的内存。
参见 http://manpages.ubuntu.com/manpages/xenial/en/man3/getaddrinfo.3.html
为了简化 API,我创建了这个函数:
import Foundation
enum SystemError: Swift.Error {
case getaddrinfo(Int32, Int32?)
}
public func getaddrinfo(node: String?, service: String?, hints: addrinfo?) throws -> [addrinfo] {
var err: Int32
var res: UnsafeMutablePointer<addrinfo>?
if var hints = hints {
err = getaddrinfo(node, service, &hints, &res)
} else {
err = getaddrinfo(node, service, nil, &res)
}
if err == EAI_SYSTEM {
throw SystemError.getaddrinfo(err, errno)
}
if err != 0 {
throw SystemError.getaddrinfo(err, nil)
}
defer {
freeaddrinfo(res)
}
var result = [addrinfo]()
var ai = res?.pointee
while ai != nil {
result.append(ai!)
ai = ai!.ai_next?.pointee
}
return result
}
不过我觉得函数不正确。
- Swift 内存模型如何知道
getaddrinfo
分配了内存,并且 Swift 不应该用自己的东西覆盖该内存?
- Swift怎么知道
freeaddrinfo
删除了整个列表,应该把已经赋给result数组的ai信息复制出来?
与 getaddrinfo
交互的正确方法是什么?
getaddrinfo
分配的内存(例如malloc
)不会分配给任何其他人
同一 运行 进程中的动态内存分配函数
直到 freeaddrinfo
释放(例如 free
)。
因此 Swift 运行时不会践踏该内存(如果我们
假设它没有编程错误,例如错误的指针计算)。
另外struct addrinfo
是值类型,所以
result.append(ai!)
会将指向的结构的副本附加到数组。
但是还是有问题 struct addrinfo
的部分成员
是指针
public var ai_canonname: UnsafeMutablePointer<Int8>! /* canonical name for hostname */
public var ai_addr: UnsafeMutablePointer<sockaddr>! /* binary address */
可能指向 getaddrinfo
分配的内存,因此
在 freeaddrinfo
之后无效,并在您的之后取消引用它们
函数 returns 导致未定义的行为。
因此您必须将 freeaddrinfo
推迟到
不再需要地址列表,或复制信息。
这有点麻烦,因为 ai_addr
可能指向一个
具有不同长度的 IPv4 或 IPv6 套接字地址结构。
以下代码演示了如何复制地址列表
到 sockaddr_storage
结构的数组(足够大
持有任何IP地址)。此代码尚未经过彻底测试,
请谨慎使用。
public func getaddrinfo(node: String, service: String, hints: addrinfo?) throws -> [sockaddr_storage] {
var err: Int32
var res: UnsafeMutablePointer<addrinfo>?
if var hints = hints {
err = getaddrinfo(node, service, &hints, &res)
} else {
err = getaddrinfo(node, service, nil, &res)
}
if err == EAI_SYSTEM {
throw SystemError.getaddrinfo(err, errno)
}
if err != 0 {
throw SystemError.getaddrinfo(err, nil)
}
defer {
freeaddrinfo(res)
}
guard let firstAddr = res else {
return []
}
var result = [sockaddr_storage]()
for addr in sequence(first: firstAddr, next: { [=12=].pointee.ai_next }) {
var sockAddr = sockaddr_storage()
memcpy(&sockAddr, addr.pointee.ai_addr, Int(addr.pointee.ai_addrlen))
result.append(sockAddr)
}
return result
}
备注:
如果您只对单个地址族感兴趣,那么
您可以将 sockaddr_storage
替换为 sockaddr_in
或
sockaddr_in6
.
我已将 node
和 service
参数设置为非可选参数。
原因是Swift目前传多了有Bug
比 C 函数的一个可选字符串,请参阅
Why does Swift return an unexpected pointer when converting an optional String into an UnsafePointer?.
这里使用sequence()
来遍历链表,而不是
while
循环。
POSIX getaddrinfo
分配稍后必须使用 freeaddrinfo
释放的内存。
参见 http://manpages.ubuntu.com/manpages/xenial/en/man3/getaddrinfo.3.html
为了简化 API,我创建了这个函数:
import Foundation
enum SystemError: Swift.Error {
case getaddrinfo(Int32, Int32?)
}
public func getaddrinfo(node: String?, service: String?, hints: addrinfo?) throws -> [addrinfo] {
var err: Int32
var res: UnsafeMutablePointer<addrinfo>?
if var hints = hints {
err = getaddrinfo(node, service, &hints, &res)
} else {
err = getaddrinfo(node, service, nil, &res)
}
if err == EAI_SYSTEM {
throw SystemError.getaddrinfo(err, errno)
}
if err != 0 {
throw SystemError.getaddrinfo(err, nil)
}
defer {
freeaddrinfo(res)
}
var result = [addrinfo]()
var ai = res?.pointee
while ai != nil {
result.append(ai!)
ai = ai!.ai_next?.pointee
}
return result
}
不过我觉得函数不正确。
- Swift 内存模型如何知道
getaddrinfo
分配了内存,并且 Swift 不应该用自己的东西覆盖该内存? - Swift怎么知道
freeaddrinfo
删除了整个列表,应该把已经赋给result数组的ai信息复制出来?
与 getaddrinfo
交互的正确方法是什么?
getaddrinfo
分配的内存(例如malloc
)不会分配给任何其他人
同一 运行 进程中的动态内存分配函数
直到 freeaddrinfo
释放(例如 free
)。
因此 Swift 运行时不会践踏该内存(如果我们
假设它没有编程错误,例如错误的指针计算)。
另外struct addrinfo
是值类型,所以
result.append(ai!)
会将指向的结构的副本附加到数组。
但是还是有问题 struct addrinfo
的部分成员
是指针
public var ai_canonname: UnsafeMutablePointer<Int8>! /* canonical name for hostname */
public var ai_addr: UnsafeMutablePointer<sockaddr>! /* binary address */
可能指向 getaddrinfo
分配的内存,因此
在 freeaddrinfo
之后无效,并在您的之后取消引用它们
函数 returns 导致未定义的行为。
因此您必须将 freeaddrinfo
推迟到
不再需要地址列表,或复制信息。
这有点麻烦,因为 ai_addr
可能指向一个
具有不同长度的 IPv4 或 IPv6 套接字地址结构。
以下代码演示了如何复制地址列表
到 sockaddr_storage
结构的数组(足够大
持有任何IP地址)。此代码尚未经过彻底测试,
请谨慎使用。
public func getaddrinfo(node: String, service: String, hints: addrinfo?) throws -> [sockaddr_storage] {
var err: Int32
var res: UnsafeMutablePointer<addrinfo>?
if var hints = hints {
err = getaddrinfo(node, service, &hints, &res)
} else {
err = getaddrinfo(node, service, nil, &res)
}
if err == EAI_SYSTEM {
throw SystemError.getaddrinfo(err, errno)
}
if err != 0 {
throw SystemError.getaddrinfo(err, nil)
}
defer {
freeaddrinfo(res)
}
guard let firstAddr = res else {
return []
}
var result = [sockaddr_storage]()
for addr in sequence(first: firstAddr, next: { [=12=].pointee.ai_next }) {
var sockAddr = sockaddr_storage()
memcpy(&sockAddr, addr.pointee.ai_addr, Int(addr.pointee.ai_addrlen))
result.append(sockAddr)
}
return result
}
备注:
如果您只对单个地址族感兴趣,那么 您可以将
sockaddr_storage
替换为sockaddr_in
或sockaddr_in6
.我已将
node
和service
参数设置为非可选参数。 原因是Swift目前传多了有Bug 比 C 函数的一个可选字符串,请参阅 Why does Swift return an unexpected pointer when converting an optional String into an UnsafePointer?.
这里使用sequence()
来遍历链表,而不是while
循环。