For other articles in series see:
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.
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.
The binary protocol used in all examples are very simple:
Client logic is:
connect send/write receive/read close
Socket and ServerSocket classes has been in Java since version 1.0. Java also have DataInputStream and DataOutputStream classes for convenient reading and writing binary data.
Note that DataInputStream and DataOutputStream always use big endian.
package socket.plain.binary.traditional;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
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 streams
DataInputStream dis = new DataInputStream(s.getInputStream());
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
System.out.println("(connected)");
// send a and b
int a = 123;
int b = 456;
dos.writeInt(a);
dos.writeInt(b);
dos.flush();
// receive c and print
int c = dis.readInt();
System.out.println(c);
// send v
String v = "ABC";
byte[] vb = v.getBytes("UTF-8");
dos.writeByte(vb.length);
dos.write(vb);
dos.flush();
// receive v2 and print
int buflen = dis.readByte();
byte[] buf = new byte[buflen];
int ix = 0;
while(ix < buf.length) {
ix += dis.read(buf, ix, buf.length - ix);
}
String v2 = new String(buf, "UTF-8");
System.out.println(v2);
// close connection
dis.close();
dos.close();
s.close();
} catch (IOException e) {
// TODO
e.printStackTrace();
}
}
}
Java 1.4 added several new IO features called NIO including a SocketChannel. It is not that much of an improvement for client except that is supports both little and big endian.
package socket.plain.binary.modern;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.SocketChannel;
public class Client {
private static final String HOST = "localhost";
private static final int PORT = 12345;
public static void main(String[] args) {
try {
// open connection
SocketChannel sc = SocketChannel.open();
sc.connect(new InetSocketAddress(HOST, PORT));
System.out.println("(connected)");
ByteBuffer bb;
// send a and b
int a = 123;
int b = 456;
bb = ByteBuffer.allocate(8);
bb.order(ByteOrder.BIG_ENDIAN);
bb.putInt(a);
bb.putInt(b);
bb.rewind();
sc.write(bb);
// receive c and print
bb = ByteBuffer.allocate(4);
while(bb.hasRemaining() && sc.read(bb) > 0);
bb.rewind();
bb.order(ByteOrder.BIG_ENDIAN);
int c = bb.getInt();
System.out.println(c);
// send v
String v = "ABC";
byte[] vb = v.getBytes("UTF-8");
bb = ByteBuffer.allocate(1 + vb.length);
bb.put((byte)vb.length);
bb.put(vb);
bb.rewind();
sc.write(bb);
// receive v2 and print
bb = ByteBuffer.allocate(1);
sc.read(bb);
bb.rewind();
int buflen = bb.get();
bb = ByteBuffer.allocate(buflen);
while(bb.hasRemaining() && sc.read(bb) > 0);
bb.rewind();
byte[] buf = new byte[buflen];
bb.get(buf);
String v2 = new String(buf, "UTF-8");
System.out.println(v2);
// close connection
sc.close();
} catch (IOException e) {
// TODO
e.printStackTrace();
}
}
}
.NET provide two levels of socket API:
This is the low level API.
using System;
using System.Net.Sockets;
using System.Text;
namespace SocketDemo.Binary.Low.Client
{
public class Program
{
private static byte[] IntToBytes(int v)
{
byte[] buf = BitConverter.GetBytes(v);
if(BitConverter.IsLittleEndian) Array.Reverse(buf);
return buf;
}
private static int BytesToInt(byte[] buf)
{
if(BitConverter.IsLittleEndian) Array.Reverse(buf);
return BitConverter.ToInt32(buf, 0);
}
private const string HOST = "localhost";
private const int PORT = 12345;
public static void Main(string[] args)
{
// open connection
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
s.Connect(HOST, PORT);
Console.WriteLine("(connected)");
byte[] buf;
int ix;
// send a and b
int a = 123;
int b = 456;
s.Send(IntToBytes(a));
s.Send(IntToBytes(b));
// receive c and print
buf = new Byte[4];
ix = 0;
while(ix < buf.Length)
{
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None);
}
int c = BytesToInt(buf);
Console.WriteLine(c);
// send v
string v = "ABC";
byte[] vb = Encoding.UTF8.GetBytes(v);
s.Send(new byte[] { (byte)vb.Length });
s.Send(vb);
// receive v2 and print
buf = new Byte[1];
s.Receive(buf);
int buflen = buf[0];
buf = new byte[buflen];
ix = 0;
while(ix < buf.Length)
{
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None);
}
string v2 = Encoding.UTF8.GetString(buf);
Console.WriteLine(v2);
// close connection
s.Close();
}
}
}
.NET provide two levels of socket API:
This is the high level API.
using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
namespace SocketDemo.Binary.High.Client
{
public class Program
{
private static int HTON(int v)
{
if(BitConverter.IsLittleEndian)
{
byte[] buf = BitConverter.GetBytes(v);
Array.Reverse(buf);
return BitConverter.ToInt32(buf, 0);
}
else
{
return v;
}
}
private static int NTOH(int v)
{
if(BitConverter.IsLittleEndian)
{
byte[] buf = BitConverter.GetBytes(v);
Array.Reverse(buf);
return BitConverter.ToInt32(buf, 0);
}
else
{
return v;
}
}
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();
BinaryReader br = new BinaryReader(stm);
BinaryWriter bw = new BinaryWriter(stm);
Console.WriteLine("(connected)");
// send a and b
int a = 123;
int b = 456;
bw.Write(HTON(a));
bw.Write(HTON(b));
bw.Flush();
// receive c and print
int c = NTOH(br.ReadInt32());
Console.WriteLine(c);
// send v
string v = "ABC";
byte[] vb = Encoding.UTF8.GetBytes(v);
bw.Write((byte)vb.Length);
bw.Write(vb);
bw.Flush();
// receive v2 and print
int buflen = br.ReadByte();
byte[] buf = new byte[buflen];
int ix = 0;
while(ix < buf.Length)
{
ix += br.Read(buf, ix, buf.Length - ix);
}
string v2 = Encoding.UTF8.GetString(buf);
Console.WriteLine(v2);
// close connection
br.Close();
bw.Close();
cli.Close();
}
}
}
.NET provide two levels of socket API:
This is the low level API.
Imports System
Imports System.Net.Sockets
Imports System.Text
Namespace SocketDemo.Binary.Low.Client
Public Class Program
Private Shared Function IntToBytes(v As Integer) As Byte()
Dim buf As Byte() = BitConverter.GetBytes(v)
If BitConverter.IsLittleEndian Then
Array.Reverse(buf)
End If
Return buf
End Function
Private Shared Function BytesToInt(buf As Byte()) As Integer
If BitConverter.IsLittleEndian Then
Array.Reverse(buf)
End If
Return BitConverter.ToInt32(buf, 0)
End Function
Private Const HOST As String = "localhost"
Private Const PORT As Integer = 12345
Public Shared Sub Main(args As String())
' open connection
Dim s As New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
s.Connect(HOST, PORT)
Console.WriteLine("(connected)")
Dim buf As Byte()
Dim ix As Integer
' send a and b
Dim a As Integer = 123
Dim b As Integer = 456
s.Send(IntToBytes(a))
s.Send(IntToBytes(b))
' receive c and print
buf = New [Byte](3) {}
ix = 0
While ix < buf.Length
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None)
End While
Dim c As Integer = BytesToInt(buf)
Console.WriteLine(c)
' send v
Dim v As String = "ABC"
Dim vb As Byte() = Encoding.UTF8.GetBytes(v)
s.Send(New Byte() {CByte(vb.Length)})
s.Send(vb)
' receive v2 and print
buf = New [Byte](0) {}
s.Receive(buf)
Dim buflen As Integer = buf(0)
buf = New Byte(buflen - 1) {}
ix = 0
While ix < buf.Length
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None)
End While
Dim v2 As String = Encoding.UTF8.GetString(buf)
Console.WriteLine(v2)
' close connection
s.Close()
End Sub
End Class
End Namespace
.NET provide two levels of socket API:
This is the high level API.
Imports System
Imports System.IO
Imports System.Net.Sockets
Imports System.Text
Namespace SocketDemo.Binary.High.Client
Public Class Program
Private Shared Function HTON(v As Integer) As Integer
If BitConverter.IsLittleEndian Then
Dim buf As Byte() = BitConverter.GetBytes(v)
Array.Reverse(buf)
Return BitConverter.ToInt32(buf, 0)
Else
Return v
End If
End Function
Private Shared Function NTOH(v As Integer) As Integer
If BitConverter.IsLittleEndian Then
Dim buf As Byte() = BitConverter.GetBytes(v)
Array.Reverse(buf)
Return BitConverter.ToInt32(buf, 0)
Else
Return v
End If
End Function
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 br As New BinaryReader(stm)
Dim bw As New BinaryWriter(stm)
Console.WriteLine("(connected)")
' send a and b
Dim a As Integer = 123
Dim b As Integer = 456
bw.Write(HTON(a))
bw.Write(HTON(b))
bw.Flush()
' receive c and print
Dim c As Integer = NTOH(br.ReadInt32())
Console.WriteLine(c)
' send v
Dim v As String = "ABC"
Dim vb As Byte() = Encoding.UTF8.GetBytes(v)
bw.Write(CByte(vb.Length))
bw.Write(vb)
bw.Flush()
' receive v2 and print
Dim buflen As Integer = br.ReadByte()
Dim buf As Byte() = New Byte(buflen - 1) {}
Dim ix As Integer = 0
While ix < buf.Length
ix += br.Read(buf, ix, buf.Length - ix)
End While
Dim v2 As String = Encoding.UTF8.GetString(buf)
Console.WriteLine(v2)
' close connection
br.Close()
bw.Close()
cli.Close()
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 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
int main()
{
int sd, status, ix, buflen;
long int a, b, c, tmp;
char *buf, *v, *v2, len;
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;
tmp = htonl(a);
status = send(sd, (char *)&tmp, sizeof(tmp), 0);
if(status < 0)
{
printf("Error sending a: %s\n", strerror(errno));
exit(0);
}
tmp = htonl(b);
status = send(sd, (char *)&tmp, sizeof(tmp), 0);
if(status < 0)
{
printf("Error sending b: %s\n", strerror(errno));
exit(0);
}
/* receive c and print */
buflen = sizeof(tmp);
ix = 0;
while(ix < buflen)
{
ix = ix + recv(sd, (char *)&tmp + ix, buflen - ix, 0);
}
c = ntohl(tmp);
printf("%ld\n", c);
/* send v */
v = "ABC";
len = strlen(v);
status = send(sd, &len, sizeof(len), 0);
if(status < 0)
{
printf("Error sending v: %s\n", strerror(errno));
exit(0);
}
status = send(sd, v, len, 0);
if(status < 0)
{
printf("Error sending v: %s\n", strerror(errno));
exit(0);
}
/* receive v2 and print */
recv(sd, &len, sizeof(len), 0);
buflen = len;
buf = malloc(buflen);
ix = 0;
while(ix < buflen)
{
ix = ix + recv(sd, buf + ix, buflen - ix, 0);
}
v2 = malloc(buflen + 1);
memcpy(v2, buf, buflen);
v2[buflen] = '\0';
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 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"
int main()
{
int sd, status, ix, buflen;
long int a, b, c, tmp;
char *buf, *v, *v2, len;
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);
}
freeaddrinfo(res);
/* send a and b */
a = 123;
b = 456;
tmp = htonl(a);
status = send(sd, (char *)&tmp, sizeof(tmp), 0);
if(status < 0)
{
printf("Error sending a: %s\n", strerror(errno));
exit(0);
}
tmp = htonl(b);
status = send(sd, (char *)&tmp, sizeof(tmp), 0);
if(status < 0)
{
printf("Error sending b: %s\n", strerror(errno));
exit(0);
}
/* receive c and print */
buflen = sizeof(tmp);
ix = 0;
while(ix < buflen)
{
ix = ix + recv(sd, (char *)&tmp + ix, buflen - ix, 0);
}
c = ntohl(tmp);
printf("%ld\n", c);
/* send v */
v = "ABC";
len = strlen(v);
status = send(sd, &len, sizeof(len), 0);
if(status < 0)
{
printf("Error sending v: %s\n", strerror(errno));
exit(0);
}
status = send(sd, v, len, 0);
if(status < 0)
{
printf("Error sending v: %s\n", strerror(errno));
exit(0);
}
/* receive v2 and print */
recv(sd, &len, sizeof(len), 0);
buflen = len;
buf = malloc(buflen);
ix = 0;
while(ix < buflen)
{
ix = ix + recv(sd, buf + ix, buflen - ix, 0);
}
v2 = malloc(buflen + 1);
memcpy(v2, buf, buflen);
v2[buflen] = '\0';
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 PlainBinClientHigh;
uses
IdTCPClient;
const
HOST = 'localhost';
PORT = 12345;
var
cli : TIdTCPClient;
v, v2 : string;
a, b, c, len : integer;
begin
(* open connection *)
cli := TIdTCPClient.Create;
cli.Connect(HOST, PORT);
(* send a and b *)
a := 123;
b := 456;
cli.Socket.Write(a, true);
cli.Socket.Write(b, true);
(* receive c and print *)
c := cli.Socket.ReadInt32(true);
writeln(c);
(* send v *)
v := 'ABC';
cli.Socket.Write(Byte(length(v)));
cli.Socket.Write(v);
(* received v2 and print *)
len := cli.Socket.ReadByte;
v2 := cli.Socket.ReadString(len);
writeln(v2);
(* close connection *)
cli.Disconnect;
cli.Free;
end.
Besides Indy it is also possible to use plain WinSock.
program PlainBinClientLow;
uses
SysUtils,
Windows,
WinSock;
const
HOST = 'localhost';
PORT = 12345;
var
wsa : TWSAData;
s : TSocket;
hostinfo : PHOstEnt;
remote : TSockAddrIn;
v, v2 : string;
status, a, b, c, tmp, ix, buflen : integer;
buf : array of byte;
len : 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;
tmp := htonl(a);
status := Send(s, @tmp, sizeof(tmp), 0);
if status < 0 then begin
writeln('Error sending a: ' + SysErrorMessage(GetLastError));
halt;
end;
tmp := htonl(b);
status := Send(s, @tmp, sizeof(tmp), 0);
if status < 0 then begin
writeln('Error sending b: ' + SysErrorMessage(GetLastError));
halt;
end;
(* receive c and print *)
buflen := sizeof(tmp);
ix := 0;
while ix < buflen do begin
ix := ix + Recv(s, PChar(@tmp) + ix, buflen - ix, 0);
end;
c := ntohl(tmp);
writeln(c);
(* send v *)
v := 'ABC';
len := Length(v);
status := Send(s, len, sizeof(len), 0);
if status < 0 then begin
writeln('Error sending v: ' + SysErrorMessage(GetLastError));
halt;
end;
status := Send(s, v[1], len, 0);
if status < 0 then begin
writeln('Error sending v: ' + SysErrorMessage(GetLastError));
halt;
end;
(* receive v2 and print *)
Recv(s, @len, sizeof(len), 0);
SetLength(buf, len);
buflen := len;
ix := 0;
while ix < buflen do begin
ix := ix + Recv(s, buf[Low(buf) + ix], buflen - ix, 0);
end;
SetString(v2, PAnsiChar(@buf[Low(buf)]), len);
writeln(v2);
CloseSocket(s);
WSACleanup;
end.
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 have DataInputStream and DataOutputStream classes for convenient reading and writing binary data.
Note that DataInputStream and DataOutputStream always use big endian.
package socket.plain.binary.traditional;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
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 streams
DataInputStream dis = new DataInputStream(s.getInputStream());
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
// read a and b
int a = dis.readInt();
int b = dis.readInt();
// calculate c and send
int c = a + b;
dos.writeInt(c);
dos.flush();
// read v
int buflen = dis.readByte();
byte[] buf = new byte[buflen];
int ix = 0;
while(ix < buf.length) {
ix += dis.read(buf, ix, buf.length - ix);
}
String v = new String(buf, "UTF-8");
// calculate v2 and send
String v2 = v + v;
byte[] v2b = v2.getBytes("UTF-8");
dos.writeByte(v2b.length);
dos.write(v2b);
dos.flush();
// close connection
dis.close();
dos.close();
s.close();
}
} catch (IOException e) {
// TODO
e.printStackTrace();
}
}
}
Java 1.4 added several new IO features called NIO including a SocketChannel. It is not that much of an improvement for single threaded server except that is supports both little and big endian.
package socket.plain.binary.modern;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class SingleServer {
private static final int PORT = 12345;
public static void main(String[] args) {
try {
// listen on port
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(PORT));
while(true) {
// accept connection
SocketChannel sc = ssc.accept();
System.out.printf("(connection from %s)\n", sc.getRemoteAddress());
ByteBuffer bb;
// read a and b
bb = ByteBuffer.allocate(8);
while(bb.hasRemaining() && sc.read(bb) > 0);
bb.rewind();
bb.order(ByteOrder.BIG_ENDIAN);
int a = bb.getInt();
int b = bb.getInt();
// calculate c and send
int c = a + b;
bb = ByteBuffer.allocate(4);
bb.order(ByteOrder.BIG_ENDIAN);
bb.putInt(c);
bb.rewind();
sc.write(bb);
// read v
bb = ByteBuffer.allocate(1);
sc.read(bb);
bb.rewind();
int buflen = bb.get();
bb = ByteBuffer.allocate(buflen);
while(bb.hasRemaining() && sc.read(bb) > 0);
bb.rewind();
byte[] buf = new byte[buflen];
bb.get(buf);
String v = new String(buf, "UTF-8");
// calculate v2 and send
String v2 = v + v;
byte[] v2b = v2.getBytes("UTF-8");
bb = ByteBuffer.allocate(1 + v2b.length);
bb.put((byte)v2b.length);
bb.put(v2b);
bb.rewind();
sc.write(bb);
// close connection
sc.close();
}
} catch (IOException e) {
// TODO
e.printStackTrace();
}
}
}
.NET provide two levels of socket API:
This is the low level API.
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace SocketDemo.Binary.Low.SingleServer
{
public class Program
{
private static byte[] IntToBytes(int v)
{
byte[] buf = BitConverter.GetBytes(v);
if(BitConverter.IsLittleEndian) Array.Reverse(buf);
return buf;
}
private static int BytesToInt(byte[] buf)
{
if(BitConverter.IsLittleEndian) Array.Reverse(buf);
return BitConverter.ToInt32(buf, 0);
}
private const int PORT = 12345;
private const int BACKLOG = 100;
public static void Main(string[] args)
{
// listen on port
Socket ss = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
ss.Bind(new IPEndPoint(IPAddress.Any, PORT));
ss.Listen(BACKLOG);
while(true) {
// accept connection
Socket s = ss.Accept();
Console.WriteLine("(connection from {0})", s.RemoteEndPoint);
byte[] buf;
int ix;
// read a and b
buf = new Byte[4];
ix = 0;
while(ix < buf.Length)
{
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None);
}
int a = BytesToInt(buf);
buf = new Byte[4];
ix = 0;
while(ix < buf.Length)
{
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None);
}
int b = BytesToInt(buf);
// calculate c and send
int c = a + b;
s.Send(IntToBytes(c));
// read v
buf = new Byte[1];
s.Receive(buf);
int buflen = buf[0];
buf = new byte[buflen];
ix = 0;
while(ix < buf.Length)
{
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None);
}
string v = Encoding.UTF8.GetString(buf);
// calculate v2 and send
string v2 = v + v;
byte[] v2b = Encoding.UTF8.GetBytes(v2);
s.Send(new byte[] { (byte)v2b.Length });
s.Send(v2b);
// close connection
s.Close();
}
}
}
}
.NET provide two levels of socket API:
This is the high level API.
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace SocketDemo.Binary.High.SingleServer
{
public class Program
{
private static int HTON(int v)
{
if(BitConverter.IsLittleEndian)
{
byte[] buf = BitConverter.GetBytes(v);
Array.Reverse(buf);
return BitConverter.ToInt32(buf, 0);
}
else
{
return v;
}
}
private static int NTOH(int v)
{
if(BitConverter.IsLittleEndian)
{
byte[] buf = BitConverter.GetBytes(v);
Array.Reverse(buf);
return BitConverter.ToInt32(buf, 0);
}
else
{
return v;
}
}
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();
BinaryReader br = new BinaryReader(stm);
BinaryWriter bw = new BinaryWriter(stm);
Console.WriteLine("(connection from {0})", cli.Client.RemoteEndPoint);
// read a and b
int a = NTOH(br.ReadInt32());
int b = NTOH(br.ReadInt32());
// calculate c and send
int c = a + b;
bw.Write(HTON(c));
bw.Flush();
// read v
int buflen = br.ReadByte();
byte[] buf = new byte[buflen];
int ix = 0;
while(ix < buf.Length)
{
ix += br.Read(buf, ix, buf.Length - ix);
}
string v = Encoding.UTF8.GetString(buf);
// calculate v2 and send
string v2 = v + v;
byte[] v2b = Encoding.UTF8.GetBytes(v2);
bw.Write((byte)v2b.Length);
bw.Write(v2b);
bw.Flush();
// close connection
br.Close();
bw.Close();
cli.Close();
}
}
}
}
.NET provide two levels of socket API:
This is the low level API.
Imports System
Imports System.Net
Imports System.Net.Sockets
Imports System.Text
Namespace SocketDemo.Binary.Low.SingleServer
Public Class Program
Private Shared Function IntToBytes(v As Integer) As Byte()
Dim buf As Byte() = BitConverter.GetBytes(v)
If BitConverter.IsLittleEndian Then
Array.Reverse(buf)
End If
Return buf
End Function
Private Shared Function BytesToInt(buf As Byte()) As Integer
If BitConverter.IsLittleEndian Then
Array.Reverse(buf)
End If
Return BitConverter.ToInt32(buf, 0)
End Function
Private Const PORT As Integer = 12345
Private Const BACKLOG As Integer = 100
Public Shared Sub Main(args As String())
' listen on port
Dim ss As New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
ss.Bind(New IPEndPoint(IPAddress.Any, PORT))
ss.Listen(BACKLOG)
While True
' accept connection
Dim s As Socket = ss.Accept()
Console.WriteLine("(connection from {0})", s.RemoteEndPoint)
Dim buf As Byte()
Dim ix As Integer
' read a and b
buf = New [Byte](3) {}
ix = 0
While ix < buf.Length
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None)
End While
Dim a As Integer = BytesToInt(buf)
buf = New [Byte](3) {}
ix = 0
While ix < buf.Length
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None)
End While
Dim b As Integer = BytesToInt(buf)
' calculate c and send
Dim c As Integer = a + b
s.Send(IntToBytes(c))
' read v
buf = New [Byte](0) {}
s.Receive(buf)
Dim buflen As Integer = buf(0)
buf = New Byte(buflen - 1) {}
ix = 0
While ix < buf.Length
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None)
End While
Dim v As String = Encoding.UTF8.GetString(buf)
' calculate v2 and send
Dim v2 As String = v & v
Dim v2b As Byte() = Encoding.UTF8.GetBytes(v2)
s.Send(New Byte() {CByte(v2b.Length)})
s.Send(v2b)
' close connection
s.Close()
End While
End Sub
End Class
End Namespace
.NET provide two levels of socket API:
This is the high level API.
Imports System
Imports System.IO
Imports System.Net
Imports System.Net.Sockets
Imports System.Text
Namespace SocketDemo.Binary.High.SingleServer
Public Class Program
Private Shared Function HTON(v As Integer) As Integer
If BitConverter.IsLittleEndian Then
Dim buf As Byte() = BitConverter.GetBytes(v)
Array.Reverse(buf)
Return BitConverter.ToInt32(buf, 0)
Else
Return v
End If
End Function
Private Shared Function NTOH(v As Integer) As Integer
If BitConverter.IsLittleEndian Then
Dim buf As Byte() = BitConverter.GetBytes(v)
Array.Reverse(buf)
Return BitConverter.ToInt32(buf, 0)
Else
Return v
End If
End Function
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 br As New BinaryReader(stm)
Dim bw As New BinaryWriter(stm)
Console.WriteLine("(connection from {0})", cli.Client.RemoteEndPoint)
' read a and b
Dim a As Integer = NTOH(br.ReadInt32())
Dim b As Integer = NTOH(br.ReadInt32())
' calculate c and send
Dim c As Integer = a + b
bw.Write(HTON(c))
bw.Flush()
' read v
Dim buflen As Integer = br.ReadByte()
Dim buf As Byte() = New Byte(buflen - 1) {}
Dim ix As Integer = 0
While ix < buf.Length
ix += br.Read(buf, ix, buf.Length - ix)
End While
Dim v As String = Encoding.UTF8.GetString(buf)
' calculate v2 and send
Dim v2 As String = v & v
Dim v2b As Byte() = Encoding.UTF8.GetBytes(v2)
bw.Write(CByte(v2b.Length))
bw.Write(v2b)
bw.Flush()
' close connection
br.Close()
bw.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 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
int main()
{
int sd, sd2, status, ix, buflen, slen;
long int a, b, c, tmp;
char *buf, *v, *v2, len;
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 = sizeof(tmp);
ix = 0;
while(ix < buflen)
{
ix = ix + recv(sd2, (char *)&tmp + ix, buflen - ix, 0);
}
a = ntohl(tmp);
buflen = sizeof(tmp);
ix = 0;
while(ix < buflen)
{
ix = ix + recv(sd2, (char *)&tmp + ix, buflen - ix, 0);
}
b = ntohl(tmp);
/* calculate c and send */
c = a + b;
tmp = htonl(c);
status = send(sd2, (char *)&tmp, sizeof(tmp), 0);
if(status < 0)
{
printf("Error sending c: %s\n", strerror(errno));
exit(0);
}
/* read v */
recv(sd2, &len, sizeof(len), 0);
buflen = len;
buf = malloc(buflen);
ix = 0;
while(ix < buflen)
{
ix = ix + recv(sd2, buf + ix, buflen - ix, 0);
}
v = malloc(buflen + 1);
memcpy(v, buf, buflen);
v[buflen] = '\0';
free(buf);
/* calculate v2 and send */
v2 = malloc(2 * strlen(v) + 1);
strcpy(v2, v);
strcat(v2, v);
len = strlen(v2);
status = send(sd2, &len, sizeof(len), 0);
if(status < 0)
{
printf("Error sending v2: %s\n", strerror(errno));
exit(0);
}
status = send(sd2, v2, len, 0);
if(status < 0)
{
printf("Error sending v2: %s\n", strerror(errno));
exit(0);
}
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 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
int main()
{
int sd, sd2, status, ix, buflen, slen;
long int a, b, c, tmp;
char *buf, *v, *v2, len, 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 = sizeof(tmp);
ix = 0;
while(ix < buflen)
{
ix = ix + recv(sd2, (char *)&tmp + ix, buflen - ix, 0);
}
a = ntohl(tmp);
buflen = sizeof(tmp);
ix = 0;
while(ix < buflen)
{
ix = ix + recv(sd2, (char *)&tmp + ix, buflen - ix, 0);
}
b = ntohl(tmp);
/* calculate c and send */
c = a + b;
tmp = htonl(c);
status = send(sd2, (char *)&tmp, sizeof(tmp), 0);
if(status < 0)
{
printf("Error sending c: %s\n", strerror(errno));
exit(0);
}
/* read v */
recv(sd2, &len, sizeof(len), 0);
buflen = len;
buf = malloc(buflen);
ix = 0;
while(ix < buflen)
{
ix = ix + recv(sd2, buf + ix, buflen - ix, 0);
}
v = malloc(buflen + 1);
memcpy(v, buf, buflen);
v[buflen] = '\0';
free(buf);
/* calculate v2 and send */
v2 = malloc(2 * strlen(v) + 1);
strcpy(v2, v);
strcat(v2, v);
len = strlen(v2);
status = send(sd2, &len, sizeof(len), 0);
if(status < 0)
{
printf("Error sending v2: %s\n", strerror(errno));
exit(0);
}
status = send(sd2, v2, len, 0);
if(status < 0)
{
printf("Error sending v2: %s\n", strerror(errno));
exit(0);
}
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 PlainBinSingleServerHigh;
uses
IdTCPServer, IdContext;
const
PORT = 12345;
BACKLOG = 100;
type
ClientHandler = class
public
procedure Run(ctx : TIdContext);
end;
procedure ClientHandler.Run(ctx : TIdContext);
var
v, v2 : string;
a, b, c, len : integer;
begin
writeln('(connection from ', ctx.Connection.Socket.Binding.PeerIP, ':', ctx.Connection.Socket.Binding.PeerPort, ')');
(* read a and b *)
a := ctx.Connection.Socket.ReadInt32(true);
b := ctx.Connection.Socket.ReadInt32(true);
(* calculate c and send *)
c := a + b;
ctx.Connection.Socket.Write(c, true);
(* read v *)
len := ctx.Connection.Socket.ReadByte;
v := ctx.Connection.Socket.ReadString(len);
(* calculate v2 and send *)
v2 := v + v;
ctx.Connection.Socket.Write(Byte(length(v2)));
ctx.Connection.Socket.Write(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 PlainBinSingleServerLow;
uses
SysUtils,
Windows,
WinSock;
const
PORT = 12345;
BACKLOG = 100;
var
wsa : TWSAData;
s, s2 : TSocket;
local, remote : TSockAddrIn;
v, v2 : string;
status, slen, a, b, c, tmp, ix, buflen : integer;
buf : array of byte;
len : 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 *)
buflen := sizeof(tmp);
ix := 0;
while ix < buflen do begin
ix := ix + Recv(s2, PChar(@tmp) + ix, buflen - ix, 0);
end;
a := ntohl(tmp);
buflen := sizeof(tmp);
ix := 0;
while ix < buflen do begin
ix := ix + Recv(s2, PChar(@tmp) + ix, buflen - ix, 0);
end;
b := ntohl(tmp);
(* calculate c and send *)
c := a + b;
tmp := htonl(c);
status := Send(s2, @tmp, sizeof(tmp), 0);
if status < 0 then begin
writeln('Error sending c: ' + SysErrorMessage(GetLastError));
halt;
end;
(* read v *)
Recv(s2, @len, sizeof(len), 0);
SetLength(buf, len);
buflen := len;
ix := 0;
while ix < buflen do begin
ix := ix + Recv(s2, buf[Low(buf) + ix], buflen - ix, 0);
end;
SetString(v, PAnsiChar(@buf[Low(buf)]), len);
(* calculate v2 and send *)
v2 := v + v;
len := Length(v2);
status := Send(s2, len, sizeof(len), 0);
if status < 0 then begin
writeln('Error sending v2: ' + SysErrorMessage(GetLastError));
halt;
end;
status := Send(s2, v2[1], len, 0);
if status < 0 then begin
writeln('Error sending v2: ' + SysErrorMessage(GetLastError));
halt;
end;
CloseSocket(s2);
end;
CloseSocket(s);
WSACleanup;
end.
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 have DataInputStream and DataOutputStream classes for convenient reading and writing binary data.
Note that DataInputStream and DataOutputStream always use big endian.
package socket.plain.binary.traditional;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
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 {
System.out.printf("(handler in thread %d)\n", Thread.currentThread().getId());
// get streams
DataInputStream dis = new DataInputStream(s.getInputStream());
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
// read a and b
int a = dis.readInt();
int b = dis.readInt();
// calculate c and send
int c = a + b;
dos.writeInt(c);
dos.flush();
// read v
int buflen = dis.readByte();
byte[] buf = new byte[buflen];
int ix = 0;
while(ix < buf.length) {
ix += dis.read(buf, ix, buf.length - ix);
}
String v = new String(buf, "UTF-8");
// calculate v2 and send
String v2 = v + v;
byte[] v2b = v2.getBytes("UTF-8");
dos.writeByte(v2b.length);
dos.write(v2b);
dos.flush();
// close connection
dis.close();
dos.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();
}
}
}
Java 1.4 added several new IO features called NIO including a SocketChannel. It is not that much of an improvement for multithreaded server except that is supports both little and big endian.
package socket.plain.binary.modern;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class MultiServer {
public static class ClientHandler extends Thread {
private SocketChannel sc;
public ClientHandler(SocketChannel sc) {
this.sc = sc;
}
@Override
public void run() {
try {
System.out.printf("(handler in thread %d)\n", Thread.currentThread().getId());
ByteBuffer bb;
// read a and b
bb = ByteBuffer.allocate(8);
while(bb.hasRemaining() && sc.read(bb) > 0);
bb.rewind();
bb.order(ByteOrder.BIG_ENDIAN);
int a = bb.getInt();
int b = bb.getInt();
// calculate c and send
int c = a + b;
bb = ByteBuffer.allocate(4);
bb.order(ByteOrder.BIG_ENDIAN);
bb.putInt(c);
bb.rewind();
sc.write(bb);
// read v
bb = ByteBuffer.allocate(1);
sc.read(bb);
bb.rewind();
int buflen = bb.get();
bb = ByteBuffer.allocate(buflen);
while(bb.hasRemaining() && sc.read(bb) > 0);
bb.rewind();
byte[] buf = new byte[buflen];
bb.get(buf);
String v = new String(buf, "UTF-8");
// calculate v2 and send
String v2 = v + v;
byte[] v2b = v2.getBytes("UTF-8");
bb = ByteBuffer.allocate(1 + v2b.length);
bb.put((byte)v2b.length);
bb.put(v2b);
bb.rewind();
sc.write(bb);
// close connection
sc.close();
} catch (IOException e) {
// TODO
e.printStackTrace();
}
}
}
private static final int PORT = 12345;
public static void main(String[] args) {
try {
// listen on port
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(PORT));
while(true) {
// accept connection
SocketChannel sc = ssc.accept();
System.out.printf("(connection from %s)\n", sc.getRemoteAddress());
// start new thread to handle connection
(new ClientHandler(sc)).start();
}
} catch (IOException e) {
// TODO
e.printStackTrace();
}
}
}
.NET provide two levels of socket API:
This is the low level API.
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace SocketDemo.Binary.Low.MultiServer
{
public class Program
{
private static byte[] IntToBytes(int v)
{
byte[] buf = BitConverter.GetBytes(v);
if(BitConverter.IsLittleEndian) Array.Reverse(buf);
return buf;
}
private static int BytesToInt(byte[] buf)
{
if(BitConverter.IsLittleEndian) Array.Reverse(buf);
return BitConverter.ToInt32(buf, 0);
}
private class ClientHandler
{
private Socket s;
public ClientHandler(Socket s)
{
this.s = s;
}
public void Run()
{
Console.WriteLine("(handler in thread {0})", Thread.CurrentThread.ManagedThreadId);
byte[] buf;
int ix;
// read a and b
buf = new Byte[4];
ix = 0;
while(ix < buf.Length)
{
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None);
}
int a = BytesToInt(buf);
buf = new Byte[4];
ix = 0;
while(ix < buf.Length)
{
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None);
}
int b = BytesToInt(buf);
// calculate c and send
int c = a + b;
s.Send(IntToBytes(c));
// read v
buf = new Byte[1];
s.Receive(buf);
int buflen = buf[0];
buf = new byte[buflen];
ix = 0;
while(ix < buf.Length)
{
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None);
}
string v = Encoding.UTF8.GetString(buf);
// calculate v2 and send
string v2 = v + v;
byte[] v2b = Encoding.UTF8.GetBytes(v2);
s.Send(new byte[] { (byte)v2b.Length });
s.Send(v2b);
// close connection
s.Close();
}
}
private const int PORT = 12345;
private const int BACKLOG = 100;
public static void Main(string[] args)
{
// listen on port
Socket ss = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
ss.Bind(new IPEndPoint(IPAddress.Any, PORT));
ss.Listen(BACKLOG);
while(true) {
// accept connection
Socket s = ss.Accept();
Console.WriteLine("(connection from {0})", s.RemoteEndPoint);
(new Thread((new ClientHandler(s)).Run)).Start();
}
}
}
}
.NET provide two levels of socket API:
This is the high level API.
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace SocketDemo.Binary.High.MultiServer
{
public class Program
{
private static int HTON(int v)
{
if(BitConverter.IsLittleEndian)
{
byte[] buf = BitConverter.GetBytes(v);
Array.Reverse(buf);
return BitConverter.ToInt32(buf, 0);
}
else
{
return v;
}
}
private static int NTOH(int v)
{
if(BitConverter.IsLittleEndian)
{
byte[] buf = BitConverter.GetBytes(v);
Array.Reverse(buf);
return BitConverter.ToInt32(buf, 0);
}
else
{
return v;
}
}
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();
BinaryReader br = new BinaryReader(stm);
BinaryWriter bw = new BinaryWriter(stm);
// read a and b
int a = NTOH(br.ReadInt32());
int b = NTOH(br.ReadInt32());
// calculate c and send
int c = a + b;
bw.Write(HTON(c));
bw.Flush();
// read v
int buflen = br.ReadByte();
byte[] buf = new byte[buflen];
int ix = 0;
while(ix < buf.Length)
{
ix += br.Read(buf, ix, buf.Length - ix);
}
string v = Encoding.UTF8.GetString(buf);
// calculate v2 and send
string v2 = v + v;
byte[] v2b = Encoding.UTF8.GetBytes(v2);
bw.Write((byte)v2b.Length);
bw.Write(v2b);
bw.Flush();
// close connection
br.Close();
bw.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:
This is the low level API.
Imports System
Imports System.Net
Imports System.Net.Sockets
Imports System.Text
Imports System.Threading
Namespace SocketDemo.Binary.Low.MultiServer
Public Class Program
Private Shared Function IntToBytes(v As Integer) As Byte()
Dim buf As Byte() = BitConverter.GetBytes(v)
If BitConverter.IsLittleEndian Then
Array.Reverse(buf)
End If
Return buf
End Function
Private Shared Function BytesToInt(buf As Byte()) As Integer
If BitConverter.IsLittleEndian Then
Array.Reverse(buf)
End If
Return BitConverter.ToInt32(buf, 0)
End Function
Private Class ClientHandler
Private s As Socket
Public Sub New(s As Socket)
Me.s = s
End Sub
Public Sub Run()
Console.WriteLine("(handler in thread {0})", Thread.CurrentThread.ManagedThreadId)
Dim buf As Byte()
Dim ix As Integer
' read a and b
buf = New [Byte](3) {}
ix = 0
While ix < buf.Length
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None)
End While
Dim a As Integer = BytesToInt(buf)
buf = New [Byte](3) {}
ix = 0
While ix < buf.Length
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None)
End While
Dim b As Integer = BytesToInt(buf)
' calculate c and send
Dim c As Integer = a + b
s.Send(IntToBytes(c))
' read v
buf = New [Byte](0) {}
s.Receive(buf)
Dim buflen As Integer = buf(0)
buf = New Byte(buflen - 1) {}
ix = 0
While ix < buf.Length
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None)
End While
Dim v As String = Encoding.UTF8.GetString(buf)
' calculate v2 and send
Dim v2 As String = v & v
Dim v2b As Byte() = Encoding.UTF8.GetBytes(v2)
s.Send(New Byte() {CByte(v2b.Length)})
s.Send(v2b)
' close connection
s.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 ss As New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
ss.Bind(New IPEndPoint(IPAddress.Any, PORT))
ss.Listen(BACKLOG)
While True
' accept connection
Dim s As Socket = ss.Accept()
Console.WriteLine("(connection from {0})", s.RemoteEndPoint)
Dim t As Thread = New Thread(AddressOf (New ClientHandler(s)).Run)
t.Start()
End While
End Sub
End Class
End Namespace
.NET provide two levels of socket API:
This is the high level API.
Imports System
Imports System.IO
Imports System.Net
Imports System.Net.Sockets
Imports System.Text
Imports System.Threading
Namespace SocketDemo.Binary.High.MultiServer
Public Class Program
Private Shared Function HTON(v As Integer) As Integer
If BitConverter.IsLittleEndian Then
Dim buf As Byte() = BitConverter.GetBytes(v)
Array.Reverse(buf)
Return BitConverter.ToInt32(buf, 0)
Else
Return v
End If
End Function
Private Shared Function NTOH(v As Integer) As Integer
If BitConverter.IsLittleEndian Then
Dim buf As Byte() = BitConverter.GetBytes(v)
Array.Reverse(buf)
Return BitConverter.ToInt32(buf, 0)
Else
Return v
End If
End Function
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 br As New BinaryReader(stm)
Dim bw As New BinaryWriter(stm)
' read a and b
Dim a As Integer = NTOH(br.ReadInt32())
Dim b As Integer = NTOH(br.ReadInt32())
' calculate c and send
Dim c As Integer = a + b
bw.Write(HTON(c))
bw.Flush()
' read v
Dim buflen As Integer = br.ReadByte()
Dim buf As Byte() = New Byte(buflen - 1) {}
Dim ix As Integer = 0
While ix < buf.Length
ix += br.Read(buf, ix, buf.Length - ix)
End While
Dim v As String = Encoding.UTF8.GetString(buf)
' calculate v2 and send
Dim v2 As String = v & v
Dim v2b As Byte() = Encoding.UTF8.GetBytes(v2)
bw.Write(CByte(v2b.Length))
bw.Write(v2b)
bw.Flush()
' close connection
br.Close()
bw.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 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 PlainBinMultiServerLow;
uses
Classes,
SysUtils,
Windows,
WinSock;
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
v, v2 : string;
status, a, b, c, tmp, ix, buflen : integer;
buf : array of byte;
len : byte;
begin
(* read a and b *)
buflen := sizeof(tmp);
ix := 0;
while ix < buflen do begin
ix := ix + Recv(_s2, PChar(@tmp) + ix, buflen - ix, 0);
end;
a := ntohl(tmp);
buflen := sizeof(tmp);
ix := 0;
while ix < buflen do begin
ix := ix + Recv(_s2, PChar(@tmp) + ix, buflen - ix, 0);
end;
b := ntohl(tmp);
(* calculate c and send *)
c := a + b;
tmp := htonl(c);
status := Send(_s2, @tmp, sizeof(tmp), 0);
if status < 0 then begin
writeln('Error sending c: ' + SysErrorMessage(GetLastError));
halt;
end;
(* read v *)
Recv(_s2, @len, sizeof(len), 0);
SetLength(buf, len);
buflen := len;
ix := 0;
while ix < buflen do begin
ix := ix + Recv(_s2, buf[Low(buf) + ix], buflen - ix, 0);
end;
SetString(v, PAnsiChar(@buf[Low(buf)]), len);
(* calculate v2 and send *)
v2 := v + v;
len := Length(v2);
status := Send(_s2, len, sizeof(len), 0);
if status < 0 then begin
writeln('Error sending v2: ' + SysErrorMessage(GetLastError));
halt;
end;
status := Send(_s2, v2[1], len, 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.
Multiplexed server logic is:
function accepthandler accept add to selector endfunction function inputhandler receive/read send/write close endfunction listen add to selector while select from selector for all activity call handler endfor endwhile
Note that a multiplexed server is only using a single thread.
Java 1.4 added several new IO features called NIO including a SocketChannel. One of the advantages is that it supports select/multiplexing.
package socket.plain.binary.modern;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
public class SelectServer {
public static interface EventHandler {
public void process() throws IOException;
}
public static class ConnectionHandler implements EventHandler {
private ServerSocketChannel ssc;
private Selector sel;
public ConnectionHandler(ServerSocketChannel ssc, Selector sel) {
this.ssc = ssc;
this.sel = sel;
}
@Override
public void process() throws IOException {
// accept connection
SocketChannel sc = ssc.accept();
System.out.printf("(connection from %s)\n", sc.getRemoteAddress());
sc.configureBlocking(false);
// register receive integers handler
sc.register(sel, SelectionKey.OP_READ, new ReceiveIntegersHandler(sc, sel));
}
}
public static class ReceiveIntegersHandler implements EventHandler {
private SocketChannel sc;
private Selector sel;
public ReceiveIntegersHandler(SocketChannel sc, Selector sel) {
this.sc = sc;
this.sel = sel;
}
@Override
public void process() throws IOException {
ByteBuffer bb;
// read a and b
bb = ByteBuffer.allocate(8);
while(bb.hasRemaining() && sc.read(bb) >= 0);
bb.rewind();
bb.order(ByteOrder.BIG_ENDIAN);
int a = bb.getInt();
int b = bb.getInt();
// calculate c and send
int c = a + b;
bb = ByteBuffer.allocate(4);
bb.order(ByteOrder.BIG_ENDIAN);
bb.putInt(c);
bb.rewind();
sc.write(bb);
// register receive string handler
sc.register(sel, SelectionKey.OP_READ, new ReceiveStringHandler(sc));
}
}
public static class ReceiveStringHandler implements EventHandler {
private SocketChannel sc;
public ReceiveStringHandler(SocketChannel sc) {
this.sc = sc;
}
@Override
public void process() throws IOException {
ByteBuffer bb;
// read v
bb = ByteBuffer.allocate(1);
while(bb.hasRemaining() && sc.read(bb) >= 0);
bb.rewind();
int buflen = bb.get();
bb = ByteBuffer.allocate(buflen);
while(bb.hasRemaining() && sc.read(bb) >= 0);
bb.rewind();
byte[] buf = new byte[buflen];
bb.get(buf);
String v = new String(buf, "UTF-8");
// calculate v2 and send
String v2 = v + v;
byte[] v2b = v2.getBytes("UTF-8");
bb = ByteBuffer.allocate(1 + v2b.length);
bb.put((byte)v2b.length);
bb.put(v2b);
bb.rewind();
sc.write(bb);
// close connection
sc.close();
}
}
private static final int PORT = 12345;
public static void main(String[] args) {
try {
// selector initiating all events
Selector sel = Selector.open();
// listen on port
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(PORT));
ssc.configureBlocking(false);
// register connection handler
ssc.register(sel, SelectionKey.OP_ACCEPT, new ConnectionHandler(ssc, sel));
while(true) {
sel.select();
Iterator<SelectionKey> it = sel.selectedKeys().iterator();
while(it.hasNext()) {
SelectionKey sk = it.next();
EventHandler eh = (EventHandler) sk.attachment();
eh.process();
it.remove();
}
}
} catch (IOException e) {
// TODO
e.printStackTrace();
}
}
}
.NET provide two levels of socket API:
Only the low level API supports multiplexing.
Note that the style used here is not common, but I think it does make sense.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace SocketDemo.Binary.Low.SingleServer
{
public class Program
{
private static byte[] IntToBytes(int v)
{
byte[] buf = BitConverter.GetBytes(v);
if(BitConverter.IsLittleEndian) Array.Reverse(buf);
return buf;
}
private static int BytesToInt(byte[] buf)
{
if(BitConverter.IsLittleEndian) Array.Reverse(buf);
return BitConverter.ToInt32(buf, 0);
}
public delegate void EventHandler(Socket s, IList sel, IDictionary<Socket,EventHandler> h);
public static void AcceptHandler(Socket ss, IList sel, IDictionary<Socket,EventHandler> h)
{
// accept connection
Socket s = ss.Accept();
Console.WriteLine("(connection from {0})", s.RemoteEndPoint);
// next handler
sel.Add(s);
h[s] = ReceiveIntegerHandler;
}
public static void ReceiveIntegerHandler(Socket s, IList sel, IDictionary<Socket,EventHandler> h)
{
byte[] buf;
int ix;
// read a and b
buf = new Byte[4];
ix = 0;
while(ix < buf.Length)
{
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None);
}
int a = BytesToInt(buf);
buf = new Byte[4];
ix = 0;
while(ix < buf.Length)
{
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None);
}
int b = BytesToInt(buf);
// calculate c and send
int c = a + b;
s.Send(IntToBytes(c));
// next handler
h[s] = ReceiveStringHandler;
}
public static void ReceiveStringHandler(Socket s, IList sel, IDictionary<Socket,EventHandler> h)
{
byte[] buf;
int ix;
// read v
buf = new Byte[1];
s.Receive(buf);
int buflen = buf[0];
buf = new byte[buflen];
ix = 0;
while(ix < buf.Length)
{
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None);
}
string v = Encoding.UTF8.GetString(buf);
// calculate v2 and send
string v2 = v + v;
byte[] v2b = Encoding.UTF8.GetBytes(v2);
s.Send(new byte[] { (byte)v2b.Length });
s.Send(v2b);
// close connection
s.Close();
// next handler
sel.Remove(s);
h.Remove(s);
}
private const int PORT = 12345;
private const int BACKLOG = 100;
public static void Main(string[] args)
{
// listen on port
Socket ss = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
ss.Bind(new IPEndPoint(IPAddress.Any, PORT));
ss.Listen(BACKLOG);
IList sel = new ArrayList();
IDictionary<Socket,EventHandler> h = new Dictionary<Socket,EventHandler>();
sel.Add(ss);
h.Add(ss, AcceptHandler);
while(true) {
// select
IList sav = new ArrayList(sel);
Socket.Select(sel, null, null, 1000000);
foreach(Socket s in sel)
{
h[s](s, sav, h);
}
sel = sav;
}
}
}
}
.NET provide two levels of socket API:
Only the low level API supports multiplexing.
Note that the style used here is not common, but I think it does make sense.
Imports System
Imports System.Collections
Imports System.Collections.Generic
Imports System.Net
Imports System.Net.Sockets
Imports System.Text
Namespace SocketDemo.Binary.Low.SingleServer
Public Class Program
Private Shared Function IntToBytes(v As Integer) As Byte()
Dim buf As Byte() = BitConverter.GetBytes(v)
If BitConverter.IsLittleEndian Then
Array.Reverse(buf)
End If
Return buf
End Function
Private Shared Function BytesToInt(buf As Byte()) As Integer
If BitConverter.IsLittleEndian Then
Array.Reverse(buf)
End If
Return BitConverter.ToInt32(buf, 0)
End Function
Public Delegate Sub EventHandler(s As Socket, sel As IList, h As IDictionary(Of Socket, EventHandler))
Public Shared Sub AcceptHandler(ss As Socket, sel As IList, h As IDictionary(Of Socket, EventHandler))
' accept connection
Dim s As Socket = ss.Accept()
Console.WriteLine("(connection from {0})", s.RemoteEndPoint)
' next handler
sel.Add(s)
h(s) = AddressOf ReceiveIntegerHandler
End Sub
Public Shared Sub ReceiveIntegerHandler(s As Socket, sel As IList, h As IDictionary(Of Socket, EventHandler))
Dim buf As Byte()
Dim ix As Integer
' read a and b
buf = New [Byte](3) {}
ix = 0
While ix < buf.Length
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None)
End While
Dim a As Integer = BytesToInt(buf)
buf = New [Byte](3) {}
ix = 0
While ix < buf.Length
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None)
End While
Dim b As Integer = BytesToInt(buf)
' calculate c and send
Dim c As Integer = a + b
s.Send(IntToBytes(c))
' next handler
h(s) = AddressOf ReceiveStringHandler
End Sub
Public Shared Sub ReceiveStringHandler(s As Socket, sel As IList, h As IDictionary(Of Socket, EventHandler))
Dim buf As Byte()
Dim ix As Integer
' read v
buf = New [Byte](0) {}
s.Receive(buf)
Dim buflen As Integer = buf(0)
buf = New Byte(buflen - 1) {}
ix = 0
While ix < buf.Length
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None)
End While
Dim v As String = Encoding.UTF8.GetString(buf)
' calculate v2 and send
Dim v2 As String = v & v
Dim v2b As Byte() = Encoding.UTF8.GetBytes(v2)
s.Send(New Byte() {CByte(v2b.Length)})
s.Send(v2b)
' close connection
s.Close()
' next handler
sel.Remove(s)
h.Remove(s)
End Sub
Private Const PORT As Integer = 12345
Private Const BACKLOG As Integer = 100
Public Shared Sub Main(args As String())
' listen on port
Dim ss As New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
ss.Bind(New IPEndPoint(IPAddress.Any, PORT))
ss.Listen(BACKLOG)
Dim sel As IList = New ArrayList()
Dim h As IDictionary(Of Socket, EventHandler) = New Dictionary(Of Socket, EventHandler)()
sel.Add(ss)
h.Add(ss, AddressOf AcceptHandler)
While True
' select
Dim sav As IList = New ArrayList(sel)
Socket.[Select](sel, Nothing, Nothing, 1000000)
For Each s As Socket In sel
h(s)(s, sav, h)
Next
sel = sav
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 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.
Note that the style used here is not common, but I think it does make sense.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef WIN32
#define FD_SETSIZE 1024
#include <winsock2.h>
#else
#include <sys/socket.h>
#include <netdb.h>
#endif
#include <errno.h>
#define PORT 12345
#define BACKLOG 100
struct handler2;
typedef void (*handler)(int sd2, fd_set *sel, struct handler2 *h);
struct handler2
{
handler fptr;
};
void accept_handler(int sd, fd_set *sel, struct handler2 *h);
void integer_handler(int sd2, fd_set *sel, struct handler2 *h);
void string_handler(int sd2, fd_set *sel, struct handler2 *h);
void accept_handler(int sd, fd_set *sel, struct handler2 *h)
{
int sd2, slen;
struct sockaddr_in remote;
/* 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));
/* next handler */
FD_SET(sd2, sel);
h[sd2].fptr = integer_handler;
}
void integer_handler(int sd2, fd_set *sel, struct handler2 *h)
{
int status, ix, buflen;
long int a, b, c, tmp;
/* read a and b */
buflen = sizeof(tmp);
ix = 0;
while(ix < buflen)
{
ix = ix + recv(sd2, (char *)&tmp + ix, buflen - ix, 0);
}
a = ntohl(tmp);
buflen = sizeof(tmp);
ix = 0;
while(ix < buflen)
{
ix = ix + recv(sd2, (char *)&tmp + ix, buflen - ix, 0);
}
b = ntohl(tmp);
/* calculate c and send */
c = a + b;
tmp = htonl(c);
status = send(sd2, (char *)&tmp, sizeof(tmp), 0);
if(status < 0)
{
printf("Error sending c: %s\n", strerror(errno));
exit(0);
}
/* next handler */
h[sd2].fptr = string_handler;
}
void string_handler(int sd2, fd_set *sel, struct handler2 *h)
{
int status, ix, buflen;
char *buf, *v, *v2, len;
/* read v */
recv(sd2, &len, sizeof(len), 0);
buflen = len;
buf = malloc(buflen);
ix = 0;
while(ix < buflen)
{
ix = ix + recv(sd2, buf + ix, buflen - ix, 0);
}
v = malloc(buflen + 1);
memcpy(v, buf, buflen);
v[buflen] = '\0';
free(buf);
/* calculate v2 and send */
v2 = malloc(2 * strlen(v) + 1);
strcpy(v2, v);
strcat(v2, v);
len = strlen(v2);
status = send(sd2, &len, sizeof(len), 0);
if(status < 0)
{
printf("Error sending v2: %s\n", strerror(errno));
exit(0);
}
status = send(sd2, v2, len, 0);
if(status < 0)
{
printf("Error sending v2: %s\n", strerror(errno));
exit(0);
}
free(v);
free(v2);
/* close socket */
#ifdef WIN32
closesocket(sd2);
#else
close(sd2);
#endif
/* next handler */
FD_CLR(sd2, sel);
h[sd2].fptr = NULL;
}
int main()
{
int sd, status, i;
struct sockaddr_in local;
fd_set sel, sav;
struct timeval tv;
struct handler2 h[FD_SETSIZE];
#ifdef WIN32
WSADATA WSAData;
if(WSAStartup(0x0101, &WSAData) != 0)
{
printf("Error initializing\n");
exit(0);
}
#endif
tv.tv_sec = 1;
tv.tv_usec = 0;
/* 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);
}
FD_ZERO(&sel);
FD_SET(sd, &sel);
h[sd].fptr = accept_handler;
for(;;)
{
/* select */
sav = sel;
status = select(FD_SETSIZE, &sel, NULL, NULL, &tv);
if(status < 0)
{
printf("Error selecting: %s\n", strerror(errno));
exit(0);
}
for(i = 0; i < = FD_SETSIZE; i++)
{
if(FD_ISSET(i, &sel))
{
h[i].fptr(i, &sav, h);
}
}
sel = sav;
}
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 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.
Note that the style used here is not common, but I think it does make sense.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef WIN32
#define FD_SETSIZE 1024
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <netdb.h>
#endif
#include <errno.h>
#define PORT "12345"
#define BACKLOG 100
struct handler2;
typedef void (*handler)(int sd2, fd_set *sel, struct handler2 *h);
struct handler2
{
handler fptr;
};
void accept_handler(int sd, fd_set *sel, struct handler2 *h);
void integer_handler(int sd2, fd_set *sel, struct handler2 *h);
void string_handler(int sd2, fd_set *sel, struct handler2 *h);
void accept_handler(int sd, fd_set *sel, struct handler2 *h)
{
int sd2, slen;
struct sockaddr remote;
char addr[INET6_ADDRSTRLEN];
/* 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)));
/* next handler */
FD_SET(sd2, sel);
h[sd2].fptr = integer_handler;
}
void integer_handler(int sd2, fd_set *sel, struct handler2 *h)
{
int status, ix, buflen;
long int a, b, c, tmp;
/* read a and b */
buflen = sizeof(tmp);
ix = 0;
while(ix < buflen)
{
ix = ix + recv(sd2, (char *)&tmp + ix, buflen - ix, 0);
}
a = ntohl(tmp);
buflen = sizeof(tmp);
ix = 0;
while(ix < buflen)
{
ix = ix + recv(sd2, (char *)&tmp + ix, buflen - ix, 0);
}
b = ntohl(tmp);
/* calculate c and send */
c = a + b;
tmp = htonl(c);
status = send(sd2, (char *)&tmp, sizeof(tmp), 0);
if(status < 0)
{
printf("Error sending c: %s\n", strerror(errno));
exit(0);
}
/* next handler */
h[sd2].fptr = string_handler;
}
void string_handler(int sd2, fd_set *sel, struct handler2 *h)
{
int status, ix, buflen;
char *buf, *v, *v2, len;
/* read v */
recv(sd2, &len, sizeof(len), 0);
buflen = len;
buf = malloc(buflen);
ix = 0;
while(ix < buflen)
{
ix = ix + recv(sd2, buf + ix, buflen - ix, 0);
}
v = malloc(buflen + 1);
memcpy(v, buf, buflen);
v[buflen] = '\0';
free(buf);
/* calculate v2 and send */
v2 = malloc(2 * strlen(v) + 1);
strcpy(v2, v);
strcat(v2, v);
len = strlen(v2);
status = send(sd2, &len, sizeof(len), 0);
if(status < 0)
{
printf("Error sending v2: %s\n", strerror(errno));
exit(0);
}
status = send(sd2, v2, len, 0);
if(status < 0)
{
printf("Error sending v2: %s\n", strerror(errno));
exit(0);
}
free(v);
free(v2);
/* close socket */
#ifdef WIN32
closesocket(sd2);
#else
close(sd2);
#endif
/* next handler */
FD_CLR(sd2, sel);
h[sd2].fptr = NULL;
}
int main()
{
int sd, status, i;
struct addrinfo hints, *res;
fd_set sel, sav;
struct timeval tv;
struct handler2 h[FD_SETSIZE];
#ifdef WIN32
WSADATA WSAData;
if(WSAStartup(0x0101, &WSAData) != 0)
{
printf("Error initializing\n");
exit(0);
}
#endif
tv.tv_sec = 1;
tv.tv_usec = 0;
/* 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);
}
FD_ZERO(&sel);
FD_SET(sd, &sel);
h[sd].fptr = accept_handler;
for(;;)
{
/* select */
sav = sel;
status = select(FD_SETSIZE, &sel, NULL, NULL, &tv);
if(status < 0)
{
printf("Error selecting: %s\n", strerror(errno));
exit(0);
}
for(i = 0; i < = FD_SETSIZE; i++)
{
if(FD_ISSET(i, &sel))
{
h[i].fptr(i, &sav, h);
}
}
sel = sav;
}
return 0;
}
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.
.NET provide two levels of socket API:
Only the low level API supports asynchroneous in a meaningful way.
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace SocketDemo.Binary.Low.AsyncServer
{
public class Program
{
private static byte[] IntToBytes(int v)
{
byte[] buf = BitConverter.GetBytes(v);
if(BitConverter.IsLittleEndian) Array.Reverse(buf);
return buf;
}
private static int BytesToInt(byte[] buf)
{
if(BitConverter.IsLittleEndian) Array.Reverse(buf);
return BitConverter.ToInt32(buf, 0);
}
private class ReceiveContext
{
public byte[] Buffer { get; set; }
public Socket Socket { get; set; }
}
private static void ReceiveStringHandler(IAsyncResult ar)
{
int ix;
ReceiveContext rcvctx = (ReceiveContext) ar.AsyncState;
Socket s = rcvctx.Socket;
byte[] buf = rcvctx.Buffer;
// complete read v
s.EndReceive(ar);
int buflen = buf[0];
buf = new byte[buflen];
ix = 0;
while(ix < buf.Length)
{
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None);
}
string v = Encoding.UTF8.GetString(buf);
// calculate v2 and send
string v2 = v + v;
byte[] v2b = Encoding.UTF8.GetBytes(v2);
s.Send(new byte[] { (byte)v2b.Length });
s.Send(v2b);
// close connection
s.Close();
}
private static void ReceiveIntegersHandler(IAsyncResult ar)
{
int ix;
ReceiveContext rcvctx = (ReceiveContext) ar.AsyncState;
Socket s = rcvctx.Socket;
byte[] buf = rcvctx.Buffer;
// complete read a and b
ix = s.EndReceive(ar);
while(ix < buf.Length)
{
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None);
}
int a = BytesToInt(buf);
buf = new Byte[4];
ix = 0;
while(ix < buf.Length)
{
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None);
}
int b = BytesToInt(buf);
// calculate c and send
int c = a + b;
s.Send(IntToBytes(c));
// begin read v
rcvctx.Buffer = new Byte[1];
s.BeginReceive(rcvctx.Buffer, 0, rcvctx.Buffer.Length, 0, ReceiveStringHandler, rcvctx);
}
private static void ConnectionHandler(IAsyncResult ar)
{
Socket ss = (Socket) ar.AsyncState;
// complete accept connection
Socket s = ss.EndAccept(ar);
Console.WriteLine("(connection from {0})", s.RemoteEndPoint);
// begin read a and b
ReceiveContext rcvctx = new ReceiveContext { Buffer = new Byte[4], Socket = s };
s.BeginReceive(rcvctx.Buffer, 0, rcvctx.Buffer.Length, 0, ReceiveIntegersHandler, rcvctx);
// begin accept connection
ss.BeginAccept(ConnectionHandler, ss);
}
private const int PORT = 12345;
private const int BACKLOG = 100;
public static void Main(string[] args)
{
// listen on port
Socket ss = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
ss.Bind(new IPEndPoint(IPAddress.Any, PORT));
ss.Listen(BACKLOG);
// begin accept connection
ss.BeginAccept(ConnectionHandler, ss);
Console.ReadKey();
}
}
}
.NET provide two levels of socket API:
Only the low level API supports asynchroneous in a meaningful way.
Imports System
Imports System.Net
Imports System.Net.Sockets
Imports System.Text
Namespace SocketDemo.Binary.Low.AsyncServer
Public Class Program
Private Shared Function IntToBytes(v As Integer) As Byte()
Dim buf As Byte() = BitConverter.GetBytes(v)
If BitConverter.IsLittleEndian Then
Array.Reverse(buf)
End If
Return buf
End Function
Private Shared Function BytesToInt(buf As Byte()) As Integer
If BitConverter.IsLittleEndian Then
Array.Reverse(buf)
End If
Return BitConverter.ToInt32(buf, 0)
End Function
Private Class ReceiveContext
Public Property Buffer() As Byte()
Public Property Socket() As Socket
End Class
Private Shared Sub ReceiveStringHandler(ar As IAsyncResult)
Dim ix As Integer
Dim rcvctx As ReceiveContext = DirectCast(ar.AsyncState, ReceiveContext)
Dim s As Socket = rcvctx.Socket
Dim buf As Byte() = rcvctx.Buffer
' complete read v
s.EndReceive(ar)
Dim buflen As Integer = buf(0)
buf = New Byte(buflen - 1) {}
ix = 0
While ix < buf.Length
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None)
End While
Dim v As String = Encoding.UTF8.GetString(buf)
' calculate v2 and send
Dim v2 As String = v & v
Dim v2b As Byte() = Encoding.UTF8.GetBytes(v2)
s.Send(New Byte() {CByte(v2b.Length)})
s.Send(v2b)
' close connection
s.Close()
End Sub
Private Shared Sub ReceiveIntegersHandler(ar As IAsyncResult)
Dim ix As Integer
Dim rcvctx As ReceiveContext = DirectCast(ar.AsyncState, ReceiveContext)
Dim s As Socket = rcvctx.Socket
Dim buf As Byte() = rcvctx.Buffer
' complete read a and b
ix = s.EndReceive(ar)
While ix < buf.Length
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None)
End While
Dim a As Integer = BytesToInt(buf)
buf = New [Byte](3) {}
ix = 0
While ix < buf.Length
ix += s.Receive(buf, ix, buf.Length - ix, SocketFlags.None)
End While
Dim b As Integer = BytesToInt(buf)
' calculate c and send
Dim c As Integer = a + b
s.Send(IntToBytes(c))
' begin read v
rcvctx.Buffer = New [Byte](0) {}
s.BeginReceive(rcvctx.Buffer, 0, rcvctx.Buffer.Length, 0, AddressOf ReceiveStringHandler, rcvctx)
End Sub
Private Shared Sub ConnectionHandler(ar As IAsyncResult)
Dim ss As Socket = DirectCast(ar.AsyncState, Socket)
' complete accept connection
Dim s As Socket = ss.EndAccept(ar)
Console.WriteLine("(connection from {0})", s.RemoteEndPoint)
' begin read a and b
Dim rcvctx As New ReceiveContext() With { .Buffer = New [Byte](3) {}, .Socket = s }
s.BeginReceive(rcvctx.Buffer, 0, rcvctx.Buffer.Length, 0, AddressOf ReceiveIntegersHandler, rcvctx)
' begin accept connection
ss.BeginAccept(AddressOf ConnectionHandler, ss)
End Sub
Private Const PORT As Integer = 12345
Private Const BACKLOG As Integer = 100
Public Shared Sub Main(args As String())
' listen on port
Dim ss As New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
ss.Bind(New IPEndPoint(IPAddress.Any, PORT))
ss.Listen(BACKLOG)
' begin accept connection
ss.BeginAccept(AddressOf ConnectionHandler, ss)
Console.ReadKey()
End Sub
End Class
End Namespace
The Java library Netty provide an async framework on top of NIO.
package socket.plain.binary.netty;
import java.io.UnsupportedEncodingException;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
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;
public class AsyncServer {
public static enum NextPhase { WAIT_INTADD, WAIT_STRDUP, DONE }
private static class ReadHandler extends ChannelInboundHandlerAdapter {
private ByteBuf inbb;
private NextPhase state;
public ReadHandler(Channel ch) {
inbb = ch.alloc().buffer();
state = NextPhase.WAIT_INTADD;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws UnsupportedEncodingException, InterruptedException {
// append bytes read read from socket to buffer
inbb.writeBytes((ByteBuf)msg);
// act based on current state
switch(state) {
case WAIT_INTADD:
// test if received a and b
if(inbb.readableBytes() >= 8) {
// read a and b
int a = inbb.getInt(0);
int b = inbb.getInt(4);
// calculate c and send
int c = a + b;
ByteBuf outbb = ctx.channel().alloc().buffer();
outbb.writeInt(c);
ctx.channel().writeAndFlush(outbb);
// update state
state = NextPhase.WAIT_STRDUP;
}
break;
case WAIT_STRDUP:
// test if received buflen
if(inbb.readableBytes() >= 9) {
// read v
int buflen = inbb.getByte(8);
if(inbb.readableBytes() < 9 + buflen) return; // test if received buf
byte[] buf = new byte[buflen];
inbb.getBytes(9, buf, 0, buflen);
String v = new String(buf, "UTF-8");
// calculate v2 and send
String v2 = v + v;
ByteBuf outbb = ctx.channel().alloc().buffer();
outbb.writeByte(v2.length());
outbb.writeBytes(v2.getBytes("UTF-8"));
ctx.channel().writeAndFlush(outbb);
// 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 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();
}
}
}
Version | Date | Description |
---|---|---|
1.0 | May 2nd 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 |
See list of all articles here
Please send comments to Arne Vajhøj