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 Data.ByteString import Control.Monad import Control.Concurrent import Control.Exception main :: IO () main = do s <- socket :: IO (Socket SockAddrIn STREAM TCP) bind s (SockAddrIn 8080 (pack [127,0,0,1])) listen s 5 forever $ do (peer,addr) <- accept s forkIO $ do sendAll peer "Hello world!" mempty `finally` close peer
This downloads the [Haskell website](http://www.haskell.org) and shows how to handle exceptions. Note the use of IPv4-mapped IPv6 addresses: This will work even if you don't have IPv6 connectivity yet and is the preferred method when new applications.
{-# LANGUAGE OverloadedStrings #-} module Main where import Control.Monad import Control.Exception import Data.Function (fix) import qualified Data.ByteString as BS import System.IO import System.Exit import System.Socket main :: IO () main = fetch `catch` (\e-> do hPutStr stderr "Something failed when resolving the name: " hPutStrLn stderr $ show (e :: AddrInfoException) exitFailure ) `catch` (\e-> do hPutStr stderr "Something went wrong with the socket: " hPutStrLn stderr $ show (e :: SocketException) exitFailure ) fetch :: IO () fetch = do addrs <- getAddrInfo (Just "www.haskell.org") (Just "80") aiV4MAPPED :: IO [AddrInfo SockAddrIn6 STREAM TCP] case addrs of (addr:_) -> -- always use the `bracket` pattern to reliably release resources! bracket ( socket :: IO (Socket SockAddrIn6 STREAM TCP) ) ( close ) ( \s-> do connect s (addrAddress addr) sendAll s "GET / HTTP/1.0\r\nHost: www.haskell.org\r\n\r\n" mempty fix $ \recvMore-> do bs <- recv s 4096 mempty BS.putStr bs if BS.length bs == 0 -- an empty string means the peer terminated the connection then exitSuccess else recvMore ) _ -> error "Illegal state: getAddrInfo yields non-empty list or exception."
- data AddrInfo a t p = AddrInfo {}
- getAddrInfo :: (Address a, Type t, Protocol p) => Maybe ByteString -> Maybe ByteString -> AddrInfoFlags -> IO [AddrInfo a t p]
- getNameInfo :: Address a => a -> NameInfoFlags -> IO (ByteString, ByteString)
- socket :: (Address a, Type t, Protocol p) => IO (Socket a t p)
- bind :: (Address a, Type t, Protocol p) => Socket a t p -> a -> IO ()
- listen :: (Address a, Type t, Protocol p) => Socket a t p -> Int -> IO ()
- accept :: (Address a, Type t, Protocol p) => Socket a t p -> IO (Socket a t p, a)
- connect :: (Address a, Type t, Protocol p) => Socket a t p -> a -> IO ()
- send :: (Address a, Type t, Protocol p) => Socket a t p -> ByteString -> MsgFlags -> IO Int
- sendAll :: (Address a, Type t, Protocol p) => Socket a t p -> ByteString -> MsgFlags -> IO ()
- sendTo :: (Address a, Type t, Protocol p) => Socket a t p -> ByteString -> MsgFlags -> a -> IO Int
- recv :: (Address a, Type t, Protocol p) => Socket a t p -> Int -> MsgFlags -> IO ByteString
- recvFrom :: forall a t p. (Address a, Type t, Protocol p) => Socket a t p -> Int -> MsgFlags -> IO (ByteString, a)
- close :: (Address a, Type t, Protocol p) => Socket a t p -> IO ()
- newtype Socket d t p = Socket (MVar Fd)
- class Storable a => Address a where
- addressFamilyNumber :: a -> CInt
- data SockAddrIn = SockAddrIn {
- sinPort :: Word16
- sinAddr :: ByteString
- data SockAddrIn6 = SockAddrIn6 {}
- data SockAddrUn = SockAddrUn {}
- class Type t where
- typeNumber :: t -> CInt
- data DGRAM
- data STREAM
- data SEQPACKET
- class Protocol p where
- protocolNumber :: p -> CInt
- data UDP
- data TCP
- data SCTP
- newtype SocketException = SocketException Errno
- data AddrInfoException = AddrInfoException CInt String
- class GetSockOpt o where
- getSockOpt :: Socket f t p -> IO o
- class SetSockOpt o where
- setSockOpt :: Socket f t p -> o -> IO ()
- data SO_ACCEPTCONN = SO_ACCEPTCONN Bool
- newtype MsgFlags = MsgFlags CInt
- msgEOR :: MsgFlags
- msgNOSIGNAL :: MsgFlags
- msgOOB :: MsgFlags
- msgWAITALL :: MsgFlags
- newtype AddrInfoFlags = AddrInfoFlags CInt
- aiADDRCONFIG :: AddrInfoFlags
- aiALL :: AddrInfoFlags
- aiCANONNAME :: AddrInfoFlags
- aiNUMERICHOST :: AddrInfoFlags
- aiNUMERICSERV :: AddrInfoFlags
- aiPASSIVE :: AddrInfoFlags
- aiV4MAPPED :: AddrInfoFlags
- newtype NameInfoFlags = NameInfoFlags CInt
- niNAMEREQD :: NameInfoFlags
- niDGRAM :: NameInfoFlags
- niNOFQDN :: NameInfoFlags
- niNUMERICHOST :: NameInfoFlags
- niNUMERICSERV :: NameInfoFlags
Name Resolution
getAddrInfo
getAddrInfo :: (Address a, Type t, Protocol p) => Maybe ByteString -> Maybe ByteString -> AddrInfoFlags -> IO [AddrInfo a t p] Source
Maps names to addresses (i.e. by DNS lookup).
The operation throws AddrInfoException
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.
> getAddrInfo (Just "www.haskell.org") (Just "80") aiV4MAPPED :: IO [AddrInfo SockAddrIn6 STREAM TCP] [AddrInfo {addrInfoFlags = AddrInfoFlags 8, addrAddress = "[2400:cb00:2048:0001:0000:0000:6ca2:cc3c]:80", addrCanonName = Nothing}] > getAddrInfo (Just "darcs.haskell.org") Nothing aiV4MAPPED :: IO [AddrInfo SockAddrIn6 STREAM TCP] [AddrInfo {addrInfoFlags = AddrInfoFlags 8, addrAddress = "[0000:0000:0000:0000:0000:ffff:17fd:e1ad]:0", addrCanonName = Nothing}] > getAddrInfo (Just "darcs.haskell.org") Nothing mempty :: IO [AddrInfo SockAddrIn6 STREAM TCP] *** Exception: AddrInfoException (-2) "Name or service not known"
getNameInfo
getNameInfo :: Address a => a -> NameInfoFlags -> IO (ByteString, ByteString) Source
Maps addresss to readable host- and service names.
The operation throws AddrInfoException
s.
> getNameInfo (SockAddrIn 80 $ pack [23,253,242,70]) mempty ("haskell.org","http")
Operations
socket
socket :: (Address a, Type t, Protocol p) => IO (Socket a t p) Source
Creates a new socket.
Whereas the underlying POSIX socket function 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 SockAddrIn DGRAM UDP) -- create a IPv6-TCP-streaming socket sock6 <- socket :: IO (Socket SockAddrIn6 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. - 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:EAFNOSUPPORT
- The socket domain is not supported.
EMFILE
- The process is out file descriptors.
ENFILE
- The system is out file descriptors.
EPROTONOSUPPORT
- The socket protocol is not supported (for this socket domain).
EPROTOTYPE
- The socket type is not supported by the protocol.
EACCES
- The process is lacking necessary privileges.
ENOMEM
- Insufficient memory.
bind
bind :: (Address a, Type t, Protocol p) => Socket a t p -> a -> IO () Source
Bind a socket to an address.
- Calling
bind
on aclose
d socket throwsEBADF
even if the former file descriptor has been reassigned. - It is assumed that
c_bind
never blocks and thereforeEINPROGRESS
,EALREADY
andEINTR
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). The following
SocketException
s are relevant and might be thrown (seeman bind
for more exceptions regarding SockAddrUn sockets):EADDRINUSE
- The address is in use.
EADDRNOTAVAIL
- The address is not available.
EBADF
- Not a valid file descriptor.
EINVAL
- Socket is already bound and cannot be re-bound or the socket has been shut down.
ENOBUFS
- Insufficient resources.
EOPNOTSUPP
- The socket type does not support binding.
EACCES
- The address is protected and the process is lacking permission.
EISCONN
- The socket is already connected.
ELOOP
- More than {SYMLOOP_MAX} symbolic links were encountered during resolution of the pathname in address.
ENAMETOOLONG
- The length of a pathname exceeds {PATH_MAX}, or pathname resolution of a symbolic link produced an intermediate result with a length that exceeds {PATH_MAX}.
The following
SocketException
s are theoretically possible, but should not occur if the library is correct:EAFNOSUPPORT
- The address family is invalid.
ENOTSOCK
- The file descriptor is not a socket.
EINVAL
- Address length does not match address family.
listen
listen :: (Address a, Type t, Protocol p) => Socket a t p -> Int -> IO () Source
Accept connections on a connection-mode socket.
- Calling
listen
on aclose
d socket throwsEBADF
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:EBADF
- Not a valid file descriptor (only after socket has been closed).
EDESTADDRREQ
- The socket is not bound and the protocol does not support listening on an unbound socket.
EINVAL
- The socket is already connected or has been shut down.
ENOTSOCK
- The file descriptor is not a socket (should be impossible).
EOPNOTSUPP
- The protocol does not support listening.
EACCES
- The process is lacking privileges.
ENOBUFS
- Insufficient resources.
accept
accept :: (Address a, Type t, Protocol p) => Socket a t p -> IO (Socket a t p, a) Source
Accept a new connection.
- Calling
accept
on aclose
d socket throwsEBADF
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 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 catches
EAGAIN
,EWOULDBLOCK
andEINTR
internally and retries automatically. This operation throws
SocketException
s:EBADF
- Not a valid file descriptor (only after the socket has been closed).
ECONNABORTED
- A connection has been aborted.
EINVAL
- The socket is not accepting/listening.
EMFILE
- The process is out file descriptors.
ENFILE
- The system is out file descriptors.
ENOBUFS
- No buffer space available.
ENOMEM
- Out of memory.
ENOSOCK
- Not a valid socket descriptor (should be impossible).
EOPNOTSUPP
- The socket type does not support accepting connections.
EPROTO
- Generic protocol error.
connect
connect :: (Address a, Type t, Protocol p) => Socket a t p -> a -> IO () Source
Connects to an remote address.
- Calling
connect
on aclose
d socket throwsEBADF
even if the former file descriptor has been reassigned. - This function returns as soon as a connection has either been established
or refused. A failed connection attempt does not throw an exception
if
EINTR
orEINPROGRESS
were caught internally. The operation just unblocks and returns in this case. The approach is to just try to read or write the socket and eventually fail there instead. Also see [these considerations](http:/cr.yp.todocs/connect.html) for an explanation.EINTR
andEINPROGRESS
are handled internally and won't be thrown. The following
SocketException
s are relevant and might be thrown if the OS was able to decide the connection request synchronously:EADDRNOTAVAIL
- The address is not available.
EBADF
- The file descriptor is invalid.
ECONNREFUSED
- The target was not listening or refused the connection.
EISCONN
- The socket is already connected.
ENETUNREACH
- The network is unreachable.
ETIMEDOUT
- The connect timed out before a connection was established.
The following
SocketException
s are theoretically possible, but should not occur if the library is correct:EAFNOTSUPPORT
- Address family does not match the socket.
ENOTSOCK
- The descriptor is not a socket.
EPROTOTYPE
- The address type does not match the socket.
send
send :: (Address a, Type t, Protocol p) => Socket a t p -> ByteString -> MsgFlags -> IO Int Source
Send a message on a connected socket.
- Calling
send
on aclose
d socket throwsEBADF
even if the former file descriptor has been reassigned. - The operation returns the number of bytes sent.
EAGAIN
,EWOULDBLOCK
andEINTR
and handled internally and won't be thrown.- The flag
MSG_NOSIGNAL
is set to supress signals which are pointless. The following
SocketException
s are relevant and might be thrown:EBADF
- The file descriptor is invalid.
ECONNRESET
- The peer forcibly closed the connection.
EDESTADDREQ
- Remote address has not been set, but is required.
EMSGSIZE
- The message is too large to be sent all at once, but the protocol requires this.
ENOTCONN
- The socket is not connected.
EPIPE
- The socket is shut down for writing or the socket is not connected anymore.
EACCESS
- The process is lacking permissions.
EIO
- An I/O error occured while writing to the filesystem.
ENETDOWN
- The local network interface is down.
ENETUNREACH
- No route to network.
ENOBUFS
- Insufficient resources to fulfill the request.
The following
SocketException
s are theoretically possible, but should not occur if the library is correct:EOPNOTSUPP
- The specified flags are not supported.
ENOTSOCK
- The descriptor does not refer to a socket.
sendAll
sendAll :: (Address a, Type t, Protocol p) => Socket a t p -> ByteString -> MsgFlags -> IO () Source
Like send
, but continues until all data has been sent.
sendAll sock data flags = do sent <- send sock data flags when (sent < length data) $ sendAll sock (drop sent data) flags
sendTo
sendTo :: (Address a, Type t, Protocol p) => Socket a t p -> ByteString -> MsgFlags -> a -> IO Int Source
Send a message on a socket with a specific destination address.
- Calling
sendTo
on aclose
d socket throwsEBADF
even if the former file descriptor has been reassigned. - The operation returns the number of bytes sent.
EAGAIN
,EWOULDBLOCK
andEINTR
and handled internally and won't be thrown.- The flag
MSG_NOSIGNAL
is set to supress signals which are pointless. The following
SocketException
s are relevant and might be thrown:EBADF
- The file descriptor is invalid.
ECONNRESET
- The peer forcibly closed the connection.
EDESTADDREQ
- Remote address has not been set, but is required.
EMSGSIZE
- The message is too large to be sent all at once, but the protocol requires this.
ENOTCONN
- The socket is not connected.
EPIPE
- The socket is shut down for writing or the socket is not connected anymore.
EACCESS
- The process is lacking permissions.
EDESTADDRREQ
- The destination address is required.
EHOSTUNREACH
- The destination host cannot be reached.
EIO
- An I/O error occured.
EISCONN
- The socket is already connected.
ENETDOWN
- The local network is down.
ENETUNREACH
- No route to the network.
ENUBUFS
- Insufficient resources to fulfill the request.
ENOMEM
- Insufficient memory to fulfill the request.
ELOOP
AF_UNIX
only.ENAMETOOLONG
AF_UNIX
only.
The following
SocketException
s are theoretically possible, but should not occur if the library is correct:EAFNOTSUPP
- The address family does not match.
EOPNOTSUPP
- The specified flags are not supported.
ENOTSOCK
- The descriptor does not refer to a socket.
EINVAL
- The address len does not match.
recv
recv :: (Address a, Type t, Protocol p) => Socket a t p -> Int -> MsgFlags -> IO ByteString Source
Receive a message on a connected socket.
- Calling
recv
on aclose
d socket throwsEBADF
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
. EAGAIN
,EWOULDBLOCK
andEINTR
and handled internally and won't be thrown.The following
SocketException
s are relevant and might be thrown:EBADF
- The file descriptor is invalid.
ECONNRESET
- The peer forcibly closed the connection.
ENOTCONN
- The socket is not connected.
ETIMEDOUT
- The connection timed out.
EIO
- An I/O error occured while writing to the filesystem.
ENOBUFS
- Insufficient resources to fulfill the request.
ENONMEM
- Insufficient memory to fulfill the request.
The following
SocketException
s are theoretically possible, but should not occur if the library is correct:EOPNOTSUPP
- The specified flags are not supported.
ENOTSOCK
- The descriptor does not refer to a socket.
recvFrom
recvFrom :: forall a t p. (Address a, Type t, Protocol p) => Socket a t p -> Int -> MsgFlags -> IO (ByteString, a) Source
Receive a message on a socket and additionally yield the peer address.
- Calling
recvFrom
on aclose
d socket throwsEBADF
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
. EAGAIN
,EWOULDBLOCK
andEINTR
and handled internally and won't be thrown.The following
SocketException
s are relevant and might be thrown:EBADF
- The file descriptor is invalid.
ECONNRESET
- The peer forcibly closed the connection.
ENOTCONN
- The socket is not connected.
ETIMEDOUT
- The connection timed out.
EIO
- An I/O error occured while writing to the filesystem.
ENOBUFS
- Insufficient resources to fulfill the request.
ENONMEM
- Insufficient memory to fulfill the request.
The following
SocketException
s are theoretically possible, but should not occur if the library is correct:EOPNOTSUPP
- The specified flags are not supported.
ENOTSOCK
- The descriptor does not refer to a socket.
close
close :: (Address a, Type t, Protocol p) => Socket a 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 withEBADF
after the socket has been closed (close
replaces theFd
in theMVar
with-1
to reliably avoid use-after-free situations). The following
SocketException
s are relevant and might be thrown:EIO
- An I/O error occured while writing to the filesystem.
The following
SocketException
s are theoretically possible, but should not occur if the library is correct:EBADF
- The file descriptor is invalid.
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.
Addresses
SockAddrIn
data SockAddrIn Source
SockAddrIn | |
|
SockAddrIn6
data SockAddrIn6 Source
SockAddrIn6 | |
|
SockAddrUn
data SockAddrUn Source
Types
DGRAM
STREAM
SEQPACKET
Protocols
protocolNumber :: p -> CInt Source
UDP
TCP
SCTP
Exceptions
SocketException
newtype SocketException Source
AddrInfoException
data AddrInfoException Source
Contains the error code that can be matched against and a readable
description taken from eia_strerr
.
Options
class SetSockOpt o where Source
setSockOpt :: Socket f t p -> o -> IO () Source
SO_ACCEPTCONN
Flags
MsgFlags
Use the Monoid
instance to combine several flags:
mconcat [msgNOSIGNAL, msgWAITALL]
AddrInfoFlags
newtype AddrInfoFlags Source
Use the Monoid
instance to combine several flags:
mconcat [aiADDRCONFIG, aiV4MAPPED]
NameInfoFlags
newtype NameInfoFlags Source
Use the Monoid
instance to combine several flags:
mconcat [niNAMEREQD, niNOFQDN]
niNAMEREQD :: NameInfoFlags Source
Throw an exception if the hostname cannot be determined.
niDGRAM :: NameInfoFlags Source
Service is datagram based (UDP) rather than stream based (TCP).
niNOFQDN :: NameInfoFlags Source
Return only the hostname part of the fully qualified domain name for local hosts.
niNUMERICHOST :: NameInfoFlags Source
Return the numeric form of the host address.
niNUMERICSERV :: NameInfoFlags Source
Return the numeric form of the service address.