patternswiftMinor
Pure Swift solution for socket programming
Viewed 0 times
swiftpureprogrammingforsolutionsocket
Problem
I have spend quite some time now to find out how exactly to do socket programming in Swift (Xcode 6.1). There are a few examples out there, but most of them no longer work for the latest release of Swift.
Finally I came up with the following (see below). That seems to work, but I am not 100% certain that it is fully correct. Maybe some of you can have a look at it and see if I made any errors that might cause future (i.e. currently under the hood) problems.
The calls to "log...." refer to a self made logging facility, but that should be clear enough I think.
Any suggestions on how to improve this are welcome.
```
var status: Int32 = 0
// Protocol configuration
var hints = addrinfo(
ai_flags: AI_PASSIVE, // Assign the address of my local host to the socket structures
ai_family: AF_UNSPEC, // Either IPv4 or IPv6
ai_socktype: SOCK_STREAM, // TCP
ai_protocol: 0,
ai_addrlen: 0,
ai_canonname: UnsafeMutablePointer.null(),
ai_addr: UnsafeMutablePointer.null(),
ai_next: UnsafeMutablePointer.null())
// For the result from the getaddrinfo
var servinfo = UnsafeMutablePointer.null()
// Get the info we need to create our socket descriptor
status = getaddrinfo(UnsafePointer.null(), ap_servicePortNumber, &hints, &servinfo)
// Cop out if there is an error
if status != 0 { return }
// Print a list of the found IP addresses
if ap_applicationInDebugMode {
var info = servinfo
while info != UnsafeMutablePointer.null() {
// Check for IPv4
if info.memory.ai_family == AF_INET {
var ipAddressString = CChar, repeatedValue: 0)
inet_ntop(
info.memory.ai_family,
info.memory.ai_addr,
&ipAddressString,
socklen_t(INET_ADDRSTRLEN))
let addressStr = String.fromCString(&ipAddressString)
var message = addressStr == nil ? "No IPv4 address found" : "IPv4 address: " + addressStr!
Finally I came up with the following (see below). That seems to work, but I am not 100% certain that it is fully correct. Maybe some of you can have a look at it and see if I made any errors that might cause future (i.e. currently under the hood) problems.
The calls to "log...." refer to a self made logging facility, but that should be clear enough I think.
Any suggestions on how to improve this are welcome.
```
var status: Int32 = 0
// Protocol configuration
var hints = addrinfo(
ai_flags: AI_PASSIVE, // Assign the address of my local host to the socket structures
ai_family: AF_UNSPEC, // Either IPv4 or IPv6
ai_socktype: SOCK_STREAM, // TCP
ai_protocol: 0,
ai_addrlen: 0,
ai_canonname: UnsafeMutablePointer.null(),
ai_addr: UnsafeMutablePointer.null(),
ai_next: UnsafeMutablePointer.null())
// For the result from the getaddrinfo
var servinfo = UnsafeMutablePointer.null()
// Get the info we need to create our socket descriptor
status = getaddrinfo(UnsafePointer.null(), ap_servicePortNumber, &hints, &servinfo)
// Cop out if there is an error
if status != 0 { return }
// Print a list of the found IP addresses
if ap_applicationInDebugMode {
var info = servinfo
while info != UnsafeMutablePointer.null() {
// Check for IPv4
if info.memory.ai_family == AF_INET {
var ipAddressString = CChar, repeatedValue: 0)
inet_ntop(
info.memory.ai_family,
info.memory.ai_addr,
&ipAddressString,
socklen_t(INET_ADDRSTRLEN))
let addressStr = String.fromCString(&ipAddressString)
var message = addressStr == nil ? "No IPv4 address found" : "IPv4 address: " + addressStr!
Solution
(Remark: I am using println() for diagnostic output in this review instead of your
log.atLevelDebug(). The reason is that I wanted to test the code before posting
an answer.)
That seems to be working code (with one exception, see below), but there are some things that can be simplified
or improved.
initialization, for example:
You traverse the address list from
Writing this as a for-loop might be easier to read:
Your printing of the numerical IP addresses with
the second argument must be the address of a
and not the address of
But the more modern interface is
addresses). Written as a separate utility function:
which you could use as
Instead of
The error message string would be more instructive than a error number. For example:
Your function prints all found IP addresses in debug mode, but binds the socket
only to the first one. The recommended practice (if I remember correctly, and I will
try to find a reference) is to
is successful. In the first case, your main loop would look like
and then
listening.
The more Swifty way however would be to define an optional
which is set to a valid socket descriptor or stays
log.atLevelDebug(). The reason is that I wanted to test the code before posting
an answer.)
That seems to be working code (with one exception, see below), but there are some things that can be simplified
or improved.
nil is a possible value for any UnsafeMutablePointer, so you can simplify yourinitialization, for example:
var hints = addrinfo(
ai_flags: AI_PASSIVE, // Assign the address of my local host to the socket structures
ai_family: AF_UNSPEC, // Either IPv4 or IPv6
ai_socktype: SOCK_STREAM, // TCP
ai_protocol: 0,
ai_addrlen: 0,
ai_canonname: nil,
ai_addr: nil,
ai_next: nil)You traverse the address list from
getaddrinfo withvar info = servinfo
while info != UnsafeMutablePointer.null() {
// ...
info = info.memory.ai_next
}Writing this as a for-loop might be easier to read:
for (var info = servinfo; info != nil; info = info.memory.ai_next) {
// ...
}Your printing of the numerical IP addresses with
inet_ntop() does not work correctly,the second argument must be the address of a
struct in_addr/in6_addrand not the address of
struct sockaddr, for example:var sin4addr = UnsafePointer(info.memory.ai_addr).memory.sin_addr
var ipAddressString = [CChar](count:Int(INET_ADDRSTRLEN), repeatedValue: 0)
inet_ntop(
info.memory.ai_family,
&sin4addr,
&ipAddressString,
socklen_t(INET_ADDRSTRLEN))
let addressStr = String.fromCString(&ipAddressString)But the more modern interface is
getnameinfo() (which works for both IPv4 and IPv6addresses). Written as a separate utility function:
func sockaddrDescription(addr: UnsafePointer) -> String
{
var host : String?
var port : String?
var hostBuffer = [CChar](count: Int(NI_MAXHOST), repeatedValue: 0)
var servBuffer = [CChar](count: Int(NI_MAXSERV), repeatedValue: 0)
if (getnameinfo(addr, socklen_t(addr.memory.sa_len),
&hostBuffer, socklen_t(hostBuffer.count),
&servBuffer, socklen_t(servBuffer.count),
NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
host = String.fromCString(hostBuffer)
port = String.fromCString(servBuffer)
}
return "host: " + (host ?? "?") + " port: " + (port ?? "?")
}which you could use as
for (var info = servinfo; info != nil; info = info.memory.ai_next) {
let addrInfo = info.memory
let desc = sockaddrDescription(addrInfo.ai_addr)
println(desc)
}Instead of
errno you could log strerror(errno) if any of the socket functions fails.The error message string would be more instructive than a error number. For example:
status = bind(...)
if status == -1 {
println("bind failed: " + String(UTF8String: strerror(errno))!)
}Your function prints all found IP addresses in debug mode, but binds the socket
only to the first one. The recommended practice (if I remember correctly, and I will
try to find a reference) is to
- try all IP addresses until socket/bind/listen is successful, or
- try to listen on all IP addresses.
is successful. In the first case, your main loop would look like
var socketDescriptor : Int32 = -1 // Invalid socket
for (var info = servinfo; info != nil; info = info.memory.ai_next) {
let addrInfo = info.memory
let sd = socket(addrInfo.ai_family, addrInfo.ai_socktype, addrInfo.ai_protocol)
if sd == -1 {
println("socket failed: " + String(UTF8String: strerror(errno))!)
} else {
status = bind(sd, addrInfo.ai_addr, addrInfo.ai_addrlen)
if status == -1 {
println("bind failed: " + String(UTF8String: strerror(errno))!)
} else {
status = listen(sd, 1)
if status == -1 {
println("listen failed: " + String(UTF8String: strerror(errno))!)
} else {
// SUCCESS !!
socketDescriptor = sd
break
}
}
close(sd)
}
}and then
socketDescriptor is -1 or a valid socket descriptor which is bound andlistening.
The more Swifty way however would be to define an optional
var socketDescriptor : Int32?which is set to a valid socket descriptor or stays
nil in the error case.Code Snippets
var hints = addrinfo(
ai_flags: AI_PASSIVE, // Assign the address of my local host to the socket structures
ai_family: AF_UNSPEC, // Either IPv4 or IPv6
ai_socktype: SOCK_STREAM, // TCP
ai_protocol: 0,
ai_addrlen: 0,
ai_canonname: nil,
ai_addr: nil,
ai_next: nil)var info = servinfo
while info != UnsafeMutablePointer<addrinfo>.null() {
// ...
info = info.memory.ai_next
}for (var info = servinfo; info != nil; info = info.memory.ai_next) {
// ...
}var sin4addr = UnsafePointer<sockaddr_in>(info.memory.ai_addr).memory.sin_addr
var ipAddressString = [CChar](count:Int(INET_ADDRSTRLEN), repeatedValue: 0)
inet_ntop(
info.memory.ai_family,
&sin4addr,
&ipAddressString,
socklen_t(INET_ADDRSTRLEN))
let addressStr = String.fromCString(&ipAddressString)func sockaddrDescription(addr: UnsafePointer<sockaddr>) -> String
{
var host : String?
var port : String?
var hostBuffer = [CChar](count: Int(NI_MAXHOST), repeatedValue: 0)
var servBuffer = [CChar](count: Int(NI_MAXSERV), repeatedValue: 0)
if (getnameinfo(addr, socklen_t(addr.memory.sa_len),
&hostBuffer, socklen_t(hostBuffer.count),
&servBuffer, socklen_t(servBuffer.count),
NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
host = String.fromCString(hostBuffer)
port = String.fromCString(servBuffer)
}
return "host: " + (host ?? "?") + " port: " + (port ?? "?")
}Context
StackExchange Code Review Q#71861, answer score: 8
Revisions (0)
No revisions yet.