I have been playing around with the various networking APIs Windows has to offer and now I have advanced to experimenting with I/O Completion Ports (IOCP). There is a peculiar, I’d say “pointer technique”, that is apparently quite common when dealing with IOCP. However, I’m not sure whether it is totally sound in Swift, due to its lack of guarantees around struct layout etc.
Currently, I’m trying different ways of writing a TCP server. In the IOCP world, you submit I/O requests to an I/O completion port and then wait for any of the requests to finish processing their results. In my case, you first create an ordinary TCP socket, bind it to an address and call listen on it. Then you construct the central I/O completion port and dispatch an accept request. After that, you wait for I/O completions through GetQueuedCompletionStatus. Something along the lines of
import WinSDK
// Create socket
let listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP.rawValue)
bind(listenSocket, addr, addrLen)
listen(listenSocket, backlog)
// Create IO completion port
let iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nil, 0, 1)
// Create IO completion port for the listening socket
CreateIoCompletionPort(HANDLE(bitPattern: UInt(listenSocket)), iocp, 0, 0)
// Submit initial accept
let newSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP.rawValue)
let buffer: UnsafeRawBufferPointer = .allocate(capacity: 1024, alignment: 1)
let overlappedPointer: UnsafeMutablePointer<OVERLAPPED> = .allocate(capacity: 1)
overlappedPointer.initialize(to: .init())
// acceptExPtr is a function pointer obtained through a call to WSAIoctl
acceptExPtr(
listenSocket,
newSocket,
buffer,
0,
DWORD(MemoryLayout<sockaddr_in>.size + 16),
DWORD(MemoryLayout<sockaddr_in>.size + 16),
nil,
overlappedPointer
)
while true {
var transferred: DWORD = 0
var key: ULONG_PTR = 0
var overlapped: UnsafeMutablePointer<OVERLAPPED>? = nil
GetQueuedCompletionStatus(iocp, &transferred, &key, &overlapped, INFINITE)
// Process ...
}
It is commonly through the UnsafeMutablePointer<OVERLAPPED> that you identify and process the various I/O completions (you can also use the key to identify requests). When the call to GetQueuedCompletionStatus completes, it is guaranteed that the UnsafeMutablePointer<OVERLAPPED> is the same that was originally passed.
To carry a more convenient kind of context with each request, you can do the following. In my case, I define the struct
enum OperationType: UInt32 {
case accept
case recv
case send
}
struct Context: ~Copyable {
var overlapped: OVERLAPPED = OVERLAPPED()
var wsaBuf: WSABUF = WSABUF()
let buffer: UnsafeMutableRawBufferPointer = .allocate(byteCount: 1024, alignment: 1)
var operation: OperationType
var socket: SOCKET
init(operation: OperationType, socket: SOCKET) {
self.operation = operation
self.socket = socket
}
deinit { buffer.deallocate() }
}
The initial accept request is then modified
let newSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP.rawValue)
let acceptContext: UnsafeMutablePointer<Context> = .allocate(capacity: 1)
acceptContext.initialize(to: .init(operation: .accept, socket: newSocket))
let overlappedPointer: UnsafeMutablePointer<OVERLAPPED> = .init(OpaquePointer(acceptContext))
let _ = acceptExPtr(
listenSocket,
newSocket,
acceptContext.pointee.buffer.baseAddress,
0,
DWORD(MemoryLayout<sockaddr_in>.size + 16),
DWORD(MemoryLayout<sockaddr_in>.size + 16),
nil,
overlappedPointer
)
while true {
var transferred: DWORD = 0
var key: ULONG_PTR = 0
var overlapped: UnsafeMutablePointer<OVERLAPPED>? = nil
GetQueuedCompletionStatus(iocp, &transferred, &key, &overlapped, INFINITE)
let context: UnsafeMutablePointer<Context> = .init(OpaquePointer(overlapped!))
switch context.pointee.operation {
case .accept:
// ...
case .recv:
// ...
case .send:
// ...
}
}
and currently it works like a charm.
When I first came across this trick (while looking at C examples), I was initially very confused as to why these casts would be legal in any world
// In C
// OVERLAPPED* overlappedPointer = (OVERLAPPED*)acceptContext;
let acceptContext: UnsafeMutablePointer<Context>
let overlappedPointer: UnsafeMutablePointer<OVERLAPPED> = .init(OpaquePointer(acceptContext))
// In C
// Context* context = (Context*)overlapped;
var overlapped: UnsafeMutablePointer<OVERLAPPED>? = nil
GetQueuedCompletionStatus(iocp, &transferred, &key, &overlapped, INFINITE)
let context: UnsafeMutablePointer<Context> = .init(OpaquePointer(overlapped!))
The reason, at least in C, is that since Context.overlapped is the first member of the struct, the address of that member is exactly the same as the address of the struct itself. My question is whether this is legal in the Swift world. I have read many times that Swift structs do not have a guaranteed layout, but I'm not entirely sure to what extent. In this case it would be useful to be able to assume that the memory address of the first member in a struct would be the same as the struct’s address. Of course one work around would be to define my struct in C and import that (or use the key parameter above). But it is extremely convenient to define a non-copyable struct with all the necessary data for these asynchronous calls, pass it around and clean up everything on deinit.