Echo server

From Rosetta Code

Jump to: navigation, search
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 or multi-process solution may be used. Each connection must be able to echo more than a single line.

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

Contents

[edit] AutoHotkey

echoserver.ahk, modified from script by zed gecko.

#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

A client is also available for testing this code.

[edit] 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).

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
 
#define MAX_ENQUEUED 20
#define BUF_LEN 256
#define PORT_STR "12321"
 
/* ------------------------------------------------------------ */
/* How to clean up after dead child processes */
/* ------------------------------------------------------------ */
 
void wait_for_zombie(int s)
{
while(waitpid(-1, NULL, WNOHANG) > 0) ;
}
 
/* ------------------------------------------------------------ */
/* Core of implementation of a child process */
/* ------------------------------------------------------------ */
 
void echo_lines(int csock)
{
char buf[BUF_LEN];
int r;
 
while( (r = read(csock, buf, BUF_LEN)) > 0 ) {
(void)write(csock, buf, r);
}
exit(EXIT_SUCCESS);
}
 
/* ------------------------------------------------------------ */
/* Core of implementation of the parent process */
/* ------------------------------------------------------------ */
 
void take_connections_forever(int ssock)
{
for(;;) {
struct sockaddr addr;
socklen_t addr_size = sizeof(addr);
int csock;
 
/* Block until we take one connection to the server socket */
csock = accept(ssock, &addr, &addr_size);
 
/* If it was a successful connection, spawn a worker process to service it */
if ( csock == -1 ) {
perror("accept");
} else if ( fork() == 0 ) {
close(ssock);
echo_lines(csock);
} else {
close(csock);
}
}
}
 
/* ------------------------------------------------------------ */
/* The server process's one-off setup code */
/* ------------------------------------------------------------ */
 
int main()
{
struct addrinfo hints, *res;
struct sigaction sa;
int sock;
 
/* Look up the address to bind to */
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);
}
 
/* Make a socket */
if ( (sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1 ) {
perror("socket");
exit(EXIT_FAILURE);
}
 
/* Arrange to clean up child processes (the workers) */
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);
}
 
/* Associate the socket with its address */
if ( bind(sock, res->ai_addr, res->ai_addrlen) != 0 ) {
perror("bind");
exit(EXIT_FAILURE);
}
 
freeaddrinfo(res);
 
/* State that we've opened a server socket and are listening for connections */
if ( listen(sock, MAX_ENQUEUED) != 0 ) {
perror("listen");
exit(EXIT_FAILURE);
}
 
/* Serve the listening socket until killed */
take_connections_forever(sock);
return EXIT_SUCCESS;
}

[edit] C#

 
using System.Net.Sockets;
using System.Threading;
 
namespace ConsoleApplication1
{
class Program
{
static TcpListener listen;
static Thread serverthread;
 
static void Main(string[] args)
{
listen = new TcpListener(System.Net.IPAddress.Parse("127.0.0.1"), 12321);
serverthread = new Thread(new ThreadStart(DoListen));
serverthread.Start();
}
 
private static void DoListen()
{
// Listen
listen.Start();
Console.WriteLine("Server: Started server");
 
while (true)
{
Console.WriteLine("Server: Waiting...");
TcpClient client = listen.AcceptTcpClient();
Console.WriteLine("Server: Waited");
 
// New thread with client
Thread clientThread = new Thread(new ParameterizedThreadStart(DoClient));
clientThread.Start(client);
}
}
 
private static void DoClient(object client)
{
// Read data
TcpClient tClient = (TcpClient)client;
 
Console.WriteLine("Client (Thread: {0}): Connected!", Thread.CurrentThread.ManagedThreadId);
do
{
if (!tClient.Connected)
{
tClient.Close();
Thread.CurrentThread.Abort(); // Kill thread.
}
 
if (tClient.Available > 0)
{
// Resend
byte pByte = (byte)tClient.GetStream().ReadByte();
Console.WriteLine("Client (Thread: {0}): Data {1}", Thread.CurrentThread.ManagedThreadId, pByte);
tClient.GetStream().WriteByte(pByte);
}
 
// Pause
Thread.Sleep(100);
} while (true);
}
}
}
 

[edit] Clojure

(use '[clojure.contrib.server-socket :only (create-server)])
(use '[clojure.contrib.duck-streams :only (read-lines write-lines)])
 
(defn echo [input output]
(write-lines (java.io.PrintWriter. output true) (read-lines input)))
 
(create-server 12321 echo)

Note here that an auto-flushing PrintWriter needs to be created, otherwise 'output' could simply be passed to write-lines.

[edit] Common Lisp

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

(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)

[edit] Erlang

 
-module(echo).
-export([start/0]).
 
start() ->
spawn(fun () -> {ok, Sock} = gen_tcp:listen(12321, [{packet, line}]),
echo_loop(Sock)
end).
 
echo_loop(Sock) ->
{ok, Conn} = gen_tcp:accept(Sock),
io:format("Got connection: ~p~n", [Conn]),
Handler = spawn(fun () -> handle(Conn) end),
gen_tcp:controlling_process(Conn, Handler),
echo_loop(Sock).
 
handle(Conn) ->
receive
{tcp, Conn, Data} ->
gen_tcp:send(Conn, Data),
handle(Conn);
{tcp_closed, Conn} ->
io:format("Connection closed: ~p~n", [Conn])
end.
 

[edit] Factor

Connections get logged to /place-where-factor-is/logs/echo-server.

USING: accessors io io.encodings.utf8 io.servers.connection
threads ;
IN: rosetta.echo
 
CONSTANT: echo-port 12321
 
: handle-client ( -- )
[ write "\r\n" write flush ] each-line ;
 
: <echo-server> ( -- threaded-server )
utf8 <threaded-server>
"echo-server" >>name
echo-port >>insecure
[ handle-client ] >>handler ;
 
: start-echo-server ( -- threaded-server )
<echo-server> [ start-server ] in-thread ;

[edit] Forth

Works with: GNU Forth version 0.7.0

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

TODO: use tasker.fs and non-blocking semantics to handle mutliple connections

[edit] Go

package main
 
import (
"fmt"
"net"
"bufio"
)
 
func echo(s net.Conn, i int) {
fmt.Printf("%d: %v <-> %v\n", i, s.LocalAddr(), s.RemoteAddr())
b := bufio.NewReader(s)
for {
line, e := b.ReadBytes('\n')
if e != nil {
break
}
s.Write(line)
}
fmt.Printf("%d: closed\n", i)
}
 
func main() {
l, e := net.Listen("tcp", ":12321")
for i := 0; e == nil; i++ {
var s net.Conn
s, e = l.Accept()
go echo(s, i)
}
}
 

[edit] 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)

[edit] Java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
 
public class EchoServer {
ServerSocket serverSocket;
public EchoServer(){
}
 
public void start() {
try {
serverSocket = new ServerSocket(12321);
while(true){
Thread clientThread = new Thread(new ClientHandler(serverSocket.accept()));
clientThread.start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
System.out.println("closing server socket");
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
 
}
 
public static void main(String[] args) {
EchoServer es = new EchoServer();
es.start();
}
}
 
class ClientHandler implements Runnable {
private static int numConnections;
private int connectionId = 0;
Socket clientSocket;
 
public ClientHandler(Socket s) {
connectionId = numConnections++;
System.out.println("handling connection, #" + connectionId);
clientSocket = s;
}
 
public void run() {
PrintWriter out = null;
BufferedReader in = null;
try {
out = new PrintWriter(clientSocket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
String inputLine, outputLine;
while((inputLine = in.readLine()) != null){
outputLine = inputLine;
System.out.println("recieved: " + outputLine);
out.write(outputLine+"\n");
out.flush();
if (outputLine.equals("exit"))
break;
}
} catch(Exception e) {
e.printStackTrace();
} finally {
out.close();
try {
in.close();
clientSocket.close();
System.out.println("closing connection, #" + connectionId);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

[edit] Oz

declare
ServerSocket = {New Open.socket init}
 
proc {Echo Socket}
case {Socket getS($)} of false then skip
[] Line then
{System.showInfo "Received line: "#Line}
{Socket write(vs:Line#"\n")}
{Echo Socket}
end
end
 
class TextSocket from Open.socket Open.text end
in
{ServerSocket bind(takePort:12321)}
{System.showInfo "Socket bound."}
 
{ServerSocket listen}
{System.showInfo "Started listening."}
 
for do
ClientHost ClientPort
ClientSocket = {ServerSocket accept(accepted:$
acceptClass:TextSocket
host:?ClientHost
port:?ClientPort
)}
in
{System.showInfo "Connection accepted from "#ClientHost#":"#ClientPort#"."}
thread
{Echo ClientSocket}
 
{System.showInfo "Connection lost: "#ClientHost#":"#ClientPort#"."}
{ClientSocket close}
end
end

Client test code:

declare
Socket = {New class $ from Open.socket Open.text end init}
in
{Socket connect(port:12321)}
{Socket write(vs:"Hello\n")}
{System.showInfo "Client received: "#{Socket getS($)}}
{Socket close}

Example session:

Socket bound.
Started listening.
Connection accepted from localhost:2048.
Received line: Hello
Client received: Hello
Connection lost: localhost:2048.

[edit] Perl

This server will run indefinitely listening in the port 12321 and forking every time a client connects, the childs listen to the client and write back.

This is an example using the IO::Socket module:

use IO::Socket;
my $sock = new IO::Socket::INET (
LocalHost => '127.0.0.1',
LocalPort => '12321',
Proto => 'tcp',
Listen => 1, # maximum queued connections
Reuse => 1,
);
die "Could not create socket: $!\n" unless $sock;
 
print "server is waiting clients..\n";
my $con;
while(1)
{
$con = $sock->accept();
last if !fork; # create a child to listen to the new client
# and keep waiting more clients
}
 
print "child listening..\n";
 
print $con $_ while(<$con>);
 
print "child dead\n";
close($sock);

This is an equivalent program using the Net::Server module:

package Echo;
use base 'Net::Server::Fork';
sub process_request {
print while <STDIN>;
}
Echo->run(port => 12321, log_level => 3);

It also prints the IP address and port number of every connection.

This is a more complicated example using preforking:

package Echo;
use base 'Net::Server::PreFork';
sub process_request {
print while <STDIN>;
}
Echo->run(port => 12321, log_level => 3);

By default it spawns 5 child processes at startup, makes sure there are always at least 2 and at most 10 spare children available for new requests, each of which will be killed after processing 1000 requests and new ones will take their place.

[edit] PHP

$socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
socket_bind($socket, '127.0.0.1', 12321);
socket_listen($socket);
 
$client_count = 0;
while (true){
if (($client = socket_accept($socket)) === false) continue;
$client_count++;
 
$client_name = 'Unknown';
socket_getpeername($client, $client_name);
echo "Client {$client_count} ({$client_name}) connected\n";
$pid = pcntl_fork();
if($pid == -1) die('Could not fork');
if($pid){
pcntl_waitpid(-1, $status, WNOHANG);
continue;
}
 
//In a child process
while(true){
if($input = socket_read($client, 1024)){
socket_write($client, $input);
} else {
socket_shutdown($client);
socket_close($client);
echo "Client {$client_count} ({$client_name}) disconnected\n";
exit();
}
}
}

[edit] PicoLisp

(setq Port (port 12321))
 
(loop
(setq Sock (listen Port)) # Listen
(NIL (fork) (close Port)) # Accepted
(close Sock) ) # Parent: Close socket and continue
 
# Child:
(prinl (stamp) " -- (Pid " *Pid ") Client connected from " *Adr)
 
(in Sock
(until (eof) # Echo lines
(out Sock (prinl (line))) ) )
 
(prinl (stamp) " -- (Pid " *Pid ") Client disconnected")
(bye) # Terminate child

[edit] Python

Works with: Python version 2.3 or above

import SocketServer
 
HOST = "localhost"
PORT = 12321
 
# this server uses ThreadingMixIn - one thread per connection
# 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]
 
 
# Create the server
server = EchoServer((HOST, PORT), EchoRequestHandler)
 
# Activate the server; this will keep running until you
# interrupt the program with Ctrl-C
print "server listening on %s:%s" % server.server_address
server.serve_forever()

[edit] REBOL

server-port: open/lines tcp://:12321
forever [
connection-port: first server-port
until [
wait connection-port
error? try [insert connection-port first connection-port]
]
close connection-port
]
close server-port

[edit] Ruby

require 'socket'
server = TCPServer.new(12321)
 
while (connection = server.accept)
Thread.new(connection) do |conn|
port, host = conn.peeraddr[1,2]
client = "#{host}:#{port}"
puts "#{client} is connected"
begin
loop do
line = conn.readline
puts "#{client} says: #{line}"
conn.puts(line)
end
rescue EOFError
conn.close
puts "#{client} has disconnected"
end
end
end

[edit] PureBasic

NewMap RecData.s()
OpenWindow(0, 100, 200, 200, 100, "Echo Server", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget )
InitNetwork()
CreateNetworkServer(1, 12321)
 
Repeat
Event = NetworkServerEvent()
ClientID = EventClient()
 
If Event = #PB_NetworkEvent_Connect ; When a new client has been connected...
AddMapElement(RecData(), Str(ClientID))
 
ElseIf Event = #PB_NetworkEvent_Data
*Buffer = AllocateMemory(20000)
count = ReceiveNetworkData(ClientID, *Buffer, 20000)
For i = 1 To count
RecData(Str(ClientID)) + Mid( PeekS(*Buffer, count), i , 1)
If Right( RecData(Str(ClientID)), 2) = #CRLF$
SendNetworkString (ClientID, RecData(Str(ClientID)))
Debug IPString(GetClientIP(ClientID)) + ":" + Str(GetClientPort(ClientID)) + " " + RecData(Str(ClientID))
RecData(Str(ClientID)) = ""
EndIf
Next
FreeMemory(*Buffer)
 
ElseIf Event = #PB_NetworkEvent_Disconnect ; When a client has closed the connection...
DeleteMapElement(RecData(), Str(ClientID))
EndIf
 
Event = WaitWindowEvent(10)
Until Event = #PB_Event_CloseWindow

[edit] Tcl

This code is single-threaded. It uses non-blocking I/O to perform the transfers, sitting on top of the event multiplexer system call (e.g., select() on Unix) to decide when to take new connections or service a particular socket. This makes this into a very lightweight echo server in terms of overall system resources.

# 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]
}
 
# 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
}
 
# Make the server socket and wait for connections
socket -server acceptEcho -myaddr localhost 12321
vwait forever

[edit] 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.

# 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]
}
 
# Called to finalize the connection
proc done {chan host port args} {
puts "closed connection from $host:$port"
close $chan
}
 
# Make the server socket and wait for connections
socket -server acceptEcho -myaddr localhost 12321
vwait forever
Personal tools
Support