use @pony_os_accept[I32](event: AsioEventID)
use @pony_os_listen_tcp[AsioEventID](owner: AsioEventNotify, host: Pointer[U8] tag,
service: Pointer[U8] tag)
use @pony_os_listen_tcp4[AsioEventID](owner: AsioEventNotify, host: Pointer[U8] tag,
service: Pointer[U8] tag)
use @pony_os_listen_tcp6[AsioEventID](owner: AsioEventNotify, host: Pointer[U8] tag,
service: Pointer[U8] tag)
actor TCPListener is AsioEventNotify
"""
Listens for new network connections.
The following program creates an echo server that listens for
connections on port 8989 and echoes back any data it receives.
```pony
use "net"
class MyTCPConnectionNotify is TCPConnectionNotify
fun ref received(
conn: TCPConnection ref,
data: Array[U8] iso,
times: USize)
: Bool
=>
conn.write(String.from_array(consume data))
true
fun ref connect_failed(conn: TCPConnection ref) =>
None
class MyTCPListenNotify is TCPListenNotify
fun ref connected(listen: TCPListener ref): TCPConnectionNotify iso^ =>
MyTCPConnectionNotify
fun ref not_listening(listen: TCPListener ref) =>
None
actor Main
new create(env: Env) =>
TCPListener(TCPListenAuth(env.root),
recover MyTCPListenNotify end, "", "8989")
```
"""
var _notify: TCPListenNotify
var _fd: U32
var _event: AsioEventID = AsioEvent.none()
var _closed: Bool = false
let _limit: USize
var _count: USize = 0
var _paused: Bool = false
let _read_buffer_size: USize
let _yield_after_reading: USize
let _yield_after_writing: USize
new create(
auth: TCPListenAuth,
notify: TCPListenNotify iso,
host: String = "",
service: String = "0",
limit: USize = 0,
read_buffer_size: USize = 16384,
yield_after_reading: USize = 16384,
yield_after_writing: USize = 16384)
=>
"""
Listens for both IPv4 and IPv6 connections.
"""
_limit = limit
_notify = consume notify
_event =
@pony_os_listen_tcp(this, host.cstring(), service.cstring())
_read_buffer_size = read_buffer_size
_yield_after_reading = yield_after_reading
_yield_after_writing = yield_after_writing
_fd = @pony_asio_event_fd(_event)
_notify_listening()
new ip4(
auth: TCPListenAuth,
notify: TCPListenNotify iso,
host: String = "",
service: String = "0",
limit: USize = 0,
read_buffer_size: USize = 16384,
yield_after_reading: USize = 16384,
yield_after_writing: USize = 16384)
=>
"""
Listens for IPv4 connections.
"""
_limit = limit
_notify = consume notify
_event =
@pony_os_listen_tcp4(this, host.cstring(), service.cstring())
_read_buffer_size = read_buffer_size
_yield_after_reading = yield_after_reading
_yield_after_writing = yield_after_writing
_fd = @pony_asio_event_fd(_event)
_notify_listening()
new ip6(
auth: TCPListenAuth,
notify: TCPListenNotify iso,
host: String = "",
service: String = "0",
limit: USize = 0,
read_buffer_size: USize = 16384,
yield_after_reading: USize = 16384,
yield_after_writing: USize = 16384)
=>
"""
Listens for IPv6 connections.
"""
_limit = limit
_notify = consume notify
_event =
@pony_os_listen_tcp6(this, host.cstring(), service.cstring())
_read_buffer_size = read_buffer_size
_yield_after_reading = yield_after_reading
_yield_after_writing = yield_after_writing
_fd = @pony_asio_event_fd(_event)
_notify_listening()
be set_notify(notify: TCPListenNotify iso) =>
"""
Change the notifier.
"""
_notify = consume notify
be dispose() =>
"""
Stop listening.
"""
close()
fun local_address(): NetAddress =>
"""
Return the bound IP address.
"""
let ip = recover NetAddress end
@pony_os_sockname(_fd, ip)
ip
be _event_notify(event: AsioEventID, flags: U32, arg: U32) =>
"""
When we are readable, we accept new connections until none remain.
"""
if event isnt _event then
return
end
if AsioEvent.errored(flags) then
close()
return
end
if AsioEvent.readable(flags) then
_accept()
end
if AsioEvent.disposable(flags) then
@pony_asio_event_destroy(_event)
_event = AsioEvent.none()
end
be _conn_closed() =>
"""
An accepted connection has closed. If we have dropped below the limit, try
to accept new connections.
"""
_count = _count - 1
if _paused and (_count < _limit) then
_paused = false
_accept()
end
fun ref _accept() =>
"""
Accept connections as long as we have spawned fewer than our limit.
"""
if _closed then
return
end
while (_limit == 0) or (_count < _limit) do
var fd = @pony_os_accept(_event)
match fd
| -1 =>
// Something other than EWOULDBLOCK (a failed accept). Bail out; the
// ASIO event will re-notify when the socket is readable again.
return
| 0 =>
// EWOULDBLOCK, don't try again.
return
else
_spawn(fd.u32())
end
end
_paused = true
fun ref _spawn(ns: U32) =>
"""
Spawn a new connection.
"""
try
TCPConnection._accept(this, _notify.connected(this)?, ns,
_read_buffer_size, _yield_after_reading, _yield_after_writing)
_count = _count + 1
else
@pony_os_socket_close(ns)
end
fun ref _notify_listening() =>
"""
Inform the notifier that we're listening.
"""
if not _event.is_null() then
_notify.listening(this)
else
_closed = true
_notify.not_listening(this)
end
fun ref close() =>
"""
Dispose of resources.
"""
if _closed then
return
end
_closed = true
if not _event.is_null() then
@pony_asio_event_unsubscribe(_event)
// POSIX closes the listener fd here; on Windows the readiness backend
// owns the close (when it sees the deferred REMOVE from the unsubscribe
// above). The accepted/rejected fds in _accept/_spawn are raw
// (unsubscribed), so those closes stay cross-platform.
ifdef not windows then
@pony_os_socket_close(_fd)
end
_fd = -1
_notify.closed(this)
end