Closed ta0kira closed 2 years ago
There should probably be some sort of SocketVisitor
(etc.) to allow other categories to operate on the file descriptor.
@value interface SocketView {
descriptor () -> (Int)
}
@value interface SocketVisitor {
visitSocket ([SocketView&StreamSocket]) -> (#self)
}
@value interface StreamSocket {
visit<#x>
#x requires SocketVisitor
(#x) -> (#x)
}
// ...
// some arbitrary socket implementation
define SomeSocket {
refines SocketView
@value Int fd
visit (visitor) {
return visitor.visitSocket(self)
}
}
// ...
// uses the select system call to wait for input
define SocketSelect {
refines SocketVisitor
@value SearchTree<Int,StreamSocket> sockets
add (socket) {
return socket.visit<?>(self)
}
visitSocket (view) {
\ sockets.set(view.descriptor(),view)
return self
}
}
// ...
SocketSelect select <- SocketSelect.new().add(socket1).add(socket2)
ReadAt<StreamSocket> ready <- select.wait()
There might also need to be a @type interface
visitor, e.g., for the dup2
system call, which modifies global state rather than creating/changing a specific object.
Not sure if this is actually all that useful, since most socket use-cases are complex and have an existing C++ library that can be wrapped. For example, I wouldn't want to implement HTTP(S) in Zeolite; I'd just wrap cpp-httplib
or something.
The POSIX socket API is also quite broad, so I think it would be too much effort to expose/test all of it. It might be useful to support AF_LOCAL
sockets and (un)named pipes however, since you don't need to deal with addresses and ports.
I started working on pipe/exec support in https://github.com/ta0kira/zeolite-async. I'm not sure if I'll end up moving that to this repo.
fork
support, anyway) or UDP yet.WriteAt
should be the destination for reads, which would require making a copy of the data. The alternative is to dynamically allocate a new buffer each read.dup
descriptor so that read and write block independently.listen
semantics.