Yes, you are correct, sorry for the confusing terminology.
I have a WiFi accessory that I connect to, this broadcasts UDP packets.
Several Apps on the device are interested in reading these UDP packets, users often want to run these apps side by side on their iPads (both running in foreground).
I am writing what is essentially a client that just consumes these UDP packets.
There are other clients running on the same device.
If those clients are allowed to run and connect first, they are able to consume the UDP packets and show the associated data. I am also able to consume the UDP data.
If I run and connect first, I am able to consume the UDP data, but the other clients are not able to consume the UDP packets.
If I don't run my app, but run two other clients, they seem to both be able to consume the UDP packets.
I have the following code fetching UDP data, it works as I would expect on my iPad apart from how it seems to block others. Also apologies some of the comments are out of date due to how I have tried various things to resolve the issue. But, I have confirmed the following code runs and fetches data.
/// Fetch the UDP socket address info for the supplied port.
var hints: addrinfo = addrinfo(
ai_flags: AI_PASSIVE,
ai_family: Int32(AF_INET),
ai_socktype: SOCK_DGRAM,
ai_protocol: 0,
ai_addrlen: 0,
ai_canonname: nil,
ai_addr: nil,
ai_next: nil
)
var servinfo: UnsafeMutablePointer<addrinfo>? = nil
let rv = getaddrinfo(nil, "\(4000)", &hints, &servinfo)
if(rv != 0) {
if let s = gai_strerror(rv) {
print("getaddreinfo error: %s\n", String(cString: s))
}
endWith(error: ClientError.CouldNotFetchAdresses)
return
}
//iterate through the returned addresses and try to connect to one.
var sockfd: Int32 = -1
var servinfoi: UnsafeMutablePointer<addrinfo>? = servinfo
while servinfoi != nil {
if let si = servinfo {
sockfd = socket(si.pointee.ai_family, si.pointee.ai_socktype, si.pointee.ai_protocol)
if sockfd == -1 {
print("listener: socket could not be created. iterate to next")
servinfoi = servinfoi?.pointee.ai_next
continue
}
//set the port to play nicely with other apps/sockets
var resuse: Int = 1
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &resuse, socklen_t(MemoryLayout<Int>.size))
setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &resuse, socklen_t(MemoryLayout<Int>.size))
let bound = bind(sockfd, si.pointee.ai_addr, si.pointee.ai_addrlen)
if bound == -1 {
close(sockfd)
print("listener: bound call could not be handled. iterate to next")
servinfoi = servinfoi?.pointee.ai_next
continue
}
//enable the nonblock flag on the socket so we can use poll to avoid locking up resources
var flags: Int32 = 0;
flags = fcntl(sockfd,F_GETFL,0);
_ = fcntl(sockfd, F_SETFL, flags | O_NONBLOCK)
break
}
}
if (servinfoi == nil) {
print("listener: failed to bind socket\n")
endWith(error: ClientError.CouldNotBindSocket)
return
}
freeaddrinfo(servinfo);
print("connected")
//as long as we can, we continue to read from the socket
while continueToFetchMessages {
//we use poll with a timeout of 500ms, so the loop will sleep here until the
//OS tells us there is more data, or the timeout happens. so on entry to background
//at worst the loop exits after 500ms.
var pollInfo: pollfd = pollfd(fd: sockfd, events: Int16(POLLIN), revents: 0)
let poll_count = poll(&pollInfo, 1, 1)
//if we had an error trying to poll, we want to stop the fetch loop which will close the socket.
if(poll_count == -1) {
close(sockfd)
endWith(error: ClientError.CouldNotPollSocket)
return
}
//if we have info read to read, we can do so.
//if this is false it probably means no data arrived in the allotted 500ms block
//so we just loop around and try again
if((pollInfo.revents & Int16(POLLIN)) != 0) {
var numbytes: Int = -1
var buffer: Data = .init(count: 256)
buffer.withUnsafeMutableBytes { mbp in
numbytes = recv(sockfd, mbp.baseAddress, 255, 0)
}
//it is apparently valid to read a zero byte UDP packet, so we want to check that we got something more than that before we handle
if numbytes > 0 {
let out = buffer.subdata(in: buffer.startIndex ..< buffer.startIndex.advanced(by: numbytes))
DispatchQueue.main.async {
let d = Message().messageType(data: out)
self.messageCallback(d, out)
}
}
}
}
//remember to close the socket when the loop bails
//this is what permits other apps to use the resource
close(sockfd)
Post
Replies
Boosts
Views
Activity
I think I've done this correctly, the following code has been inserted outside the while loop that creates the socket and binds.
if let info = servinfoi?.pointee.ai_addr {
var hbuf: [CChar] = Array(repeating: 0, count: Int(NI_MAXHOST))
var sbuf: [CChar] = Array(repeating: 0, count: Int(NI_MAXSERV))
let flag = NI_NUMERICHOST | NI_NUMERICSERV
let nameInfoResult = getnameinfo(info, socklen_t(info.pointee.sa_len), &hbuf, socklen_t(NI_MAXHOST), &sbuf, socklen_t(NI_MAXSERV), flag)
if nameInfoResult == 0 {
print("Host: \(String(cString: hbuf)), Serv: \(String(cString: sbuf))")
}
}
The result is
Host: 0.0.0.0, Serv: 4000
If you put your UDP listener code into two test apps and run them both simultaneously, do you see the problem
Yep, the problem still exists with the above code.
The first App to connect will receive the data as expected.
The second App will not get any error messages, it will bind to the 0.0.0.0 4000 address, but it will not get any data.
The poll call is never indicating there is anything to read (eg, pollInfo.revents is always 0 ).
Report filed at FB12200645 (Two Apps running in the foreground side by side cannot both read broadcast UDP packets from an external source at the same time via BSD sockets.)
I think I did that right, been a while.
I will open the direct tech support issue.
What is the process from here?