Sockets 3 - plain sockets text protocol

Content:

  1. Introduction
  2. TCP and socket API
  3. Example protocol
  4. Client
  5. Singlethreaded server
  6. Multithreaded server
  7. Asynchroneous server

For other articles in series see:

Introduction:

TCP sockets is the foundation for most network communication today. And even though applications often use higher level API's, then understanding the foundation is beneficial. Both to be able to understand why higher level API's are as they are and to be able to use TCP sockets directly if necessary.

TCP and socket API:

One very important aspect to understand is that the TCP protocol and the socket API are stream oriented.

This mean that sender write chunks of bytes to the stream and receiver read chunks of bytes from the stream, but the receivers chunks can and often are different from the senders chunks.

Example:

This means that receiver can not use chunk sizes read to determine any type of data lengths.

Instead more explict management of data length has to be used:

The current socket API is very old. It goes back to Berkely sockets in BSD Unix in 1983. Since then it has been implemented on most platforms including other Unix, Linux, VMS etc.. Even Windows has a 99% compatible implementation Winsocket.

Example protoco:l

The text protocol used in all examples are very simple:

  1. client connect to server
  2. client send a text line with integer space integer CR LF
  3. server send a text line with integer with sum of clients two integers CR LF
  4. client send a text line with string CR LF
  5. server send a text line with string of duplicated string CR LF
  6. client and server closes connection

Client:

Client logic is:

connect
send/write
receive/read
close

Socket and ServerSocket classes has been in Java since version 1.0. Java also makes it easy to wrap the Socket in BufferedReader and PrintStream to read and write text data.

NIO does not does not provide any nice features for reading lines of text.

package socket.plain.text;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;

public class Client {
    private static final String HOST = "localhost";
    private static final int PORT = 12345;
    public static void main(String[] args) {
        try {
            // open connection
            Socket s = new Socket(HOST, PORT);
            // get reader and writer
            BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream(), "UTF-8"));
            PrintStream ps = new PrintStream(s.getOutputStream(), false,  "UTF-8");
            System.out.println("(connected)");
            // send a and b
            int a = 123;
            int b = 456;
            ps.println(a + " " + b);
            ps.flush();
            // receive c and print
            String line = br.readLine();
            int c = Integer.parseInt(line);
            System.out.println(c);
            // send v
            String v = "ABC";
            ps.println(v);
            // receive v2 and print
            String v2 = br.readLine();
            System.out.println(v2);
            // close connection
            br.close();
            ps.close();
            s.close();
        } catch (IOException e) {
            // TODO
            e.printStackTrace();
        }
    }
}

.NET provide two levels of socket API:

Only the high level API is easy to use for reading/writing text lines.

using System;
using System.IO;
using System.Net.Sockets;

namespace SocketDemo.Text.High.Client
{
    public class Program
    {
        private const string HOST = "localhost";
        private const int PORT = 12345;
        public static void Main(string[] args)
        {
            // open connection
            TcpClient cli = new TcpClient(HOST, PORT);
            // get reader and writer
            NetworkStream stm = cli.GetStream();
            StreamReader sr = new StreamReader(stm);
            StreamWriter sw = new StreamWriter(stm);
            Console.WriteLine("(connected)");
            // send a and b
            int a = 123;
            int b = 456;
            sw.WriteLine(a + " " + b);
            sw.Flush();
            // receive c and print
            int c = int.Parse(sr.ReadLine());
            Console.WriteLine(c);
            // send v
            string v = "ABC";
            sw.WriteLine(v);
            sw.Flush();
            // receive v2 and print
            string v2 = sr.ReadLine();
            Console.WriteLine(v2);
            // close connection
            sr.Close();
            sw.Close();
            cli.Close();
            Console.ReadKey();
        }
    }
}

.NET provide two levels of socket API:

Only the high level API is easy to use for reading/writing text lines.

Imports System
Imports System.IO
Imports System.Net.Sockets

Namespace SocketDemo.Text.High.Client
    Public Class Program
        Private Const HOST As String = "localhost"
        Private Const PORT As Integer = 12345
        Public Shared Sub Main(args As String())
            ' open connection
            Dim cli As New TcpClient(HOST, PORT)
            ' get reader and writer
            Dim stm As NetworkStream = cli.GetStream()
            Dim sr As New StreamReader(stm)
            Dim sw As New StreamWriter(stm)
            Console.WriteLine("(connected)")
            ' send a and b
            Dim a As Integer = 123
            Dim b As Integer = 456
            sw.WriteLine(a & " " & b)
            sw.Flush()
            ' receive c and print
            Dim c As Integer = Integer.Parse(sr.ReadLine())
            Console.WriteLine(c)
            ' send v
            Dim v As String = "ABC"
            sw.WriteLine(v)
            sw.Flush()
            ' receive v2 and print
            Dim v2 As String = sr.ReadLine()
            Console.WriteLine(v2)
            ' close connection
            sr.Close()
            sw.Close()
            cli.Close()
            Console.ReadKey()
        End Sub
    End Class
End Namespace

The C API has changed regarding how to lookup information. There is an old style which was created for IPv4 and is known to have some problems with IPv6 - and there is a new style which was created to support both IPv4 and IPv6 nicely.

Always use the new style when writing new code. The old style is only shown to understand existing programs or to develop on very old systems.

This example shows the old style.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef WIN32
#include <winsock2.h>
#else
#include <sys/socket.h>
#include <netdb.h>
#endif

#include <errno.h>

#define HOST "localhost"
#define PORT 12345

void recv_line(int sd, char *buf, int buflen)
{
    int ix;
    ix = 0;
    buf[ix] = '\0';
    while(ix < buflen && strstr(buf, "\r\n") == NULL)
    {
        ix = ix + recv(sd, buf + ix, buflen - ix, 0);
        buf[ix] = '\0';
    }
}

int main()
{
    int sd, status, buflen;
    long int a, b, c;
    char *buf, *v, *v2;
    struct sockaddr_in remote;
    struct hostent *hostinfo;
#ifdef WIN32
    WSADATA WSAData;
    WSAStartup(0x0101, &WSAData);
#endif
    /* lookup host */
    hostinfo = gethostbyname(HOST);
    if(!hostinfo)
    {
        printf("Error looking up host %s: %s\n", HOST, strerror(errno));
        exit(0);
    }
    /* create socket */
    sd = socket(AF_INET, SOCK_STREAM, 0);
    if(sd < 0)
    {
        printf("Error creating socket: %s\n", strerror(errno));
        exit(0);
    }
    /* connect to host */
    remote.sin_family = hostinfo->h_addrtype;
    memcpy(&remote.sin_addr, hostinfo->h_addr_list[0], hostinfo->h_length);
    remote.sin_port = htons(PORT);
    status = connect(sd, (struct sockaddr *)&remote, sizeof(remote));
    if(status != 0)
    {
        printf("Error connecting to host %s port %d: %s\n", HOST, PORT, strerror(errno));
        exit(0);
    }
    /* send a and b */
    a = 123;
    b = 456;
    buflen = 24;
    buf = malloc(buflen);
    sprintf(buf, "%ld %ld\r\n", a, b);
    status = send(sd, buf, strlen(buf), 0);
    if(status < 0)
    {
        printf("Error sending a and b: %s\n", strerror(errno));
        exit(0);
    }
    free(buf);
    /* receive c and print */
    buflen = 13;
    buf = malloc(buflen);
    recv_line(sd, buf, buflen);
    sscanf(buf, "%ld\r\n", &c);
    printf("%ld\n", c);
    free(buf);
    /* send v */
    v = "ABC";
    buflen = strlen(v) + 3;
    buf = malloc(buflen);
    sprintf(buf, "%s\r\n", v);
    status = send(sd, buf, buflen, 0);
    if(status < 0)
    {
        printf("Error sending v: %s\n", strerror(errno));
        exit(0);
    }
    free(buf);
    /* receive v2 and print */
    buflen = 258;
    buf = malloc(buflen);
    recv_line(sd, buf, buflen);
    v2 = malloc(strlen(buf) + 1);
    sscanf(buf, "%s\r\n", v2);
    printf("%s\n", v2);
    free(buf);
    free(v2);
    /* close socket */
#ifdef WIN32
    closesocket(sd);
    WSACleanup();
#else
    close(sd);
#endif
    return 0;
}

The C API has changed regarding how to lookup information. There is an old style which was created for IPv4 and is known to have some problems with IPv6 - and there is a new style which was created to support both IPv4 and IPv6 nicely.

Always use the new style when writing new code. The old style is only shown to understand existing programs or to develop on very old systems.

This example shows the new style.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <netdb.h>
#endif

#include <errno.h>

#define HOST "localhost"
#define PORT "12345"

void recv_line(int sd, char *buf, int buflen)
{
    int ix;
    ix = 0;
    buf[ix] = '\0';
    while(ix < buflen && strstr(buf, "\r\n") == NULL)
    {
        ix = ix + recv(sd, buf + ix, buflen - ix, 0);
        buf[ix] = '\0';
    }
}

int main()
{
    int sd, status, buflen;
    long int a, b, c;
    char *buf, *v, *v2;
    struct addrinfo hints, *res;
#ifdef WIN32
    WSADATA WSAData;
    WSAStartup(0x0101, &WSAData);
#endif
    /* lookup host */
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_flags = 0;
    status = getaddrinfo(HOST, PORT, &hints, &res);
    if(status != 0)
    {
        printf("Error looking up host: %s\n", HOST);
        exit(0);
    }
    /* create socket */
    sd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    if(sd < 0)
    {
        printf("Error creating socket: %s\n", strerror(errno));
        exit(0);
    }
    /* connect to host */
    status = connect(sd, res->ai_addr, res->ai_addrlen);
    if(status != 0)
    {
        printf("Error connecting to host %s port %s: %s\n", HOST, PORT, strerror(errno));
        exit(0);
    }
    /* send a and b */
    a = 123;
    b = 456;
    buflen = 24;
    buf = malloc(buflen);
    sprintf(buf, "%ld %ld\r\n", a, b);
    status = send(sd, buf, strlen(buf), 0);
    if(status < 0)
    {
        printf("Error sending a and b: %s\n", strerror(errno));
        exit(0);
    }
    free(buf);
    /* receive c and print */
    buflen = 13;
    buf = malloc(buflen);
    recv_line(sd, buf, buflen);
    sscanf(buf, "%ld\r\n", &c);
    printf("%ld\n", c);
    free(buf);
    /* send v */
    v = "ABC";
    buflen = strlen(v) + 3;
    buf = malloc(buflen);
    sprintf(buf, "%s\r\n", v);
    status = send(sd, buf, buflen, 0);
    if(status < 0)
    {
        printf("Error sending v: %s\n", strerror(errno));
        exit(0);
    }
    free(buf);
    /* receive v2 and print */
    buflen = 258;
    buf = malloc(buflen);
    recv_line(sd, buf, buflen);
    v2 = malloc(strlen(buf) + 1);
    sscanf(buf, "%s\r\n", v2);
    printf("%s\n", v2);
    free(buf);
    free(v2);
    /* close socket */
#ifdef WIN32
    closesocket(sd);
    WSACleanup();
#else
    close(sd);
#endif
    return 0;
}

Indy 10 has a convenient TIdTcpClient class.

program PlainTextClientHigh;

uses
  IdTCPClient, SysUtils;

const
  HOST = 'localhost';
  PORT = 12345;

var
  cli : TIdTCPClient;
  v, v2 : string;
  a, b, c : integer;

begin
  (* open connection *)
  cli := TIdTCPClient.Create;
  cli.Connect(HOST, PORT);
  (* send a and b *)
  a := 123;
  b := 456;
  cli.Socket.WriteLn(IntToStr(a) + ' ' + IntToStr(b));
  (* receive c and print *)
  c := StrToint(cli.Socket.ReadLn);
  writeln(c);
  (* send v *)
  v := 'ABC';
  cli.Socket.WriteLn(v);
  (* received v2 and print *)
  v2 := cli.Socket.ReadLn;
  writeln(v2);
  (* close connection *)
  cli.Disconnect;
  cli.Free;
end.

Besides Indy it is also possible to use plain WinSock.

program PlainTextClientLow;

uses
  SysUtils,
  Windows,
  WinSock;

type
  bytearray = array of byte;

procedure string2bytearray(s : string; var b : bytearray);

var
  i : integer;

begin
  SetLength(b, Length(s));
  for i := 1 to Length(s) do b[Low(b) + i -1] := ord(s[i]);
end;

function RecvLine(s : TSocket) : string;

var
  res : string;
  buf : bytearray;
  ix : integer;

begin
  SetLength(buf, 256);
  ix := 0;
  while (ix < Length(buf)) and ((buf[ix - 2] <> 13) or (buf[ix - 1] <> 10)) do begin
    ix := ix + Recv(s, buf[ix], Length(buf) - ix, 0);
  end;
  SetString(res, PAnsiChar(@buf[Low(buf)]), ix - 2);
  RecvLine := res;
end;

const
  HOST = 'localhost';
  PORT = 12345;

var
  wsa : TWSAData;
  s : TSocket;
  hostinfo : PHOstEnt;
  remote : TSockAddrIn;
  v, v2 : string;
  status, a, b, c : integer;
  buf : array of byte;

begin
  WSAStartup($101, wsa);
  (* create socket *)
  s := Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if s = INVALID_SOCKET then begin
     writeln('Error creating socket: ' + SysErrorMessage(GetLastError));
     halt;
  end;
  (* connect to host *)
  hostinfo := GetHostByName(HOST);
  remote.sin_family := AF_INET;
  remote.sin_port :=  htons(PORT);
  remote.sin_addr.S_addr := PInAddr(hostinfo^.H_Addr_List^)^.S_addr;
  status := Connect(s, remote, sizeof(remote));
  if status <> 0 then begin
    writeln('Error connecting to host ' + HOST + ' port ' + IntToStr(PORT) + ': ' + SysErrorMessage(GetLastError));
    halt;
  end;
  (* send a and b *)
  a := 123;
  b := 456;
  string2bytearray(IntToStr(a) + ' ' + IntToStr(b) + #13#10, buf);
  status := Send(s, buf[Low(buf)], Length(buf), 0);
  if status < 0 then begin
    writeln('Error sending a and b: ' + SysErrorMessage(GetLastError));
    halt;
  end;
  (* receive c and print *)
  c := StrToInt(RecvLine(s));
  writeln(c);
  (* send v *)
  v := 'ABC';
  string2bytearray(v + #13#10, buf);
  status := Send(s, buf[Low(buf)], Length(buf), 0);
  if status < 0 then begin
    writeln('Error sending v: ' + SysErrorMessage(GetLastError));
    halt;
  end;
  (* receive v2 and print *)
  v2 := RecvLine(s);
  writeln(v2);
  CloseSocket(s);
  WSACleanup;
end.

Singlethreaded server:

Singlethreaded server logic is:

listen
while
    accept
    receive/read
    send/write
    close
endwhile

Socket and ServerSocket classes has been in Java since version 1.0. Java also makes it easy to wrap the Socket in BufferedReader and PrintStream to read and write text data.

NIO does not does not provide any nice features for reading lines of text.

package socket.plain.text;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;

public class SingleServer {
    private static final int PORT = 12345;
    public static void main(String[] args) {
        try {
            // listen on port
            @SuppressWarnings("resource")
            ServerSocket ss = new ServerSocket(PORT);
            while(true) {
                // accept connection
                Socket s = ss.accept();
                System.out.printf("(connection from %s)\n", s.getRemoteSocketAddress());
                // get reader and writer
                BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream(), "UTF-8"));
                PrintStream ps = new PrintStream(s.getOutputStream(), false,  "UTF-8");
                // read a and b
                String line = br.readLine();
                String[] parts = line.split(" ");
                int a = Integer.parseInt(parts[0]);
                int b = Integer.parseInt(parts[1]);
                // calculate c and send
                int c = a + b;
                ps.println(c);
                ps.flush();
                // read v
                String v = br.readLine();
                // calculate v2 and send
                String v2 = v + v;
                ps.println(v2);
                ps.flush();
                // close connection
                br.close();
                ps.close();
                s.close();
            }
        } catch (IOException e) {
            // TODO
            e.printStackTrace();
        }
    }
}

.NET provide two levels of socket API:

Only the high level API is easy to use for reading/writing text lines.

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;

namespace SocketDemo.Text.High.SingleServer
{
    public class Program
    {
        private const int PORT = 12345;
        private const int BACKLOG = 100;
        public static void Main(string[] args)
        {
            // listen on port
            TcpListener srv = new TcpListener(IPAddress.Any, PORT);
            srv.Start(BACKLOG);
            while(true) {
                // accept connection
                TcpClient cli = srv.AcceptTcpClient();
                // get reader and writer
                NetworkStream stm = cli.GetStream();
                StreamReader sr = new StreamReader(stm);
                StreamWriter sw = new StreamWriter(stm);
                Console.WriteLine("(connection from {0})", cli.Client.RemoteEndPoint);
                // read a and b
                string line = sr.ReadLine();
                string[] parts = line.Split(' ');
                int a = int.Parse(parts[0]);
                int b = int.Parse(parts[1]);
                // calculate c and send
                int c = a + b;
                sw.WriteLine(c);
                sw.Flush();
                // read v
                string v = sr.ReadLine();
                // calculate v2 and send
                string v2 = v + v;
                sw.WriteLine(v2);
                sw.Flush();
                // close connection
                sr.Close();
                sw.Close();
                cli.Close();
            }
        }
    }
}

.NET provide two levels of socket API:

Only the high level API is easy to use for reading/writing text lines.

Imports System
Imports System.IO
Imports System.Net
Imports System.Net.Sockets

Namespace SocketDemo.Text.High.SingleServer
    Public Class Program
        Private Const PORT As Integer = 12345
        Private Const BACKLOG As Integer = 100
        Public Shared Sub Main(args As String())
            ' listen on port
            Dim srv As New TcpListener(IPAddress.Any, PORT)
            srv.Start(BACKLOG)
            While True
                ' accept connection
                Dim cli As TcpClient = srv.AcceptTcpClient()
                ' get reader and writer
                Dim stm As NetworkStream = cli.GetStream()
                Dim sr As New StreamReader(stm)
                Dim sw As New StreamWriter(stm)
                Console.WriteLine("(connection from {0})", cli.Client.RemoteEndPoint)
                ' read a and b
                Dim line As String = sr.ReadLine()
                Dim parts As String() = line.Split(" "C)
                Dim a As Integer = Integer.Parse(parts(0))
                Dim b As Integer = Integer.Parse(parts(1))
                ' calculate c and send
                Dim c As Integer = a + b
                sw.WriteLine(c)
                sw.Flush()
                ' read v
                Dim v As String = sr.ReadLine()
                ' calculate v2 and send
                Dim v2 As String = v & v
                sw.WriteLine(v2)
                sw.Flush()
                ' close connection
                sr.Close()
                sw.Close()
                cli.Close()
            End While
        End Sub
    End Class
End Namespace

The C API has changed regarding how to lookup information. There is an old style which was created for IPv4 and is known to have some problems with IPv6 - and there is a new style which was created to support both IPv4 and IPv6 nicely.

Always use the new style when writing new code. The old style is only shown to understand existing programs or to develop on very old systems.

This example shows the old style.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef WIN32
#include <winsock2.h>
#else
#include <sys/socket.h>
#include <netdb.h>
#endif

#include <errno.h>

#define PORT 12345
#define BACKLOG 100

void recv_line(int sd, char *buf, int buflen)
{
    int ix;
    ix = 0;
    buf[ix] = '\0';
    while(ix < buflen && strstr(buf, "\r\n") == NULL)
    {
        ix = ix + recv(sd, buf + ix, buflen - ix, 0);
        buf[ix] = '\0';
    }
}

int main()
{
    int sd, sd2, status, buflen, slen;
    long int a, b, c;
    char *buf, *v, *v2;
    struct sockaddr_in local, remote;
#ifdef WIN32
    WSADATA WSAData;
    if(WSAStartup(0x0101, &WSAData) != 0)
    {
        printf("Error initializing\n");
        exit(0);
    }
#endif
    /* create socket */
    sd = socket(AF_INET, SOCK_STREAM, 0);
    if(sd < 0)
    {
        printf("Error creating socket: %s\n", strerror(errno));
        exit(0);
    }
    /* bind */
    local.sin_family = AF_INET;
    local.sin_port = htons(PORT);
    local.sin_addr.s_addr = INADDR_ANY;
    status = bind(sd, (struct sockaddr *)&local ,sizeof(local));
    if(status < 0)
    {
        printf("Error binding socket: %s\n", strerror(errno));
        exit(0);
    }
    /* listen */
    status = listen(sd, BACKLOG);
    if(status < 0)
    {
        printf("Error listening socket: %s\n", strerror(errno));
        exit(0);
    }
    for(;;)
    {
        /* accept */
        sd2 = accept(sd, 0, 0);
        if(sd2 < 0)
        {
            printf("Error accepting socket: %s\n", strerror(errno));
            exit(0);
        }
        slen = sizeof(remote);
        getpeername(sd2, (struct sockaddr *)&remote, &slen);
        printf("(connection from %s)\n", inet_ntoa(remote.sin_addr));
        /* read a and b */
        buflen = 24;
        buf = malloc(buflen);
        recv_line(sd2, buf, buflen);
        sscanf(buf, "%ld %ld\r\n", &a, &b);
        free(buf);
        /* calculate c and send */
        c = a + b;
        buflen = 13;
        buf = malloc(buflen);
        sprintf(buf, "%ld\r\n", c);
        status = send(sd2, buf, strlen(buf), 0);
        if(status < 0)
        {
            printf("Error sending c: %s\n", strerror(errno));
            exit(0);
        }
        free(buf);
        /* read v */
        buflen = 258;
        buf = malloc(buflen);
        recv_line(sd2, buf, buflen);
        v = malloc(strlen(buf) + 1);
        sscanf(buf, "%s\r\n", v);
        free(buf);
        /* calculate v2 and send */
        v2 = malloc(2 * strlen(v) + 1);
        strcpy(v2, v);
        strcat(v2, v);
        buflen = strlen(v2) + 3;
        buf = malloc(buflen);
        sprintf(buf, "%s\r\n", v2);
        status = send(sd2, buf, strlen(buf), 0);
        if(status < 0)
        {
            printf("Error sending v2: %s\n", strerror(errno));
            exit(0);
        }
        free(buf);
        free(v);
        free(v2);
        /* close socket */
#ifdef WIN32
        closesocket(sd2);
#else
        close(sd2);
#endif
    }
    return 0;
}

The C API has changed regarding how to lookup information. There is an old style which was created for IPv4 and is known to have some problems with IPv6 - and there is a new style which was created to support both IPv4 and IPv6 nicely.

Always use the new style when writing new code. The old style is only shown to understand existing programs or to develop on very old systems.

This example shows the new style.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <netdb.h>
#endif

#include <errno.h>

#define PORT "12345"
#define BACKLOG 100

void recv_line(int sd, char *buf, int buflen)
{
    int ix;
    ix = 0;
    buf[ix] = '\0';
    while(ix < buflen && strstr(buf, "\r\n") == NULL)
    {
        ix = ix + recv(sd, buf + ix, buflen - ix, 0);
        buf[ix] = '\0';
    }
}

int main()
{
    int sd, sd2, status, buflen, slen;
    long int a, b, c;
    char *buf, *v, *v2, addr[47];
    struct sockaddr remote;
    struct addrinfo hints, *res;
#ifdef WIN32
    WSADATA WSAData;
    if(WSAStartup(0x0101, &WSAData) != 0)
    {
        printf("Error initializing\n");
        exit(0);
    }
#endif
    /* find port */
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET; //AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_flags = AI_PASSIVE; 
    status = getaddrinfo(NULL, PORT, &hints, &res);
    if(status != 0)
    {
        printf("Error finding port: %s\n", PORT);
        exit(0);
    }
    /* create socket */
    sd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    if(sd < 0)
    {
        printf("Error creating socket: %s\n", strerror(errno));
        exit(0);
    }
    /* bind */
    status = bind(sd, res->ai_addr, res->ai_addrlen);
    if(status < 0)
    {
        printf("Error binding socket: %s\n", strerror(errno));
        exit(0);
    }
    freeaddrinfo(res);
    /* listen */
    status = listen(sd, BACKLOG);
    if(status < 0)
    {
        printf("Error listening socket: %s\n", strerror(errno));
        exit(0);
    }
    for(;;)
    {
        /* accept */
        sd2 = accept(sd, 0, 0);
        if(sd2 < 0)
        {
            printf("Error accepting socket: %s\n", strerror(errno));
            exit(0);
        }
        slen = sizeof(remote);
        getpeername(sd2, &remote, &slen);
        if(remote.sa_family == AF_INET)
            printf("(connection from %s)\n", inet_ntop(AF_INET, &((struct sockaddr_in *)&remote)->sin_addr, addr, sizeof(addr)));
        if(remote.sa_family == AF_INET6)
            printf("(connection from %s)\n", inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&remote)->sin6_addr, addr, sizeof(addr)));
        /* read a and b */
        buflen = 24;
        buf = malloc(buflen);
        recv_line(sd2, buf, buflen);
        sscanf(buf, "%ld %ld\r\n", &a, &b);
        free(buf);
        /* calculate c and send */
        c = a + b;
        buflen = 13;
        buf = malloc(buflen);
        sprintf(buf, "%ld\r\n", c);
        status = send(sd2, buf, strlen(buf), 0);
        if(status < 0)
        {
            printf("Error sending c: %s\n", strerror(errno));
            exit(0);
        }
        free(buf);
        /* read v */
        buflen = 258;
        buf = malloc(buflen);
        recv_line(sd2, buf, buflen);
        v = malloc(strlen(buf) + 1);
        sscanf(buf, "%s\r\n", v);
        free(buf);
        /* calculate v2 and send */
        v2 = malloc(2 * strlen(v) + 1);
        strcpy(v2, v);
        strcat(v2, v);
        buflen = strlen(v2) + 3;
        buf = malloc(buflen);
        sprintf(buf, "%s\r\n", v2);
        status = send(sd2, buf, strlen(buf), 0);
        if(status < 0)
        {
            printf("Error sending v2: %s\n", strerror(errno));
            exit(0);
        }
        free(buf);
        free(v);
        free(v2);
        /* close socket */
#ifdef WIN32
        closesocket(sd2);
#else
        close(sd2);
#endif
    }
    return 0;
}

Indy 10 has a convenient TIdTcpServer class.

Indy does server sligtly different than other libraries.

program PlainTextSingleServerHigh;

uses
  IdTCPServer, IdContext, SysUtils;

const
  PORT = 12345;
  BACKLOG = 100;

type
  ClientHandler = class
    public
      procedure Run(ctx : TIdContext);
  end;

procedure ClientHandler.Run(ctx : TIdContext);

var
  line, v, v2 : string;
  a, b, c, ix : integer;

begin
  writeln('(connection from ', ctx.Connection.Socket.Binding.PeerIP, ':', ctx.Connection.Socket.Binding.PeerPort, ')');
  (* read a and b *)
  line := ctx.Connection.Socket.ReadLn;
  ix := Pos(' ', line);
  a := StrToInt(Copy(line, 1, ix - 1));
  b := StrToInt(Copy(line, ix + 1, length(line) - ix));
  (* calculate c and send *)
  c := a + b;
  ctx.Connection.Socket.WriteLn(IntToStr(c));
  (* read v *)
  v :=  ctx.Connection.Socket.ReadLn;
  (* calculate v2 and send *)
  v2 := v + v;
  ctx.Connection.Socket.WriteLn(v2);
  (* close connection *)
  ctx.Connection.Disconnect;
end;

var
  srv : TIdTCPServer;
  h : ClientHandler;

begin
  (* listen on port *)
  srv := TIdTCPServer.Create();
  srv.ListenQueue := BACKLOG;
  srv.Bindings.Add.Port := PORT;
  srv.StartListening;
  (* accept connection *)
  h := ClientHandler.Create;
  srv.OnExecute := @h.Run;
  srv.Active := true;
  readln;
  (* close connection *)
  srv.Free;
end.

Besides Indy it is also possible to use plain WinSock.

program PlainTextSingleServerLow;

uses
  SysUtils,
  Windows,
  WinSock;

type
  bytearray = array of byte;

procedure string2bytearray(s : string; var b : bytearray);

var
  i : integer;

begin
  SetLength(b, Length(s));
  for i := 1 to Length(s) do b[Low(b) + i -1] := ord(s[i]);
end;

function RecvLine(s : TSocket) : string;

var
  res : string;
  buf : bytearray;
  ix : integer;

begin
  SetLength(buf, 256);
  ix := 0;
  while (ix < Length(buf)) and ((buf[ix - 2] <> 13) or (buf[ix - 1] <> 10)) do begin
    ix := ix + Recv(s, buf[ix], Length(buf) - ix, 0);
  end;
  SetString(res, PAnsiChar(@buf[Low(buf)]), ix - 2);
  RecvLine := res;
end;

const
  PORT = 12345;
  BACKLOG = 100;

var
  wsa : TWSAData;
  s, s2 : TSocket;
  local, remote : TSockAddrIn;
  line, v, v2 : string;
  status, slen, a, b, c, ix : integer;
  buf : array of byte;

begin
  WSAStartup($101, wsa);
  (* create socket *)
  s := Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if s = INVALID_SOCKET then begin
     writeln('Error creating socket: ' + SysErrorMessage(GetLastError));
     halt;
  end;
  (* bind *)
  local.sin_family := AF_INET;
  local.sin_port :=  htons(PORT);
  local.sin_addr.S_addr := INADDR_ANY;
  status := bind(s, local, sizeof(local));
  if status <> 0 then begin
    writeln('Error binding socket: ' + SysErrorMessage(GetLastError));
    halt;
  end;
  (* listen *)
  status := listen(s, BACKLOG);
  if status <> 0 then begin
    writeln('Error listening socket: ' + SysErrorMessage(GetLastError));
    halt;
  end;
  while true do begin
    (* accept *)
    slen := sizeof(remote);
    s2 := Accept(s, @remote, slen);
    writeln('(connection from ' + inet_ntoa(remote.sin_addr) + ')');
    (* read a and b *)
    line := RecvLine(s2);
    ix := Pos(' ', line);
    a := StrToInt(Copy(line, 1, ix - 1));
    b := StrToInt(Copy(line, ix + 1, Length(line) - ix));
    (* calculate c and send *)
    c := a + b;
    string2bytearray(IntToStr(c) + #13#10, buf);
    status := Send(s2, buf[Low(buf)], Length(buf), 0);
    if status < 0 then begin
      writeln('Error sending c: ' + SysErrorMessage(GetLastError));
      halt;
    end;
    (* read v *)
    v := RecvLine(s2);
    (* calculate v2 and send *)
    v2 := v + v;
    string2bytearray(v2 + #13#10, buf);
    status := Send(s2, buf[Low(buf)], Length(buf), 0);
    if status < 0 then begin
      writeln('Error sending v2: ' + SysErrorMessage(GetLastError));
      halt;
    end;
    CloseSocket(s2);
  end;
  CloseSocket(s);
  WSACleanup;
end.

Multithreaded server:

Multithreaded server logic is:

function handler
    receive/read
    send/write
    close
endfunction

listen
while
    accept
    start thread with handler
endwhile

Socket and ServerSocket classes has been in Java since version 1.0. Java also makes it easy to wrap the Socket in BufferedReader and PrintStream to read and write text data.

NIO does not does not provide any nice features for reading lines of text.

package socket.plain.text;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;

public class MultiServer {
    public static class ClientHandler extends Thread {
        private Socket s;
        public ClientHandler(Socket s) {
            this.s = s;
        }
        @Override
        public void run() {
            try {
                // get reader and writer
                BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream(), "UTF-8"));
                PrintStream ps = new PrintStream(s.getOutputStream(), false,  "UTF-8");
                // read a and b
                String line = br.readLine();
                String[] parts = line.split(" ");
                int a = Integer.parseInt(parts[0]);
                int b = Integer.parseInt(parts[1]);
                // calculate c and send
                int c = a + b;
                ps.println(c);
                ps.flush();
                // read v
                String v = br.readLine();
                // calculate v2 and send
                String v2 = v + v;
                ps.println(v2);
                ps.flush();
                // close connection
                br.close();
                ps.close();
                s.close();
            } catch (IOException e) {
                // TODO
                e.printStackTrace();
            }
        }
    }
    private static final int PORT = 12345;
    public static void main(String[] args) {
        try {
            // listen on port
            @SuppressWarnings("resource")
            ServerSocket ss = new ServerSocket(PORT);
            while(true) {
                // accept connection
                Socket s = ss.accept();
                System.out.printf("(connection from %s)\n", s.getRemoteSocketAddress());
                // start new thread to handle connection
                (new ClientHandler(s)).start();
            }
        } catch (IOException e) {
            // TODO
            e.printStackTrace();
        }
    }
}

.NET provide two levels of socket API:

Only the high level API is easy to use for reading/writing text lines.

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace SocketDemo.Text.High.MultiServer
{
    public class Program
    {
        private class ClientHandler
        {
            private TcpClient cli;
            public ClientHandler(TcpClient cli)
            {
                this.cli = cli;
            }
            public void Run()
            {
                Console.WriteLine("(handler in thread {0})", Thread.CurrentThread.ManagedThreadId);   
                // get reader and writer
                NetworkStream stm = cli.GetStream();
                StreamReader sr = new StreamReader(stm);
                StreamWriter sw = new StreamWriter(stm);
                // read a and b
                string line = sr.ReadLine();
                string[] parts = line.Split(' ');
                int a = int.Parse(parts[0]);
                int b = int.Parse(parts[1]);
                // calculate c and send
                int c = a + b;
                sw.WriteLine(c);
                sw.Flush();
                // read v
                string v = sr.ReadLine();
                // calculate v2 and send
                string v2 = v + v;
                sw.WriteLine(v2);
                sw.Flush();
                // close connection
                sr.Close();
                sw.Close();
                cli.Close();
            }
        }
        private const int PORT = 12345;
        private const int BACKLOG = 100;
        public static void Main(string[] args)
        {
            // listen on port
            TcpListener srv = new TcpListener(IPAddress.Any, PORT);
            srv.Start(BACKLOG);
            while(true) {
                // accept connection
                TcpClient cli = srv.AcceptTcpClient();
                Console.WriteLine("(connection from {0})", cli.Client.RemoteEndPoint);
                (new Thread((new ClientHandler(cli)).Run)).Start();
            }
        }
    }
}

.NET provide two levels of socket API:

Only the high level API is easy to use for reading/writing text lines.

Imports System
Imports System.IO
Imports System.Net
Imports System.Net.Sockets
Imports System.Threading

Namespace SocketDemo.Text.High.MultiServer
    Public Class Program
        Private Class ClientHandler
            Private cli As TcpClient
            Public Sub New(cli As TcpClient)
                Me.cli = cli
            End Sub
            Public Sub Run()
                Console.WriteLine("(handler in thread {0})", Thread.CurrentThread.ManagedThreadId)
                ' get reader and writer
                Dim stm As NetworkStream = cli.GetStream()
                Dim sr As New StreamReader(stm)
                Dim sw As New StreamWriter(stm)
                ' read a and b
                Dim line As String = sr.ReadLine()
                Dim parts As String() = line.Split(" "C)
                Dim a As Integer = Integer.Parse(parts(0))
                Dim b As Integer = Integer.Parse(parts(1))
                ' calculate c and send
                Dim c As Integer = a + b
                sw.WriteLine(c)
                sw.Flush()
                ' read v
                Dim v As String = sr.ReadLine()
                ' calculate v2 and send
                Dim v2 As String = v & v
                sw.WriteLine(v2)
                sw.Flush()
                ' close connection
                sr.Close()
                sw.Close()
                cli.Close()
            End Sub
        End Class
        Private Const PORT As Integer = 12345
        Private Const BACKLOG As Integer = 100
        Public Shared Sub Main(args As String())
            ' listen on port
            Dim srv As New TcpListener(IPAddress.Any, PORT)
            srv.Start(BACKLOG)
            While True
                ' accept connection
                Dim cli As TcpClient = srv.AcceptTcpClient()
                Console.WriteLine("(connection from {0})", cli.Client.RemoteEndPoint)
                Dim t As Thread = New Thread(AddressOf (New ClientHandler(cli)).Run)
                t.Start()
            End While
        End Sub
    End Class
End Namespace

I will not show C examples for multithreaded server. Multithreading is not standardized in C. Windows uses Windows threads. Most other OS'es use POSIX threads. An example would be more a threading example than a socket example.

To read about threads see here.

The C API has changed regarding how to lookup information. There is an old style which was created for IPv4 and is known to have some problems with IPv6 - and there is a new style which was created to support both IPv4 and IPv6 nicely.

Always use the new style when writing new code. The old style is only shown to understand existing programs or to develop on very old systems.

I will not show C examples for multithreaded server. Multithreading is not standardized in older versions of C. Windows uses Windows threads. Most other OS'es use POSIX threads. An example would be more a threading example than a socket example.

Besides Indy it is also possible to use plain WinSock.

program PlainTextMultiServerLow;

uses
  Classes,
  SysUtils,
  Windows,
  WinSock;

type
  bytearray = array of byte;

procedure string2bytearray(s : string; var b : bytearray);

var
  i : integer;

begin
  SetLength(b, Length(s));
  for i := 1 to Length(s) do b[Low(b) + i -1] := ord(s[i]);
end;

function RecvLine(s : TSocket) : string;

var
  res : string;
  buf : bytearray;
  ix : integer;

begin
  SetLength(buf, 256);
  ix := 0;
  while (ix < Length(buf)) and ((buf[ix - 2] <> 13) or (buf[ix - 1] <> 10)) do begin
    ix := ix + Recv(s, buf[ix], Length(buf) - ix, 0);
  end;
  SetString(res, PAnsiChar(@buf[Low(buf)]), ix - 2);
  RecvLine := res;
end;

type
  ClientHandler = class(TThread)
    constructor Create(s2 : TSocket);
  protected
    procedure Execute; override;
  private
    _s2 : TSocket;
  end;

constructor ClientHandler.Create(s2 : TSocket);

begin
  inherited Create(false);
  _s2 := s2;
end;

procedure ClientHandler.Execute;

var
  line, v, v2 : string;
  status, a, b, c, ix : integer;
  buf : array of byte;

begin
  (* read a and b *)
  line := RecvLine(_s2);
  ix := Pos(' ', line);
  a := StrToInt(Copy(line, 1, ix - 1));
  b := StrToInt(Copy(line, ix + 1, Length(line) - ix));
  (* calculate c and send *)
  c := a + b;
  string2bytearray(IntToStr(c) + #13#10, buf);
  status := Send(_s2, buf[Low(buf)], Length(buf), 0);
  if status < 0 then begin
    writeln('Error sending c: ' + SysErrorMessage(GetLastError));
    halt;
  end;
  (* read v *)
  v := RecvLine(_s2);
  (* calculate v2 and send *)
  v2 := v + v;
  string2bytearray(v2 + #13#10, buf);
  status := Send(_s2, buf[Low(buf)], Length(buf), 0);
  if status < 0 then begin
    writeln('Error sending v2: ' + SysErrorMessage(GetLastError));
    halt;
  end;
  CloseSocket(_s2);
  self.Free;
end;

const
  PORT = 12345;
  BACKLOG = 100;

var
  wsa : TWSAData;
  s, s2 : TSocket;
  local, remote : TSockAddrIn;
  status, slen : integer;

begin
  WSAStartup($101, wsa);
  (* create socket *)
  s := Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if s = INVALID_SOCKET then begin
     writeln('Error creating socket: ' + SysErrorMessage(GetLastError));
     halt;
  end;
  (* bind *)
  local.sin_family := AF_INET;
  local.sin_port :=  htons(PORT);
  local.sin_addr.S_addr := INADDR_ANY;
  status := bind(s, local, sizeof(local));
  if status <> 0 then begin
    writeln('Error binding socket: ' + SysErrorMessage(GetLastError));
    halt;
  end;
  (* listen *)
  status := listen(s, BACKLOG);
  if status <> 0 then begin
    writeln('Error listening socket: ' + SysErrorMessage(GetLastError));
    halt;
  end;
  while true do begin
    (* accept *)
    slen := sizeof(remote);
    s2 := Accept(s, @remote, slen);
    writeln('(connection from ' + inet_ntoa(remote.sin_addr) + ')');
    ClientHandler.Create(s2).Start;
  end;
  CloseSocket(s);
  WSACleanup;
end.

Asynchroneous server:

Aynschroneous server logic is:

function accepthandler
    accept
    at read call inputhandler
endfunction

function inputhandler
    receive/read
    send/write
    close
endfunction

listen
at accept call accepthandler

Note that an asynchroneous server is using threads behind the scene.

The Java library Netty provide an async framework on top of NIO.

package socket.plain.text;

import java.io.UnsupportedEncodingException;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

public class AsyncServer {
    public static enum NextPhase { WAIT_INTADD, WAIT_STRDUP, DONE }
    private static class ReadHandler extends ChannelInboundHandlerAdapter {
        private NextPhase state;
        public ReadHandler(Channel ch) {
            state = NextPhase.WAIT_INTADD;
        }
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws UnsupportedEncodingException, InterruptedException {
            String line;
            switch(state) {
                case WAIT_INTADD:
                    line = (String)msg;
                    // read a and b
                    String[] parts = line.split(" ");
                    int a = Integer.parseInt(parts[0]);
                    int b = Integer.parseInt(parts[1]);
                    // calculate c and send
                    int c = a + b;
                    ctx.channel().writeAndFlush(Integer.toString(c) + "\r\n");
                    // update state
                    state = NextPhase.WAIT_STRDUP;
                    break;
                case WAIT_STRDUP:
                    line = (String)msg;
                    // read v
                    String v = line;
                    // calculate v2 and send
                    String v2 = v + v;
                    ctx.channel().writeAndFlush(v2 + "\r\n");
                    // update state
                    state = NextPhase.DONE;
                    break;
                case DONE:
                    // ignore
                    break;
            }
        }
    }
    private static final int PORT = 12345;
    private static final int BACKLOG = 100;
    public static void main(String[] args) throws InterruptedException {
        // setup context
        EventLoopGroup g = new NioEventLoopGroup(1);
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(g).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, BACKLOG);
            // define handler
            b.childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     System.out.printf("(connection from %s)\n", ch.remoteAddress());
                     ch.pipeline().addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()))
                                  .addLast(new StringDecoder())
                                  .addLast(new StringEncoder())
                                  .addLast(new ReadHandler(ch));
                 }
             });
            // listen on port
            ChannelFuture f = b.bind(PORT).sync();
            // close connection when done (never)
            f.channel().closeFuture().sync();
        } finally {
            // teardown context
            g.shutdownGracefully();
        }       
    }
}

Article history:

Version Date Description
1.0 May 10th 2018 Initial version
1.1 July 29th 2018 Add Delphi/Lazarus Indy examples
1.2 June 19th 2019 Add Delphi/Lazarus WinSock examples
1.3 June 28th 2022 Add Java Netty example

Other articles:

See list of all articles here

Comments:

Please send comments to Arne Vajhøj