Echo server

From Rosetta Code
Revision as of 21:01, 13 August 2009 by rosettacode>Kevin Reid (omit TI-BASIC)
Task
Echo server
You are encouraged to solve this task according to the task description, using any language you may know.

Create a network service that sits on TCP port 12321, which accepts connections on that port, and which echoes complete lines (using a carriage-return/line-feed sequence as line separator) back to clients. No error handling is required. For the purposes of testing, it is only necessary to support connections from localhost (127.0.0.1). Logging of connection information to standard output is recommended.

The implementation must be able to handle simultaneous connections from multiple clients. A multi-threaded solution may be used.

The implementation must not stop responding to other clients if one client sends a partial line or stops reading responses.

AutoHotkey

Some lines in this example are too long (more than 80 characters). Please fix the code if it's possible and remove this message.

echoserver.ahk, modified from script by zed gecko. <lang AutoHotkey>#SingleInstance Force Network_Port = 12321 Network_Address = 127.0.0.1

NewData := false DataReceived = Gosub Connection_Init return

Connection_Init: OnExit, ExitSub socket := PrepareForIncomingConnection(Network_Address, Network_Port) if socket = -1

   ExitApp

Process, Exist DetectHiddenWindows On ScriptMainWindowId := WinExist("ahk_class AutoHotkey ahk_pid " . ErrorLevel) DetectHiddenWindows Off

NotificationMsg = 0x5555 OnMessage(NotificationMsg, "ReceiveData")

ExitMsg = 0x6666 OnMessage(ExitMsg, "ExitData")

FD_READ = 1 FD_CLOSE = 32 FD_CONNECT = 20

if DllCall("Ws2_32\WSAAsyncSelect", "UInt", socket, "UInt", ScriptMainWindowId, "UInt", ExitMsg, "Int", FD_CLOSE) { msgbox, closed }

if DllCall("Ws2_32\WSAAsyncSelect", "UInt", socket, "UInt", ScriptMainWindowId, "UInt", NotificationMsg, "Int", FD_READ|FD_CONNECT) {

 MsgBox % "WSAAsyncSelect() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
 DllCall("Ws2_32\WSAAsyncSelect", "UInt", socket, "UInt", ScriptMainWindowId, "UInt", ExitMsg, "Int", FD_CLOSE)
   ExitApp

}

SetTimer, NewConnectionCheck, 500 return

PrepareForIncomingConnection(IPAddress, Port) {

   VarSetCapacity(wsaData, 32)
   result := DllCall("Ws2_32\WSAStartup", "UShort", 0x0002, "UInt", &wsaData)
   if ErrorLevel
   {
       MsgBox WSAStartup() could not be called due to error %ErrorLevel%. Winsock 2.0 or higher is required.
       return -1
   }
   if result
   {
       MsgBox % "WSAStartup() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
       return -1
   }
   AF_INET = 2
   SOCK_STREAM = 1
   IPPROTO_TCP = 6
   socket := DllCall("Ws2_32\socket", "Int", AF_INET, "Int", SOCK_STREAM, "Int", IPPROTO_TCP)
   if socket = -1
   {
       MsgBox % "socket() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
       return -1
   }
   SizeOfSocketAddress = 16
   VarSetCapacity(SocketAddress, SizeOfSocketAddress)
   InsertInteger(2, SocketAddress, 0, AF_INET)
   InsertInteger(DllCall("Ws2_32\htons", "UShort", Port), SocketAddress, 2, 2)
   InsertInteger(DllCall("Ws2_32\inet_addr", "Str", IPAddress), SocketAddress, 4, 4)
   if DllCall("Ws2_32\bind", "UInt", socket, "UInt", &SocketAddress, "Int", SizeOfSocketAddress)
   {
       MsgBox % "bind() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") . "?"
       return -1
   }
   if DllCall("Ws2_32\listen", "UInt", socket, "UInt", "SOMAXCONN")
   {
       MsgBox % "LISTEN() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") . "?"
       return -1
   }
   return socket

}

ReceiveData(wParam, lParam) {

   global DataReceived
   global NewData
   global mydata
   global ConnectionList
   socket := wParam
   ReceivedDataSize = 4096
   Loop
   {
       VarSetCapacity(ReceivedData, ReceivedDataSize, 0)
       ReceivedDataLength := DllCall("Ws2_32\recv", "UInt", socket, "Str", ReceivedData, "Int", ReceivedDataSize, "Int", 0)

if ReceivedDataLength = 0

{
  
  StringReplace, ConnectionList, ConnectionList, %socket%`n
  DllCall("Ws2_32\closesocket", "UInt", socket)
}

       if ReceivedDataLength = -1
       {
           WinsockError := DllCall("Ws2_32\WSAGetLastError")
           if WinsockError = 10035
           {
               DataReceived = %TempDataReceived%
               NewData := true
               return 1
           }
           if WinsockError <> 10054
           {
               MsgBox % "recv() indicated Winsock error " . WinsockError
               StringReplace, ConnectionList, ConnectionList, %socket%`n
               DllCall("Ws2_32\closesocket", "UInt", socket)
           }

}

  mydata := ReceivedData
  gosub myreceive

if (A_Index = 1)

           TempDataReceived =
       TempDataReceived = %TempDataReceived%%ReceivedData%
   }
   return 1

}

ExitData(wParam, lParam) {

   global ConnectionList
   socket := wParam
   ReceivedDataSize = 16
   VarSetCapacity(ReceivedData, ReceivedDataSize, 0)
   ReceivedDataLength := DllCall("Ws2_32\recv", "UInt", socket, "Str", ReceivedData, "Int", ReceivedDataSize, "Int", 0)
   StringReplace, ConnectionList, ConnectionList, %socket%`n
   DllCall("Ws2_32\closesocket", "UInt", socket)
   return 1

}

SendData(wParam,SendData) {

   SendDataSize := VarSetCapacity(SendData)
   SendDataSize += 1
   Loop, parse, wParam, `n
   {
       If A_LoopField =
           Continue
       socket := A_LoopField
       sendret := DllCall("Ws2_32\send", "UInt", socket, "Str", SendData, "Int", SendDatasize, "Int", 0)
   }

}


InsertInteger(pInteger, ByRef pDest, pOffset = 0, pSize = 4) {

   Loop %pSize%
       DllCall("RtlFillMemory", "UInt", &pDest + pOffset + A_Index-1, "UInt", 1, "UChar", pInteger >> 8*(A_Index-1) & 0xFF)

}

NewConnectionCheck: ConnectionCheck := DllCall("Ws2_32\accept", "UInt", socket, "UInt", &SocketAddress, "Int", SizeOfSocketAddress) if ConnectionCheck > 1

   ConnectionList = %ConnectionList%%ConnectionCheck%`n

Return

SendProcedure: If ConnectionList <> {

   SendText = %A_Hour%:%A_Min%:%A_Sec%
   SendData(ConnectionList,SendText)

} Return

myreceive:

TrayTip, server, %mydata%, ,16
 return

GuiClose: ExitSub: DllCall("Ws2_32\WSACleanup") ExitApp</lang> echoclient.ahk <lang AutoHotkey>#SingleInstance OFF Network_Port = 12321 Network_Address = 127.0.0.1 NewData := false DataReceived = GoSub, Connection_Init loop gosub guisend return

guisend: inputbox, SendText SendData(socket,SendText) SentText = return

Connection_Init: OnExit, ExitSub

socket := ConnectToAddress(Network_Address, Network_Port) if socket = -1

   ExitApp

Process, Exist DetectHiddenWindows On ScriptMainWindowId := WinExist("ahk_class AutoHotkey ahk_pid " . ErrorLevel) DetectHiddenWindows Off

NotificationMsg = 0x5556 OnMessage(NotificationMsg, "ReceiveData")

FD_READ = 1 FD_CLOSE = 32 if DllCall("Ws2_32\WSAAsyncSelect", "UInt", socket, "UInt", ScriptMainWindowId, "UInt", NotificationMsg, "Int", FD_READ|FD_CLOSE) {

   MsgBox % "WSAAsyncSelect() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
   ExitApp

}

return


ConnectToAddress(IPAddress, Port) {

   VarSetCapacity(wsaData, 32)
   result := DllCall("Ws2_32\WSAStartup", "UShort", 0x0002, "UInt", &wsaData)
   if ErrorLevel
   {
       MsgBox WSAStartup() could not be called due to error %ErrorLevel%. Winsock 2.0 or higher is required.
       return -1
   }
   if result
   {
       MsgBox % "WSAStartup() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
       return -1
   }
   AF_INET = 2
   SOCK_STREAM = 1
   IPPROTO_TCP = 6
   socket := DllCall("Ws2_32\socket", "Int", AF_INET, "Int", SOCK_STREAM, "Int", IPPROTO_TCP)
   if socket = -1
   {
       MsgBox % "socket() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
       return -1
   }
   SizeOfSocketAddress = 16
   VarSetCapacity(SocketAddress, SizeOfSocketAddress)
   InsertInteger(2, SocketAddress, 0, AF_INET)
   InsertInteger(DllCall("Ws2_32\htons", "UShort", Port), SocketAddress, 2, 2)
   InsertInteger(DllCall("Ws2_32\inet_addr", "Str", IPAddress), SocketAddress, 4, 4)
   if DllCall("Ws2_32\connect", "UInt", socket, "UInt", &SocketAddress, "Int", SizeOfSocketAddress)
   {
       MsgBox % "connect() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") . "?"
       return -1
   }
   return socket

}

ReceiveData(wParam, lParam) {

   global DataReceived
   global NewData
   socket := wParam
   ReceivedDataSize = 4096
   Loop
   {
       VarSetCapacity(ReceivedData, ReceivedDataSize, 0)
       ReceivedDataLength := DllCall("Ws2_32\recv", "UInt", socket, "Str", ReceivedData, "Int", ReceivedDataSize, "Int", 0)
       if ReceivedDataLength = 0
           ExitApp
       if ReceivedDataLength = -1
       {
           WinsockError := DllCall("Ws2_32\WSAGetLastError")
           if WinsockError = 10035
           {
               DataReceived = %TempDataReceived%
               NewData := true
               return 1
           }
           if WinsockError <> 10054
               MsgBox % "recv() indicated Winsock error " . WinsockError
           ExitApp
       }
       if (A_Index = 1)
           TempDataReceived =
       TempDataReceived = %TempDataReceived%%ReceivedData%
   }
   return 1

}

SendData(wParam,SendData) {

   socket := wParam
   SendDataSize := VarSetCapacity(SendData)
   SendDataSize += 1
   sendret := DllCall("Ws2_32\send", "UInt", socket, "Str", SendData, "Int", SendDatasize, "Int", 0)

}

InsertInteger(pInteger, ByRef pDest, pOffset = 0, pSize = 4) {

   Loop %pSize%
       DllCall("RtlFillMemory", "UInt", &pDest + pOffset + A_Index-1, "UInt", 1, "UChar", pInteger >> 8*(A_Index-1) & 0xFF)

}

ReceiveProcedure:

   if NewData
       GuiControl, , ReceivedText, %DataReceived%
   NewData := false

Return

ExitSub: DllCall("Ws2_32\WSACleanup") ExitApp</lang>

C

Works with: POSIX

This is a rather standard code (details apart); the reference guide for such a code is the Beej's Guide to Network programming. The dependency from POSIX is mainly in the use of the read and write functions, (using the socket as a file descriptor sometimes make things simpler).

<lang c>#include <stdio.h>

  1. include <stdlib.h>
  2. include <string.h>
  3. include <errno.h>
  4. include <sys/types.h>
  5. include <sys/socket.h>
  6. include <netdb.h>
  7. include <unistd.h>
  8. include <sys/wait.h>
  9. include <signal.h>
  1. define MAX_ENQUEUED 20
  2. define BUF_LEN 256
  3. define PORT_STR "12321"

void wait_for_zombie(int s) {

  while(waitpid(-1, NULL, WNOHANG) > 0) ;

}

int main() {

   struct addrinfo hints, *res;
   struct sockaddr addr;
   struct sigaction sa;
   socklen_t addr_size;
   int sock;
   memset(&hints, 0, sizeof(struct addrinfo));
   hints.ai_family = AF_UNSPEC;
   hints.ai_socktype = SOCK_STREAM;
   hints.ai_flags = AI_PASSIVE;
   if ( getaddrinfo(NULL, PORT_STR, &hints, &res) != 0 ) {
       perror("getaddrinfo");
       exit(EXIT_FAILURE);
   }
   if ( (sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1 ) {
       perror("socket");
       exit(EXIT_FAILURE);
   }
   sa.sa_handler = wait_for_zombie;
   sigemptyset(&sa.sa_mask);
   sa.sa_flags = SA_RESTART;
   if ( sigaction(SIGCHLD, &sa, NULL) == - 1 ) {
       perror("sigaction");
       exit(EXIT_FAILURE);
   }
   if ( bind(sock, res->ai_addr, res->ai_addrlen) == 0 ) {
       freeaddrinfo(res);
       if ( listen(sock, MAX_ENQUEUED) == 0 ) {
           /* Serve the listening socket infinitely often */
           for(;;) {
               addr_size = sizeof(addr);
               int csock = accept(sock, &addr, &addr_size);
               if ( csock == -1 ) {
                   perror("accept");
               } else {
                   if ( fork() == 0 ) {
                       close(sock);
                       /* Echo loop */
                       char buf[BUF_LEN];
                       int r;
                       while( (r = read(csock, buf, BUF_LEN)) > 0 ) {
                           (void)write(csock, buf, r);
                       }
                       exit(EXIT_SUCCESS);
                   }
                   close(csock);
               }
           }
       } else {
           perror("listen");
           exit(EXIT_FAILURE);
       }
   } else {
       perror("bind");
       exit(EXIT_FAILURE);
   }
   return EXIT_SUCCESS;

}</lang>

Common Lisp

Sockets is not a standard part of Common Lisp but many implementations have support for this. The following example

Works with: CLISP

<lang lisp>(defvar *clients* '()

   "This is a list of (socket :input status) which is used with

`socket:socket-status' to test for data ready on a socket.")

(defun echo-server (port)

   "Listen on `port' for new client connections and for data arriving on

any existing client connections"

   (let ((server (socket:socket-server port)))
       (format t "Echo service listening on port ~a:~d~%"
           (socket:socket-server-host server)
           (socket:socket-server-port server))
       (unwind-protect
           (loop 
               (when (socket:socket-status server 0 1)
                   (echo-accept-client (socket:socket-accept server 
                                           :external-format :dos
                                           :buffered t)))
               (when *clients*
                   (socket:socket-status *clients* 0 1)
                   (mapcar #'(lambda (client)
                                 (when (eq :input (cddr client))
                                     (echo-service-client (car client)))
                                 (when (eq :eof (cddr client))
                                     (echo-close-client (car client)))) *clients*)))
           (socket-server-close server))))

(defun echo-accept-client (socket)

   "Accept a new client connection and add it to the watch list."
   (multiple-value-bind 
       (host port) (socket:socket-stream-peer socket)
       (format t "Connect from ~a:~d~%" host port))
   (push (list socket :input nil) *clients*))
   

(defun echo-service-client (socket)

   (let ((line (read-line socket nil nil)))
       (princ line socket)
       (finish-output socket)))

(defun echo-close-client (socket)

   "Close a client connection and remove it from the watch list."
   (multiple-value-bind 
       (host port) (socket:socket-stream-peer socket)
       (format t "Closing connection from ~a:~d~%" host port))
   (close socket)
   (setq *clients* (remove socket *clients* :key #'car)))

(echo-server 12321) </lang>

Forth

Works with: GNU Forth version 0.7.0

<lang forth> include unix/socket.fs

128 constant size

(echo) ( sock buf -- sock buf )
 begin
   cr ." waiting..."
   2dup 2dup size read-socket nip
   dup 0>
 while
   ."  got: " 2dup type
   rot write-socket
 repeat
 drop drop drop ;

create buf size allot

echo-server ( port -- )
 cr ." Listening on " dup .
 create-server
 dup 4 listen
 begin
   dup accept-socket
   cr ." Connection!"
   buf ['] (echo) catch
   cr ." Disconnected (" . ." )"
   drop close-socket
 again ;

12321 echo-server </lang> TODO: use tasker.fs and non-blocking semantics to handle mutliple connections

Haskell

<lang haskell>module Main where import Network (withSocketsDo, accept, listenOn, sClose, PortID(PortNumber)) import Control.Monad (forever) import System.IO (hGetLine, hPutStrLn, hFlush, hClose) import System.IO.Error (isEOFError) import Control.Concurrent (forkIO) import Control.Exception (bracket)

-- For convenience in testing, ensure that the listen socket is closed if the main loop is aborted withListenOn port body = bracket (listenOn port) sClose body

echo (handle, host, port) = catch (forever doOneLine) stop where

 doOneLine = do line <- hGetLine handle
                print (host, port, init line)
                hPutStrLn handle line
                hFlush handle
 stop error = do putStrLn $ "Closed connection from " ++ show (host, port) ++ " due to " ++ show error
                 hClose handle

main = withSocketsDo $

 withListenOn (PortNumber 12321) $ \listener ->
   forever $ do
     acc@(_, host, port) <- accept listener
     putStrLn $ "Accepted connection from " ++ show (host, port)
     forkIO (echo acc)</lang>

Python

Works with: Python version 2.3 or above

<lang python>import SocketServer

HOST = "localhost" PORT = 12321

  1. this server uses ThreadingMixIn - one thread per connection
  2. replace with ForkMixIn to spawn a new process per connection

class EchoServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):

   # no need to override anything - default behavior is just fine
   pass

class EchoRequestHandler(SocketServer.StreamRequestHandler):

   """
   Handles one connection to the client.
   """
   def handle(self):
       print "connection from %s" % self.client_address[0]
       while True:
           line = self.rfile.readline()
           if not line: break
           print "%s wrote: %s" % (self.client_address[0], line.rstrip())
           self.wfile.write(line)
       print "%s disconnected" % self.client_address[0]


  1. Create the server

server = EchoServer((HOST, PORT), EchoRequestHandler)

  1. Activate the server; this will keep running until you
  2. interrupt the program with Ctrl-C

print "server listening on %s:%s" % server.server_address server.serve_forever()</lang>

Ruby

Note: largely untested: may not handle multiple simultaneously connections well. <lang ruby>require 'socket' server = TCPServer.new('localhost', 12321) while (session = server.accept)

 client_port, client_host = session.peeraddr[1..2]
 puts "activity from #{client_host}:#{client_port}"
 line = session.gets
 if line.nil?
   session.close
 else
   session.puts(line)
 end

end</lang>

Tcl

<lang tcl># How to handle an incoming new connection proc acceptEcho {chan host port} {

   puts "opened connection from $host:$port"
   fconfigure $chan -blocking 0 -buffering line -translation crlf
   fileevent $chan readable [list echo $chan $host $port]

}

  1. How to handle an incoming message on a connection

proc echo {chan host port} {

   if {[gets $chan line] >= 0} {
       puts $chan $line
   } elseif {[eof $chan]} {
       close $chan
       puts "closed connection from $host:$port"
   }
   # Other conditions causing a short read need no action

}

  1. Make the server socket and wait for connections

socket -server acceptEcho -myaddr localhost 12321 vwait forever</lang>

Alternative version

A more succinct version (though one harder to adapt to other kinds of services, but closer to the standard unix echo daemon since it has no line-buffering) is to use an asynchronous binary copy. <lang tcl># How to handle an incoming new connection proc acceptEcho {chan host port} {

   puts "opened connection from $host:$port"
   fconfigure $chan -translation binary -buffering none
   fcopy $chan $chan -command [list done $chan $host $port]

}

  1. Called to finalize the connection

proc done {chan host port args} {

   puts "closed connection from $host:$port"
   close $chan

}

  1. Make the server socket and wait for connections

socket -server acceptEcho -myaddr localhost 12321 vwait forever</lang>