added high level sendTo and recvFrom to std/asyncnet (UDP functionality) (#14109)

* added high level sendTo and recvFrom to std/asyncnet; tests were also added.

* add .since annotation, a changelog entry and fixed to standard library style guide.

* Improved asserts msgs and added notes for use with UDP sockets
This commit is contained in:
rockcavera 2020-04-26 05:16:10 -03:00 • committed by GitHub
commit d23446c6ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 228 additions and 0 deletions

View file

@ -780,6 +780,133 @@ proc isClosed*(socket: AsyncSocket): bool =
## Determines whether the socket has been closed.
return socket.closed
proc sendTo*(socket: AsyncSocket, data: pointer, dataSize: int, address: string,
port: Port, flags = {SocketFlag.SafeDisconn}): owned(Future[void])
{.async, since: (1, 3).} =
## This proc sends ``data`` to the specified ``address``, which may be an IP
## address or a hostname. If a hostname is specified this function will try
## each IP of that hostname. The returned future will complete once all data
## has been sent.
##
## If an error occurs an OSError exception will be raised.
##
## This proc is normally used with connectionless sockets (UDP sockets).
assert(socket.protocol != IPPROTO_TCP,
"Cannot `sendTo` on a TCP socket. Use `send` instead.")
assert(not socket.closed, "Cannot `sendTo` on a closed socket.")
let
aiList = getAddrInfo(address, port, socket.domain, socket.sockType,
socket.protocol)
retFuture = newFuture[void]("sendTo")
var
it = aiList
success = false
lastException: ref Exception
while it != nil:
let fut = sendTo(socket.fd.AsyncFD, data, dataSize, it.ai_addr,
it.ai_addrlen.SockLen, flags)
yield fut
if not fut.failed:
success = true
break
lastException = fut.readError()
it = it.ai_next
freeaddrinfo(aiList)
if not success:
if lastException != nil:
retFuture.fail(lastException)
else:
retFuture.fail(newException(
IOError, "Couldn't resolve address: " & address))
else:
retFuture.complete()
proc sendTo*(socket: AsyncSocket, data, address: string, port: Port):
owned(Future[void]) {.async, since: (1, 3).} =
## This proc sends ``data`` to the specified ``address``, which may be an IP
## address or a hostname. If a hostname is specified this function will try
## each IP of that hostname. The returned future will complete once all data
## has been sent.
##
## If an error occurs an OSError exception will be raised.
##
## This proc is normally used with connectionless sockets (UDP sockets).
await sendTo(socket, cstring(data), len(data), address, port)
proc recvFrom*(socket: AsyncSocket, data: pointer, size: int,
address: FutureVar[string], port: FutureVar[Port],
flags = {SocketFlag.SafeDisconn}): owned(Future[int])
{.async, since: (1, 3).} =
## Receives a datagram data from ``socket`` into ``data``, which must be at
## least of size ``size``. The address and port of datagram's sender will be
## stored into ``address`` and ``port``, respectively. Returned future will
## complete once one datagram has been received, and will return size of
## packet received.
##
## If an error occurs an OSError exception will be raised.
##
## This proc is normally used with connectionless sockets (UDP sockets).
template awaitRecvFromInto() =
var lAddr = sizeof(sAddr).SockLen
result = await recvFromInto(AsyncFD(getFd(socket)), data, size,
cast[ptr SockAddr](addr sAddr), addr lAddr)
address.mget.add(getAddrString(cast[ptr SockAddr](addr sAddr)))
address.complete()
assert(socket.protocol != IPPROTO_TCP,
"Cannot `recvFrom` on a TCP socket. Use `recv` or `recvInto` instead.")
assert(not socket.closed, "Cannot `recvFrom` on a closed socket.")
var readSize: int
if socket.domain == AF_INET6:
var sAddr: Sockaddr_in6
awaitRecvFromInto()
port.complete(ntohs(sAddr.sin6_port).Port)
else:
var sAddr: Sockaddr_in
awaitRecvFromInto()
port.complete(ntohs(sAddr.sin_port).Port)
proc recvFrom*(socket: AsyncSocket, data: pointer, size: int):
owned(Future[tuple[size: int, address: string, port: Port]])
{.async, since: (1, 3).} =
## Receives a datagram data from ``socket`` into ``data``, which must be at
## least of size ``size``. Returned future will complete once one datagram has
## been received and will return tuple with: size of packet received; and
## address and port of datagram's sender.
##
## If an error occurs an OSError exception will be raised.
##
## This proc is normally used with connectionless sockets (UDP sockets).
var
fromIp = newFutureVar[string]()
fromPort = newFutureVar[Port]()
result.size = await recvFrom(socket, data, size, fromIp, fromPort)
result.address = fromIp.mget()
result.port = fromPort.mget()
when not defined(testing) and isMainModule:
type
TestCases = enum