Copyright | (c) Lars Petersen 2015 |
---|---|
License | MIT |
Maintainer | info@lars-petersen.net |
Stability | experimental |
Safe Haskell | None |
Language | Haskell2010 |
This starts a TCP server on localhost, sends "Hello world!"
to
connecting peers and closes the connection immediately.
{-# LANGUAGE OverloadedStrings #-} module Main where import System.Socket import System.Socket.Family.Inet as Inet import Data.Monoid import Data.ByteString import Control.Monad import Control.Concurrent import Control.Exception main :: IO () main = do s <- socket :: IO (Socket Inet Stream TCP) setSocketOption s (ReuseAddress True) bind s addr listen s 5 forever $ do (peer,_) <- accept s forkIO $ do sendAll peer "Hello world!" msgNoSignal `finally` close peer where addr = SocketAddressInet Inet.loopback 8080
This downloads the Haskell website and prints it to stdout.
Note the use of IPv4-mapped Inet6
addresses: This will work
even if you don't have IPv6 connectivity yet and is the preferred method
when writing new applications.
{-# LANGUAGE OverloadedStrings #-} module Main where import Data.Monoid import Data.ByteString.Lazy as B import System.Socket main :: IO () main = do withConnectedSocket "www.haskell.org" "80" (aiAll `mappend` aiV4Mapped) $ \sock-> do let _ = sock :: Socket Inet6 Stream TCP sendAll sock "GET / HTTP/1.0\r\nHost: www.haskell.org\r\n\r\n" msgNoSignal x <- receiveAll sock (1024*1024*1024) mempty B.putStr x
- data AddressInfo f t p = AddressInfo {}
- class Family f => GetAddressInfo f where
- getAddressInfo :: (Type t, Protocol p) => Maybe ByteString -> Maybe ByteString -> AddressInfoFlags -> IO [AddressInfo f t p]
- class Family f => GetNameInfo f where
- getNameInfo :: SocketAddress f -> NameInfoFlags -> IO (ByteString, ByteString)
- socket :: (Family f, Type t, Protocol p) => IO (Socket f t p)
- connect :: Family f => Socket f t p -> SocketAddress f -> IO ()
- bind :: Family f => Socket f t p -> SocketAddress f -> IO ()
- listen :: Socket f t p -> Int -> IO ()
- accept :: Family f => Socket f t p -> IO (Socket f t p, SocketAddress f)
- send :: Socket f t p -> ByteString -> MessageFlags -> IO Int
- sendTo :: Family f => Socket f t p -> ByteString -> MessageFlags -> SocketAddress f -> IO Int
- receive :: Socket f t p -> Int -> MessageFlags -> IO ByteString
- receiveFrom :: Family f => Socket f t p -> Int -> MessageFlags -> IO (ByteString, SocketAddress f)
- close :: Socket f t p -> IO ()
- withConnectedSocket :: forall f t p a. (GetAddressInfo f, Type t, Protocol p) => ByteString -> ByteString -> AddressInfoFlags -> (Socket f t p -> IO a) -> IO a
- sendAll :: Socket f Stream p -> ByteString -> MessageFlags -> IO ()
- receiveAll :: Socket f Stream p -> Int64 -> MessageFlags -> IO ByteString
- newtype Socket f t p = Socket (MVar Fd)
- class Storable (SocketAddress f) => Family f where
- type SocketAddress f
- familyNumber :: f -> CInt
- data Inet
- data Inet6
- class Type t where
- typeNumber :: t -> CInt
- data Datagram
- data Raw
- data SequentialPacket
- data Stream
- class Protocol p where
- protocolNumber :: p -> CInt
- data UDP
- data TCP
- newtype SocketException = SocketException CInt
- eOk :: SocketException
- eInterrupted :: SocketException
- eAgain :: SocketException
- eWouldBlock :: SocketException
- eBadFileDescriptor :: SocketException
- eInProgress :: SocketException
- eProtocolNotSupported :: SocketException
- eInvalid :: SocketException
- eConnectionRefused :: SocketException
- eNetworkUnreachable :: SocketException
- eNotConnected :: SocketException
- eAlready :: SocketException
- eIsConnected :: SocketException
- eTimedOut :: SocketException
- ePipe :: SocketException
- eOperationNotSupported :: SocketException
- newtype AddressInfoException = AddressInfoException CInt
- eaiAgain :: AddressInfoException
- eaiBadFlags :: AddressInfoException
- eaiFail :: AddressInfoException
- eaiFamily :: AddressInfoException
- eaiMemory :: AddressInfoException
- eaiNoName :: AddressInfoException
- eaiSocketType :: AddressInfoException
- eaiService :: AddressInfoException
- eaiSystem :: AddressInfoException
- class GetSocketOption o where
- getSocketOption :: Socket f t p -> IO o
- class SetSocketOption o where
- setSocketOption :: Socket f t p -> o -> IO ()
- data Error = Error SocketException
- data ReuseAddress = ReuseAddress Bool
- newtype MessageFlags = MessageFlags CInt
- msgEndOfRecord :: MessageFlags
- msgNoSignal :: MessageFlags
- msgOutOfBand :: MessageFlags
- msgWaitAll :: MessageFlags
- newtype AddressInfoFlags = AddressInfoFlags CInt
- aiAddressConfig :: AddressInfoFlags
- aiAll :: AddressInfoFlags
- aiCanonicalName :: AddressInfoFlags
- aiNumericHost :: AddressInfoFlags
- aiNumericService :: AddressInfoFlags
- aiPassive :: AddressInfoFlags
- aiV4Mapped :: AddressInfoFlags
- newtype NameInfoFlags = NameInfoFlags CInt
- niNameRequired :: NameInfoFlags
- niDatagram :: NameInfoFlags
- niNoFullyQualifiedDomainName :: NameInfoFlags
- niNumericHost :: NameInfoFlags
- niNumericService :: NameInfoFlags
Name Resolution
data AddressInfo f t p Source
Eq (SocketAddress f) => Eq (AddressInfo f t p) | |
Show (SocketAddress f) => Show (AddressInfo f t p) |
getAddressInfo
class Family f => GetAddressInfo f where Source
getAddressInfo :: (Type t, Protocol p) => Maybe ByteString -> Maybe ByteString -> AddressInfoFlags -> IO [AddressInfo f t p] Source
Maps names to addresses (i.e. by DNS lookup).
The operation throws AddressInfoException
s.
Contrary to the underlying getaddrinfo
operation this wrapper is
typesafe and thus only returns records that match the address, type
and protocol encoded in the type. This is the price we have to pay
for typesafe sockets and extensibility.
If you need different types of records, you need to start several
queries. If you want to connect to both IPv4 and IPV6 addresses use
aiV4Mapped
and use IPv6-sockets.
> getAddressInfo (Just "www.haskell.org") (Just "80") aiV4Mapped :: IO [AddressInfo Inet6 Stream TCP] [AddressInfo { addressInfoFlags = AddressInfoFlags 8, socketAddress = SocketAddressInet6 {address = 2400:cb00:2048:0001:0000:0000:6ca2:cc3c, port = 80, flowInfo = mempty, scopeId = 0}, canonicalName = Nothing }] > getAddressInfo (Just "darcs.haskell.org") Nothing aiV4Mapped :: IO [AddressInfo Inet6 Stream TCP] [AddressInfo { addressInfoFlags = AddressInfoFlags 8, socketAddress = SocketAddressInet6 {address = 0000:0000:0000:0000:0000:ffff:17fd:e1ad, port = 0, flowInfo = mempty, scopeId = 0}, canonicalName = Nothing }] > getAddressInfo (Just "darcs.haskell.org") Nothing mempty :: IO [AddressInfo Inet6 Stream TCP] *** Exception: AddressInfoException "Name or service not known"
getNameInfo
class Family f => GetNameInfo f where Source
Maps addresss to readable host- and service names.
The operation throws AddressInfoException
s.
> getNameInfo (SocketAddressInet loopback 80) mempty ("localhost.localdomain","http")
getNameInfo :: SocketAddress f -> NameInfoFlags -> IO (ByteString, ByteString) Source
Operations
socket
socket :: (Family f, Type t, Protocol p) => IO (Socket f t p) Source
Creates a new socket.
Whereas the underlying POSIX socket operation takes 3 parameters, this library encodes this information in the type variables. This rules out several kinds of errors and escpecially simplifies the handling of addresses (by using associated type families). Examples:
-- create a IPv4-UDP-datagram socket sock <- socket :: IO (Socket Inet Datagram UDP) -- create a IPv6-TCP-streaming socket sock6 <- socket :: IO (Socket Inet6 Stream TCP)
This operation sets up a finalizer that automatically closes the socket when the garbage collection decides to collect it. This is just a fail-safe. You might still run out of file descriptors as there's no guarantee about when the finalizer is run. You're advised to manually
close
the socket when it's no longer needed. If possible, usebracket
to reliably close the socket descriptor on exception or regular termination of your computation:result <- bracket (socket :: IO (Socket Inet6 Stream TCP)) close $ \sock-> do somethingWith sock -- your computation here return somethingelse
- This operation configures the socket non-blocking to work seamlessly with the runtime system's event notification mechanism.
- This operation can safely deal with asynchronous exceptions without leaking file descriptors.
- This operation throws
SocketException
s. Consult yourman
page for details and specificerrno
s.
connect
connect :: Family f => Socket f t p -> SocketAddress f -> IO () Source
Connects to an remote address.
- Calling
connect
on aclose
d socket throwseBadFileDescriptor
even if the former file descriptor has been reassigned. - This operation returns as soon as a connection has been established (as if the socket were blocking). The connection attempt has either failed or succeeded after this operation threw an exception or returned.
- The operation might throw
SocketException
s. Due to implementation quirks the socket should be considered in an undefined state when this operation failed. It should be closed then. - Also see [these considerations](http:/cr.yp.todocs/connect.html) on the problems with connecting non-blocking sockets.
bind
bind :: Family f => Socket f t p -> SocketAddress f -> IO () Source
Bind a socket to an address.
- Calling
bind
on aclose
d socket throwseBadFileDescriptor
even if the former file descriptor has been reassigned. - It is assumed that
c_bind
never blocks and thereforeeInProgress
,eAlready
andeInterrupted
don't occur. This assumption is supported by the fact that the Linux manpage doesn't mention any of these errors, the Posix manpage doesn't mention the last one and even MacOS' implementation will never fail with any of these when the socket is configured non-blocking as [argued here](http:/stackoverflow.coma/14485305). - This operation throws
SocketException
s. Consult yourman
page for details and specificerrno
s.
listen
listen :: Socket f t p -> Int -> IO () Source
Starts listening and queueing connection requests on a connection-mode socket.
- Calling
listen
on aclose
d socket throwseBadFileDescriptor
even if the former file descriptor has been reassigned. - The second parameter is called backlog and sets a limit on how many
unaccepted connections the socket implementation shall queue. A value
of
0
leaves the decision to the implementation. - This operation throws
SocketException
s. Consult yourman
page for details and specificerrno
s.
accept
accept :: Family f => Socket f t p -> IO (Socket f t p, SocketAddress f) Source
Accept a new connection.
- Calling
accept
on aclose
d socket throwseBadFileDescriptor
even if the former file descriptor has been reassigned. - This operation configures the new socket non-blocking (TODO: use
accept4
if available). - This operation sets up a finalizer for the new socket that automatically
closes the new socket when the garbage collection decides to collect it.
This is just a fail-safe. You might still run out of file descriptors as
there's no guarantee about when the finalizer is run. You're advised to
manually
close
the socket when it's no longer needed. - This operation throws
SocketException
s. Consult yourman
page for details and specificerrno
s. - This operation catches
eAgain
,eWouldBlock
andeInterrupted
internally and retries automatically.
send, sendTo
send :: Socket f t p -> ByteString -> MessageFlags -> IO Int Source
Send a message on a connected socket.
- Calling
send
on aclose
d socket throwseBadFileDescriptor
even if the former file descriptor has been reassigned. - The operation returns the number of bytes sent. On
Datagram
andSequentialPacket
sockets certain assurances on atomicity exist andeAgain
oreWouldBlock
are returned until the whole message would fit into the send buffer. - This operation throws
SocketException
s. Consultman 3p send
for details and specificerrno
s. eAgain
,eWouldBlock
andeInterrupted
and handled internally and won't be thrown. For performance reasons the operation first tries a write on the socket and then waits when it goteAgain
oreWouldBlock
.
sendTo :: Family f => Socket f t p -> ByteString -> MessageFlags -> SocketAddress f -> IO Int Source
Like send
, but allows for specifying a destination address.
receive, receiveFrom
receive :: Socket f t p -> Int -> MessageFlags -> IO ByteString Source
Receive a message on a connected socket.
- Calling
receive
on aclose
d socket throwseBadFileDescriptor
even if the former file descriptor has been reassigned. - The operation takes a buffer size in bytes a first parameter which
limits the maximum length of the returned
ByteString
. - This operation throws
SocketException
s. Consultman 3p receive
for details and specificerrno
s. eAgain
,eWouldBlock
andeInterrupted
and handled internally and won't be thrown. For performance reasons the operation first tries a read on the socket and then waits when it goteAgain
oreWouldBlock
.
receiveFrom :: Family f => Socket f t p -> Int -> MessageFlags -> IO (ByteString, SocketAddress f) Source
Like receive
, but additionally yields the peer address.
close
close :: Socket f t p -> IO () Source
Closes a socket.
- This operation is idempotent and thus can be performed more than once without throwing an exception. If it throws an exception it is presumably a not recoverable situation and the process should exit.
- This operation does not block.
- This operation wakes up all threads that are currently blocking on this
socket. All other threads are guaranteed not to block on operations on this socket in the future.
Threads that perform operations other than
close
on this socket will fail witheBadFileDescriptor
after the socket has been closed (close
replaces theFd
in theMVar
with-1
to reliably avoid use-after-free situations). - This operation potentially throws
SocketException
s (onlyEIO
is documented).eInterrupted
is catched internally and retried automatically, so won't be thrown.
Convenience Operations
withConnectedSocket
withConnectedSocket :: forall f t p a. (GetAddressInfo f, Type t, Protocol p) => ByteString -> ByteString -> AddressInfoFlags -> (Socket f t p -> IO a) -> IO a Source
Looks up a name and executes an supplied action with a connected socket.
- The addresses returned by
getAddressInfo
are tried in sequence until a connection has been established or all have been tried. - If
connect
fails on all addresses the exception that occured on the last connection attempt is thrown. - The supplied action is executed at most once with the first established connection.
- If the address family is
Inet6
,V6Only
is set toFalse
which means the other end may be both IPv4 or IPv6. - All sockets created by this operation get closed automatically.
- This operation throws
AddressInfoException
s,SocketException
s and all exceptions that that the supplied action might throw.
withConnectedSocket "wwww.haskell.org" "80" (aiAll `mappend` aiV4Mapped) $ \sock-> do let _ = sock :: Socket Inet6 Stream TCP doSomethingWithSocket sock
sendAll
sendAll :: Socket f Stream p -> ByteString -> MessageFlags -> IO () Source
Like send
, but operates on lazy ByteString
s and
continues until all data has been sent or an exception occured.
receiveAll
receiveAll :: Socket f Stream p -> Int64 -> MessageFlags -> IO ByteString Source
Like receive
, but operates on lazy ByteString
s and
continues until either an empty part has been received (peer closed
the connection) or given buffer limit has been exceeded or an
exception occured.
- The
Int64
parameter is a soft limit on how many bytes to receive. Collection is stopped if the limit has been exceeded. The result might be up to one internal buffer size longer than the given limit. If the returnedByteString
s length is lower or eqal than the limit, the data has not been truncated and the transmission is complete.
Sockets
A generic socket type. Also see socket
for details.
The socket is just an MVar
-wrapped file descriptor.
It is exposed in order to make this library easily extensible, but it is
usually not necessary nor advised to work directly on the file descriptor.
If you do, the following rules must be obeyed:
- Make sure not to deadlock. Use
withMVar
or similar. - The lock must not be held during a blocking call. This would make it impossible to send and receive simultaneously or to close the socket.
- The lock must be held when calling operations that use the file descriptor. Otherwise the socket might get closed or even reused by another thread/capability which might result in reading from or writing totally different connection. This is a security nightmare!
- The socket is non-blocking and all the code relies on that assumption.
You need to use GHC's eventing mechanism primitives to block until
something happens. The former rules forbid to use
threadWaitRead
as it does not seperate between registering the file descriptor (for which the lock must be held) and the actual waiting (for which you must not hold the lock). Also see [this](https:/mail.haskell.orgpipermailhaskell-cafe2014-September/115823.html) thread and read the library code to see how the problem is currently circumvented.
Families
class Storable (SocketAddress f) => Family f where Source
type SocketAddress f Source
familyNumber :: f -> CInt Source
Inet
Inet6
Types
typeNumber :: t -> CInt Source
Datagram
Raw
SequentialPacket
Stream
Protocols
protocolNumber :: p -> CInt Source
UDP
TCP
Exceptions
SocketException
newtype SocketException Source
AddressInfoException
newtype AddressInfoException Source
Contains the error code that can be matched against. Use show
to get a human readable explanation of the error.
eaiAgain :: AddressInfoException Source
AddressInfoException "Temporary failure in name resolution"
eaiBadFlags :: AddressInfoException Source
AddressInfoException "Bad value for ai_flags"
eaiFail :: AddressInfoException Source
AddressInfoException "Non-recoverable failure in name resolution"
eaiFamily :: AddressInfoException Source
AddressInfoException "ai_family not supported"
eaiMemory :: AddressInfoException Source
AddressInfoException "Memory allocation failure"
eaiNoName :: AddressInfoException Source
AddressInfoException "No such host is known"
eaiSocketType :: AddressInfoException Source
AddressInfoException "ai_socktype not supported"
eaiService :: AddressInfoException Source
AddressInfoException "Servname not supported for ai_socktype"
eaiSystem :: AddressInfoException Source
AddressInfoException "System error"
Socket Options
getSocketOption
class GetSocketOption o where Source
getSocketOption :: Socket f t p -> IO o Source
setSocketOption
class SetSocketOption o where Source
setSocketOption :: Socket f t p -> o -> IO () Source
data ReuseAddress Source
SO_REUSEADDR
Flags
MessageFlags
newtype MessageFlags Source
msgEndOfRecord :: MessageFlags Source
MSG_EOR
msgNoSignal :: MessageFlags Source
MSG_NOSIGNAL
Suppresses the generation of PIPE
signals when writing to a socket
that is no longer connected.
Although this flag is POSIX, it is not available on all platforms. Try
msgNoSignal /= mempty
in order to check whether this flag is defined on a certain platform. It is safe to just use this constant even if it might not have effect on a certain target platform. The platform independence of this flag is therefore fulfilled to some extent.
Some more explanation on the platform specific behaviour:
- Linux defines and supports
MSG_NOSIGNAL
and properly suppresses the generation of broken pipe-related signals. - Windows does not define it, but does not generate signals either.
- OSX does not define it, but generates
PIPE
signals. The GHC runtime ignores them if you don't hook them explicitly. The non-portable socket optionSO_NOSIGPIPE
may be used disable signals on a per-socket basis.
msgOutOfBand :: MessageFlags Source
MSG_OOB
msgWaitAll :: MessageFlags Source
MSG_WAITALL
AddressInfoFlags
newtype AddressInfoFlags Source
Use the Monoid
instance to combine several flags:
mconcat [aiAddressConfig, aiV4Mapped]
aiAddressConfig :: AddressInfoFlags Source
AI_ADDRCONFIG
:
aiAll :: AddressInfoFlags Source
AI_ALL
: Return both IPv4 (as mapped SocketAddressInet6
) and IPv6 addresses when
aiV4Mapped
is set independent of whether IPv6 addresses exist for this
name.
aiCanonicalName :: AddressInfoFlags Source
AI_CANONNAME
:
aiNumericHost :: AddressInfoFlags Source
AI_NUMERICHOST
:
aiNumericService :: AddressInfoFlags Source
AI_NUMERICSERV
:
aiPassive :: AddressInfoFlags Source
AI_PASSIVE
:
aiV4Mapped :: AddressInfoFlags Source
AI_V4MAPPED
: Return mapped IPv4 addresses if no IPv6 addresses could be found
or if aiAll
flag is set.
NameInfoFlags
newtype NameInfoFlags Source
Use the Monoid
instance to combine several flags:
mconcat [niNameRequired, niNoFullyQualifiedDomainName]
niNameRequired :: NameInfoFlags Source
NI_NAMEREQD
: Throw an exception if the hostname cannot be determined.
niNoFullyQualifiedDomainName :: NameInfoFlags Source
NI_NOFQDN
: Return only the hostname part of the fully qualified domain name for local hosts.
niNumericHost :: NameInfoFlags Source
NI_NUMERICHOST
: Return the numeric form of the host address.
niNumericService :: NameInfoFlags Source
NI_NUMERICSERV
: Return the numeric form of the service address.