[More writing on sockets John Goerzen **20080131022917] { hunk ./en/ch31-sockets.xml 105 + + Getting bind: permission denied when + testing this? Make sure you use a port number greater than + 1024. Some operating systems only allow the + root user to bind to ports less than 1024. + + hunk ./en/ch31-sockets.xml 142 - + + Handling Multiple TCP Streams + + With TCP, connections are stateful. That means that there is + a dedicated logical "channel" between a client and server, + rather than just one-off packets as with UDP. This makes + things easy for client developers. Server applications almost + always will want to be able to handle more than one TCP + connection at once. How then to do this? + + + On the server side, you will first create a socket and bind to + a port, just like UDP. Instead of repeatedly listening for + data from any location, your main loop will be around the + accept call. Each time a client connects, + the server's operating system allocates a new socket for it. + So we have the master socket, used only + to listen for incoming connections, and never to transmit + data. We also have the potential for multiple + child sockets to be used at once, each + corresponding to a logical TCP conversation. + + + In Haskell, you will usually use forkIO to + create a separate lightweight thread to handle each + conversation with a child. Haskell has an efficient internal + implementation of this that performs quite well. + + + + TCP Syslog Server + + Let's say that we wanted to reimplement syslog using TCP instead + of UDP. We could say that a single message is defined not by + being in a single packet, but is ended by a trailing newline + character '\n'. Any given client could send + 0 or more messages to the server using a given TCP connection. + Here's how we might write that. + + &syslogtcpserver.hs:all; + + For our SyslogTypes implementation, see . + + + Let's look at this code. Our main loop is in + procRequests, where we loop forever waiting + for new connections from clients. The + accept call blocks until a client + connects. When a client connects, we get a new socket and the + address of the client. We pass a message to the handler about + that, then use forkIO to create a thread to + handle the data from that client. This thread runs + procMessages. + + + When dealing with TCP data, it's often convenient to convert a + socket into a Haskell Handle. We do so + here, and explicitly set the buffering -- an important point + for TCP communication. Next, we set up lazy reading from the + socket's &Handle;. For each incoming line, we pass it to + handle. After there is no more data -- + because the remote end has closed the socket -- we output a + message about that. + + + Since we may be handling multiple incoming messages at once, + we need to ensure that we're not writing out multiple messages + at once in the handler. That could result in garbled output. + We use a simple lock to serialize access to the handler, and + write a simple handle function to handle + that. + + + You can test this with the client we'll present next, or you + can even use the telnet program to connect + to this server. Each line of text you send to it will be + printed on the display by the server. + + hunk ./en/ch31-sockets.xml 223 - - - Multithreading to handle multiple connections - hunk ./examples/ch31/syslogserver.hs 14 -serveLog port handlerfunc = +serveLog port handlerfunc = withSocketsDo $ hunk ./examples/ch31/syslogserver.hs 41 -plainHAndler :: HandlerFunc +plainHandler :: HandlerFunc hunk ./examples/ch31/syslogtcpserver.hs 17 -serveLog port handlerfunc = +serveLog port handlerfunc = withSocketsDo $ hunk ./examples/ch31/syslogtcpserver.hs 34 - + + -- Create a lock to use for synchronizing access to the handler hunk ./examples/ch31/syslogtcpserver.hs 42 + -- | Process incoming connection requests + procRequests :: MVar () -> Socket -> IO () hunk ./examples/ch31/syslogtcpserver.hs 50 + + -- | Process incoming messages + procMessages :: MVar () -> Socket -> SockAddr -> IO () hunk ./examples/ch31/syslogtcpserver.hs 61 + + -- Lock the handler before passing data to it. + handle :: MVar () -> HandlerFunc + -- This type is the same as + -- handle :: MVar () -> SockAddr -> String -> IO () }