Fixes socket problems on Windows and normalises some names.

Ref #2976. Ref #2003. See news.txt for details.
This commit is contained in:
Dominik Picheta
2015-06-29 20:11:21 +01:00
parent c16d153ff5
commit 615defb1a9
5 changed files with 78 additions and 55 deletions

View File

@@ -483,7 +483,7 @@ when defined(windows) or defined(nimdoc):
RemoteSockaddr, RemoteSockaddrLength)
proc connect*(socket: AsyncFD, address: string, port: Port,
af = rawsockets.AF_INET): Future[void] =
domain = rawsockets.AF_INET): Future[void] =
## Connects ``socket`` to server at ``address:port``.
##
## Returns a ``Future`` which will complete when the connection succeeds
@@ -492,14 +492,14 @@ when defined(windows) or defined(nimdoc):
var retFuture = newFuture[void]("connect")
# Apparently ``ConnectEx`` expects the socket to be initially bound:
var saddr: Sockaddr_in
saddr.sin_family = int16(toInt(af))
saddr.sin_family = int16(toInt(domain))
saddr.sin_port = 0
saddr.sin_addr.s_addr = INADDR_ANY
if bindAddr(socket.SocketHandle, cast[ptr SockAddr](addr(saddr)),
sizeof(saddr).SockLen) < 0'i32:
raiseOSError(osLastError())
var aiList = getAddrInfo(address, port, af)
var aiList = getAddrInfo(address, port, domain)
var success = false
var lastError: OSErrorCode
var it = aiList
@@ -855,17 +855,17 @@ when defined(windows) or defined(nimdoc):
return retFuture
proc newAsyncRawSocket*(domain, typ, protocol: cint): AsyncFD =
proc newAsyncRawSocket*(domain, sockType, protocol: cint): AsyncFD =
## Creates a new socket and registers it with the dispatcher implicitly.
result = newRawSocket(domain, typ, protocol).AsyncFD
result = newRawSocket(domain, sockType, protocol).AsyncFD
result.SocketHandle.setBlocking(false)
register(result)
proc newAsyncRawSocket*(domain: Domain = rawsockets.AF_INET,
typ: SockType = SOCK_STREAM,
sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP): AsyncFD =
## Creates a new socket and registers it with the dispatcher implicitly.
result = newRawSocket(domain, typ, protocol).AsyncFD
result = newRawSocket(domain, sockType, protocol).AsyncFD
result.SocketHandle.setBlocking(false)
register(result)
@@ -928,17 +928,18 @@ else:
var data = PData(fd: fd, readCBs: @[], writeCBs: @[])
p.selector.register(fd.SocketHandle, {}, data.RootRef)
proc newAsyncRawSocket*(domain: cint, typ: cint, protocol: cint): AsyncFD =
result = newRawSocket(domain, typ, protocol).AsyncFD
proc newAsyncRawSocket*(domain: cint, sockType: cint,
protocol: cint): AsyncFD =
result = newRawSocket(domain, sockType, protocol).AsyncFD
result.SocketHandle.setBlocking(false)
when defined(macosx):
result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1)
register(result)
proc newAsyncRawSocket*(domain: Domain = AF_INET,
typ: SockType = SOCK_STREAM,
sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP): AsyncFD =
result = newRawSocket(domain, typ, protocol).AsyncFD
result = newRawSocket(domain, sockType, protocol).AsyncFD
result.SocketHandle.setBlocking(false)
when defined(macosx):
result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1)
@@ -1009,7 +1010,7 @@ else:
processTimers(p)
proc connect*(socket: AsyncFD, address: string, port: Port,
af_unused = AF_INET): Future[void] =
domain = AF_INET): Future[void] =
var retFuture = newFuture[void]("connect")
proc cb(fd: AsyncFD): bool =
@@ -1017,8 +1018,8 @@ else:
retFuture.complete()
return true
var sockDomain = getSockDomain(socket.SocketHandle)
var aiList = getAddrInfo(address, port, sockDomain)
assert getSockDomain(socket.SocketHandle) == domain
var aiList = getAddrInfo(address, port, domain)
var success = false
var lastError: OSErrorCode
var it = aiList
@@ -1496,8 +1497,8 @@ macro async*(prc: stmt): stmt {.immediate.} =
result[6] = outerProcBody
#echo(treeRepr(result))
#if prc[0].getName == "test":
# echo(toStrLit(result))
if prc[0].getName == "getAsync":
echo(toStrLit(result))
proc recvLine*(socket: AsyncFD): Future[string] {.async.} =
## Reads a line of data from ``socket``. Returned future will complete once

View File

@@ -85,35 +85,44 @@ type
bioIn: BIO
bioOut: BIO
of false: nil
domain: Domain
sockType: SockType
protocol: Protocol
AsyncSocket* = ref AsyncSocketDesc
{.deprecated: [PAsyncSocket: AsyncSocket].}
# TODO: Save AF, domain etc info and reuse it in procs which need it like connect.
proc newAsyncSocket*(fd: AsyncFD, buffered = true): AsyncSocket =
proc newAsyncSocket*(fd: AsyncFD, domain: Domain = AF_INET,
sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP, buffered = true): AsyncSocket =
## Creates a new ``AsyncSocket`` based on the supplied params.
assert fd != osInvalidSocket.AsyncFD
new(result)
result.fd = fd.SocketHandle
result.isBuffered = buffered
result.domain = domain
result.sockType = sockType
result.protocol = protocol
if buffered:
result.currPos = 0
proc newAsyncSocket*(domain: Domain = AF_INET6, typ: SockType = SOCK_STREAM,
proc newAsyncSocket*(domain: Domain = AF_INET, sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP, buffered = true): AsyncSocket =
## Creates a new asynchronous socket.
##
## This procedure will also create a brand new file descriptor for
## this socket.
result = newAsyncSocket(newAsyncRawSocket(domain, typ, protocol), buffered)
result = newAsyncSocket(newAsyncRawSocket(domain, sockType, protocol), domain,
sockType, protocol, buffered)
proc newAsyncSocket*(domain, typ, protocol: cint, buffered = true): AsyncSocket =
proc newAsyncSocket*(domain, sockType, protocol: cint,
buffered = true): AsyncSocket =
## Creates a new asynchronous socket.
##
## This procedure will also create a brand new file descriptor for
## this socket.
result = newAsyncSocket(newAsyncRawSocket(domain, typ, protocol), buffered)
result = newAsyncSocket(newAsyncRawSocket(domain, sockType, protocol),
Domain(domain), SockType(sockType), Protocol(protocol), buffered)
when defined(ssl):
proc getSslError(handle: SslPtr, err: cint): cint =
@@ -169,13 +178,12 @@ when defined(ssl):
let err = getSslError(socket.sslHandle, opResult.cint)
yield appeaseSsl(socket, flags, err.cint)
proc connect*(socket: AsyncSocket, address: string, port: Port,
af = AF_INET) {.async.} =
proc connect*(socket: AsyncSocket, address: string, port: Port) {.async.} =
## Connects ``socket`` to server at ``address:port``.
##
## Returns a ``Future`` which will complete when the connection succeeds
## or an error occurs.
await connect(socket.fd.AsyncFD, address, port, af)
await connect(socket.fd.AsyncFD, address, port, socket.domain)
if socket.isSsl:
when defined(ssl):
let flags = {SocketFlag.SafeDisconn}
@@ -287,7 +295,8 @@ proc acceptAddr*(socket: AsyncSocket, flags = {SocketFlag.SafeDisconn}):
retFuture.fail(future.readError)
else:
let resultTup = (future.read.address,
newAsyncSocket(future.read.client, socket.isBuffered))
newAsyncSocket(future.read.client, socket.domain,
socket.sockType, socket.protocol, socket.isBuffered))
retFuture.complete(resultTup)
return retFuture
@@ -423,17 +432,15 @@ proc bindAddr*(socket: AsyncSocket, port = Port(0), address = "") {.
## Binds ``address``:``port`` to the socket.
##
## If ``address`` is "" then ADDR_ANY will be bound.
var sockDomain = getSockDomain(socket.fd)
var realaddr = address
if realaddr == "":
case sockDomain
case socket.domain
of AF_INET6: realaddr = "::"
of AF_INET: realaddr = "0.0.0.0"
else:
raiseOSError("Unknown socket address family and no address specified to bindAddr")
var aiList = getAddrInfo(realaddr, port, sockDomain)
var aiList = getAddrInfo(realaddr, port, socket.domain)
if bindAddr(socket.fd, aiList.ai_addr, aiList.ai_addrlen.Socklen) < 0'i32:
dealloc(aiList)
raiseOSError(osLastError())

View File

@@ -64,6 +64,9 @@ type
sslPeekChar: char
of false: nil
lastError: OSErrorCode ## stores the last error on this socket
domain: Domain
sockType: SockType
protocol: Protocol
Socket* = ref SocketImpl
@@ -124,33 +127,39 @@ proc toOSFlags*(socketFlags: set[SocketFlag]): cint =
result = result or MSG_PEEK
of SocketFlag.SafeDisconn: continue
proc newSocket*(fd: SocketHandle, buffered = true): Socket =
proc newSocket*(fd: SocketHandle, domain: Domain = AF_INET,
sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP, buffered = true): Socket =
## Creates a new socket as specified by the params.
assert fd != osInvalidSocket
new(result)
result.fd = fd
result.isBuffered = buffered
result.domain = domain
result.sockType = sockType
result.protocol = protocol
if buffered:
result.currPos = 0
proc newSocket*(domain, typ, protocol: cint, buffered = true): Socket =
proc newSocket*(domain, sockType, protocol: cint, buffered = true): Socket =
## Creates a new socket.
##
## If an error occurs EOS will be raised.
let fd = newRawSocket(domain, typ, protocol)
let fd = newRawSocket(domain, sockType, protocol)
if fd == osInvalidSocket:
raiseOSError(osLastError())
result = newSocket(fd, buffered)
result = newSocket(fd, domain.Domain, sockType.SockType, protocol.Protocol,
buffered)
proc newSocket*(domain: Domain = AF_INET, typ: SockType = SOCK_STREAM,
proc newSocket*(domain: Domain = AF_INET, sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP, buffered = true): Socket =
## Creates a new socket.
##
## If an error occurs EOS will be raised.
let fd = newRawSocket(domain, typ, protocol)
let fd = newRawSocket(domain, sockType, protocol)
if fd == osInvalidSocket:
raiseOSError(osLastError())
result = newSocket(fd, buffered)
result = newSocket(fd, domain, sockType, protocol, buffered)
when defined(ssl):
CRYPTO_malloc_init()
@@ -371,7 +380,7 @@ proc bindAddr*(socket: Socket, port = Port(0), address = "") {.
sizeof(name).SockLen) < 0'i32:
raiseOSError(osLastError())
else:
var aiList = getAddrInfo(address, port, AF_INET)
var aiList = getAddrInfo(address, port, socket.domain)
if bindAddr(socket.fd, aiList.ai_addr, aiList.ai_addrlen.SockLen) < 0'i32:
dealloc(aiList)
raiseOSError(osLastError())
@@ -532,15 +541,15 @@ proc setSockOpt*(socket: Socket, opt: SOBool, value: bool, level = SOL_SOCKET) {
var valuei = cint(if value: 1 else: 0)
setSockOptInt(socket.fd, cint(level), toCInt(opt), valuei)
proc connect*(socket: Socket, address: string, port = Port(0),
af: Domain = AF_INET) {.tags: [ReadIOEffect].} =
proc connect*(socket: Socket, address: string,
port = Port(0)) {.tags: [ReadIOEffect].} =
## Connects socket to ``address``:``port``. ``Address`` can be an IP address or a
## host name. If ``address`` is a host name, this function will try each IP
## of that host name. ``htons`` is already performed on ``port`` so you must
## not do it.
##
## If ``socket`` is an SSL socket a handshake will be automatically performed.
var aiList = getAddrInfo(address, port, af)
var aiList = getAddrInfo(address, port, socket.domain)
# try all possibilities:
var success = false
var lastError: OSErrorCode
@@ -992,15 +1001,15 @@ proc connectAsync(socket: Socket, name: string, port = Port(0),
dealloc(aiList)
if not success: raiseOSError(lastError)
proc connect*(socket: Socket, address: string, port = Port(0), timeout: int,
af: Domain = AF_INET) {.tags: [ReadIOEffect, WriteIOEffect].} =
proc connect*(socket: Socket, address: string, port = Port(0),
timeout: int) {.tags: [ReadIOEffect, WriteIOEffect].} =
## Connects to server as specified by ``address`` on port specified by ``port``.
##
## The ``timeout`` paremeter specifies the time in milliseconds to allow for
## the connection to the server to be made.
socket.fd.setBlocking(false)
socket.connectAsync(address, port, af)
socket.connectAsync(address, port, socket.domain)
var s = @[socket.fd]
if selectWrite(s, timeout) != 1:
raise newException(TimeoutError, "Call to 'connect' timed out.")

View File

@@ -157,17 +157,17 @@ else:
result = cint(ord(p))
proc newRawSocket*(domain: Domain = AF_INET, typ: SockType = SOCK_STREAM,
proc newRawSocket*(domain: Domain = AF_INET, sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP): SocketHandle =
## Creates a new socket; returns `InvalidSocket` if an error occurs.
socket(toInt(domain), toInt(typ), toInt(protocol))
socket(toInt(domain), toInt(sockType), toInt(protocol))
proc newRawSocket*(domain: cint, typ: cint, protocol: cint): SocketHandle =
proc newRawSocket*(domain: cint, sockType: cint, protocol: cint): SocketHandle =
## Creates a new socket; returns `InvalidSocket` if an error occurs.
##
## Use this overload if one of the enums specified above does
## not contain what you need.
socket(domain, typ, protocol)
socket(domain, sockType, protocol)
proc close*(socket: SocketHandle) =
## closes a socket.
@@ -190,16 +190,17 @@ proc listen*(socket: SocketHandle, backlog = SOMAXCONN): cint {.tags: [ReadIOEff
else:
result = posix.listen(socket, cint(backlog))
proc getAddrInfo*(address: string, port: Port, af: Domain = AF_INET, typ: SockType = SOCK_STREAM,
prot: Protocol = IPPROTO_TCP): ptr AddrInfo =
proc getAddrInfo*(address: string, port: Port, domain: Domain = AF_INET,
sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP): ptr AddrInfo =
##
##
## **Warning**: The resulting ``ptr TAddrInfo`` must be freed using ``dealloc``!
var hints: AddrInfo
result = nil
hints.ai_family = toInt(af)
hints.ai_socktype = toInt(typ)
hints.ai_protocol = toInt(prot)
hints.ai_family = toInt(domain)
hints.ai_socktype = toInt(sockType)
hints.ai_protocol = toInt(protocol)
hints.ai_flags = AI_V4MAPPED
var gaiResult = getaddrinfo(address, $port, addr(hints), result)
if gaiResult != 0'i32:

View File

@@ -28,7 +28,12 @@ News
``--gc:none``. ``--os:standalone`` without ``--gc:none`` is now a version
that doesn't depend on any OS but includes the GC. However this version
is currently untested!
- All procedures which construct a ``Socket``/``AsyncSocket`` now need to
specify the socket domain, type and protocol. The param name
``typ: SockType`` (in ``newSocket``/``newAsyncSocket`` procs) was also
renamed to ``sockType``. The param ``af`` in the ``connect`` procs was
removed. This affects ``asyncnet``, ``asyncdispatch``, ``net``, and
``rawsockets``.
Library additions
-----------------