TCP

General

Introduction

IP looks after the Network layer, routing packets from one host to another for particular services. The next layer up is the Transport layer, responsible for transferring data from one host to the other, looking after reliability, re-transmission of packets (if appropriate), flow control, etc.

There are many protocols in use at this layer. Under Unix/Linux the file /etc/protocols contains a list of over 100. TCP (transmission control protocol) is one of the two principal protocols in TCP/IP networking. The other, UDP is dealt with in the next chapter.

TCP is connection oriented, meaning that it maintains a connection between two hosts and that packets flow sequentially along this connection. So packets are delivered in order. TCP also has a mechanism to ensure that packets are acknowledged and may be re-transmitted if lost. It also exercises flow control with packets send backing off under network load for example.

Port addresses

TCP and many other protocols use the concept of a port to identify a service. Both ends of a TCP connection have a port number, in the range 0 - 65535. A service provider will usually have a "well known" port, and on Unix/Linux these are listed in the file /etc/services. Examples are 80 for HTTP, 443 for HTTPS, 22 for SSH and 21 for FTP control. Ports in the range 0 - 1023 are usually reserved for system-controlled ports. Clients will typically have a "transient" port, in the range 49152 - 65535.

Sockets

The programming abstraction for TCP is that of socket. A socket represents a host IP address and a port number. Of course, there is more to it than that - a socket has state such as being connected to another socket, and is used to send and receive packets.

The example used below is of an echo client/server. The client reads lines from the console and writes to the socket. The server reads each line from its socket and writes the line back to the client which prints it.

Pseudocode for the client is


connect a socket to the server
do
    read a line from the console
    write the line to the socket
    read the line back from the socket
    write the line to the console
until line == "BYE"
      

Pseudocode for the server is


while true
    accept an incoming socket
    read data from the socket
    if the data is empty
        close the socket and return
    write the data back to the socket
    if the line == "BYE"
        close the socket
      

Data format

The data sent in a TCP packet is binary data: the maximum size of a TCP packet (including headers) is 65535 bytes, but more typically is constrained to be the MTU of the network, usually about 1500 bytes. Data larger than that is fragmented across multiple packets, but usually the API will reassemble them for you.

Stevens recommends against sending binary data without extra work: hosts may be little-endian or big-endian, some may have 32 bit integers others will have 64 bits, and some languages will add padding to data structures. Managing binary data is discussed in the next chapter.

Text has many complexities too: character sets for different languages, different encodings and so on. This is discussed in a later chapter too.

For this example, we are sending data from a client to a server which doesn't attempt to do any thing with it, and just sends it back unchanged to the client that sent it. So the complexities don't arise here and we jsut look at how to establish a connection and transfer data between them.

Dual stack servers

Once there were only IPv4 hosts. Sometime mythical time in the future there may be only IPv6 hosts. In the meantime, there are dual IPv4/IPv6 hosts running both IPv4 and IPv6 stacks. Can IPv4 talk to IPv6 or vice versa? There is no way of squashing an IPv6 address into an IPv4 one, but there is a standard way of converting an IPv4 address into an IPv6 one. Most dual stack systems can do this automatically.

Both IPv4 and IPv6 clients can talk to an IPv6 server. The IPv4 addresses are converted by the receiving O/S into IPv6 addresses and handled by the IPv6 server. The programming language APIs don't always make it clear how they are managing this though.

Java

Socket address type

Java has the type InetSocketAddress to represent an IP address and a port. There are several constructors including


      InetSocketAddress​(InetAddress addr, int port)
      InetSocketAddress​(String hostname, int port)
  
We won't use these much.

Client

A client creates a socket that is intended for connection to a server socket somewhere. The most common constructors are


public Socket​(String host,
              int port)
       throws UnknownHostException,
              IOException;
public Socket​(InetAddress address,
              int port)
       throws IOException
  

Once a socket is created, it exchanges messages with a server as in

The example that follows is a simple "echo" client, intended to connect to an "echo" server discussed later. The client read strings from the console, sends them to the server as binary data, which echoes them back to the client. The client then reads another line, etc. The client terminates the conection when the user types "BYE", and then the server closes its connection.

Converting between strings and bytes is discussed in detail in the chapter on Text handling. To make it simple here, we wrap the input and output streams in BufferedReader an PrintStream and read and write lines of text. The read and write routines will look after converting to bytes.

Java has an exception mechanism to catch errors. Sometimes this rather gets in the way. This version is one that basically ignores all possible errors and is not recommended BadEchoClient.java:


import java.io.*;
import java.net.*;

public class BadEchoClient {

    public static final int SERVER_PORT = 2000;
    public static String CR_LF = "\r\n";
    
    public static void main(String[] args){
	try {

	    InetAddress address = InetAddress.getByName(args[0]);

	    Socket sock = new Socket(address, SERVER_PORT);

	    InputStream in = sock.getInputStream();

	    OutputStream out = sock.getOutputStream();

	    BufferedReader socketReader = new BufferedReader(new InputStreamReader(in));

	    PrintStream socketWriter = new PrintStream(out);

	    BufferedReader consoleReader =  
		new BufferedReader(new InputStreamReader(System.in));

	    String line = null;
	    while (true) {
		System.out.print("Enter line:");
		line = consoleReader.readLine();
		if (line.equals("BYE")
		    break;
		socketWriter.println(line);
		System.out.println(socketReader.readLine());
	    }
	} catch (Exception e) {
	    System.out.println("Something went wrong somewhere");
	}
    } // BadEchoClient
}

But the version with appropriate error handling is rather gruesome. Is it necessary? Yes. Network programs are susceptible to far more errors than standalone programs. Ignore them at your peril. The revised client is EchoClient.java:


import java.io.*;
import java.net.*;

public class EchoClient {

    public static final int SERVER_PORT = 2000;
    
    public static void main(String[] args){

	if (args.length != 1) {
	    System.err.println("Usage: Client address");
	    System.exit(1);
	}

	InetAddress address = null;
	try {
	    address = InetAddress.getByName(args[0]);
	} catch(UnknownHostException e) {
	    e.printStackTrace();
	    System.exit(2);
	}

	Socket sock = null;
	try {
	    sock = new Socket(address, SERVER_PORT);
	} catch(IOException e) {
	    e.printStackTrace();
	    System.exit(3);
	}

	InputStream in = null;
	try {
	    in = sock.getInputStream();
	} catch(IOException e) {
	    e.printStackTrace();
	    System.exit(4);
	}

	OutputStream out = null;
	try {
	    out = sock.getOutputStream();
	} catch(IOException e) {
	    e.printStackTrace();
	    System.exit(5);
	}

	BufferedReader socketReader = new BufferedReader(new InputStreamReader(in));

	PrintStream socketWriter = new PrintStream(out);

	BufferedReader consoleReader =  
                   new BufferedReader(new InputStreamReader(System.in));

	String line = null;
	while (true) {
	    line = null;
	    try {
		System.out.print("Enter line:");
		line = consoleReader.readLine();
		System.out.format("Read line: '%s'\n", line);
	    } catch(IOException e) {
		e.printStackTrace();
		System.exit(6);
	    }

	    if (line.equals("BYE"))
		break;
	    
	    try {
		socketWriter.println(line);
	    } catch(Exception e) {
		e.printStackTrace();
		System.exit(7);
	    }

	    try {
		line = socketReader.readLine();
		System.out.format("Echoed: '%s'\n", line);
	    } catch(IOException e) {
		e.printStackTrace();
		System.exit(8);
	    }
	}
	
	System.exit(0);
    }
} // EchoClient

The commmand takes a single argument, a host identifier. This can be a hostname such as jan.newmarch.name, an IPv4 address such as 127.0.0.1 or an IPv4 address such as ::1. It is run by


      java EchoClient localhost
  

Server

A server creates a ServerSocket. A constructor can take a port number on which the server listens. It then goes into a blocking state waiting for a client to connect, using the method accept(). This returns with a Socket connected to the client. Input and output then takes place on this client socket. When finished, the client socket should be closed.

A ServerSocket on a dual stack server (both IPv4 and IPv6) will listen on both the IPv4 and IPv6 ports using an IPv6 server socket (see Oracle Networking IPv6 User Guide. This means that both IPv4 and IPv6 clients can connect without any code changes.

A echo server for the echo client follows. The first version is the BadEchoServer.java as it ignores all errors (but is readable):


import java.io.*;
import java.net.*;

public class BadEchoServer {
    
    public static int MYECHOPORT = 2000;

    public static void main(String argv[]) throws Exception {
	ServerSocket s = null;

	s = new ServerSocket(MYECHOPORT);

	while (true) {
	    Socket incoming = null;
	    incoming = s.accept();
	    System.out.println("Connected");
	    incoming.setSoTimeout(10000); // 10 seconds
		
	    handleSocket(incoming);
	}  
    }
    
    public static void handleSocket(Socket incoming) throws IOException {
	BufferedReader reader =
	    new BufferedReader(new InputStreamReader(
						     incoming.getInputStream()));
	PrintStream out =
	    new PrintStream(incoming.getOutputStream());
	
	boolean done = false;
	while ( ! done) {
	    String str = reader.readLine();
	    if (str == null) {
		done = true;
		System.out.println("Null received");
	    }
	    else {
		out.println(str);
		if (str.trim().equals("BYE")) {
		    done = true;
		    System.out.println("Disconnecting");
		    incoming.close();
		}
	    }	    
	}
    }
}

A version with better error handling is EchoServer.java. Note that when an error occurs in handling a client, the server does not exit, but carries on for the next client.


import java.io.*;
import java.net.*;

public class EchoServer {
    
    public static int MYECHOPORT = 2000;
    //public static String CR_LF = "\r\n";

    public static void main(String argv[]) {
	ServerSocket s = null;
	try {
	    s = new ServerSocket(MYECHOPORT);
	} catch(IOException e) {
	    System.out.println(e);
	    System.exit(1);
	}
	while (true) {
	    Socket incoming = null;
	    try {
		incoming = s.accept();
		System.out.println("Connected");
	    } catch(IOException e) {
		System.out.println(e);
		continue;
	    }

	    handleSocket(incoming);
	}
    }
    
    public static void handleSocket(Socket incoming) {

	byte[] data = new byte[1024];
	InputStream in;
	OutputStream out;
	try {
	    in = incoming.getInputStream();
	    out = incoming.getOutputStream();
	}  catch(IOException e) {
	    System.err.println(e.toString());
	    return;
	}
	int numRead = 0;

	try {
	    while (true) {
		try {
		    numRead = in.read(data);
		} catch(IOException e) {
		    System.err.println(e.toString());
		    return;
		}
		if (numRead == -1) {
		    System.out.println("Disconnecting");
		    return;
		}
		try {
		    out.write(data, 0, numRead);
		} catch(IOException e) {
		    System.err.println(e.toString());
		    return;
		}
	    }		
	} finally {
	    System.out.println("Closing all");
	    try {
		in.close();
		out.close();
		incoming.close();
	    } catch(IOException e) {
		// nothing to do really
	    }
	}
    }
}

Multi-threaded server

The echo server has a serious flaw: it can only handle one client at a time. If another tries to connect while the first is still active, it will get blocked. In java it is easy to run client handlers concurrently by using threads. The change is minor, creating a SocketHandler class which extends Thread: EchoServer.java:


import java.io.*;
import java.net.*;

public class MultiEchoServer {
    
    public static int MYECHOPORT = 2000;

    public static void main(String argv[]) {
	ServerSocket s = null;
	try {
	    s = new ServerSocket(MYECHOPORT);
	} catch(IOException e) {
	    System.out.println(e);
	    System.exit(1);
	}
	System.out.println("Listening");

	while (true) {
	    Socket incoming = null;
	    try {
		incoming = s.accept();
		System.out.println("Connected");
	    } catch(IOException e) {
		System.out.println(e);
		continue;
	    }

	    new SocketHandler(incoming).start();

	}  
    }
}

class SocketHandler extends Thread {

    Socket incoming;

    SocketHandler(Socket incoming) {
	this.incoming = incoming;
    }

    public void run() {
	byte[] data = new byte[1024];
	InputStream in;
	OutputStream out;
	try {
	    in = incoming.getInputStream();
	    out = incoming.getOutputStream();
	}  catch(IOException e) {
	    System.err.println(e.toString());
	    return;
	}
	int numRead = 0;

	try {
	    while (true) {
		try {
		    numRead = in.read(data);
		} catch(IOException e) {
		    System.err.println(e.toString());
		    return;
		}
		if (numRead == -1) {
		    System.out.println("Disconnecting");
		    return;
		}
		try {
		    out.write(data, 0, numRead);
		} catch(IOException e) {
		    System.err.println(e.toString());
		    return;
		}
	    }		
	} finally {
	    System.out.println("Closing all");
	    try {
		in.close();
		out.close();
		incoming.close();
	    } catch(IOException e) {
		// nothing to do really
	    }
	}
    }
}

Socket options

Resources

Go

Socket address type

SocketAddress

Go has the type Addr to represent a network and address:port pair:


type Addr interface {
    Network() string // name of the network (for example, "tcp", "udp")
    String() string  // string form of address (for example, "192.0.2.1:25", "[2001:db8::1]:80")
}
  
We won't be using this.

In many cases, Go uses a string "host:port" to represent a socket address. The function JoinHostPort(host, port string) string can construct such an address. Not a big deal, except for the special case when the host is an IPv6 address (contains a ':'): then it encloses the address in '[...]' as in JoinHostPort("::1",, 80) => "[::1]:80".

Client

The function net.Dial(network, address string) (Conn, error) connects to the address using the given network protocol. This is a general purpose function, and the network protocols include "tcp4" (IPv4-only), "tcp6" (IPv6-only), "udp", "udp4" (IPv4-only), "udp6" (IPv6-only), "ip", "ip4" (IPv4-only), "ip6" (IPv6-only), "unix", "unixgram" and "unixpacket". For TCP, we use "tcp".

As with the Java example, we will read lines from stdin. The simplest method to read a line is by the function Reader.ReadString('\n'). The function returns a string including the terminator. For POSIX systems the line will end in '\n', but for Windows it will end in "\r\n". To test against the "BYE" string we will need to compare the line minus the end of line character(s). The property runtime.GOOS can be used to distinguish between POSIX and Windows.

Reading and writing strings can be done using the bufio package of Reader and Writer. After a write, we also to flush the string to ensure it is written. Like with Java, the conversion to and from bytes will be discussed later.

The client which sends lines to the server and reads them back is EchoClient.go:


/* EchoClient
 */
package main

import (
	"fmt"
	"net"
	"os"
	"runtime"
	"bufio"
)

func main() {
	if len(os.Args) != 2 {
		fmt.Fprint(os.Stderr, "Usage: ", os.Args[0], " host\n")
		os.Exit(1)
	}
	service := os.Args[1] + ":2000"

	conn, err := net.Dial("tcp", service)
	checkError("Dial", err)
	fmt.Println("Connected")

	console :=  bufio.NewReader(os.Stdin)

	reader := bufio.NewReader(conn)
	writer := bufio.NewWriter(conn)

	eol_len := 1 // POSIX: \n
	if runtime.GOOS == "windows" {
		eol_len = 2 // Windows: \r\n
	}
	
	for {
		fmt.Print("Enter line:")
		line, _ := console.ReadString('\n')
		fmt.Println("Line read: '" + line + "'")
		if line[:len(line) - eol_len] == "BYE" {
			break
		}
	
		_, err = writer.WriteString(line)
		writer.Flush()
		checkError("Write", err)
		
		str, err := reader.ReadString('\n')
		checkError("Read", err)
		fmt.Println("Echoed: '" + str + "'")
	}
	os.Exit(0)
}

func checkError(errStr string, err error) {
	if err != nil {
		fmt.Fprint(os.Stderr, errStr, err.Error())
		os.Exit(1)
	}
}

Run by


      go run EchoClient localhost
  

Multi-threaded Server

A server finds a TCP address corresponding to a port using net.ResolveTCPAddr() which takes two arguments: a network type, here "tcp" and a TCP address, here of anyhost plus port ":2000". The server then listens on that address, and will Accept() a connection. Reads and writes are the done on that connection using the Read() and Write() functions.

The server will accept connections both form IPv4 and IPv6 clients. It is multi-threaded, running each connection in a coroutine. It is EchoServer.go:


/* SimpleEchoServer
 */
package main

import (
	"fmt"
	"net"
	"os"
)

func main() {

	service := ":2000"
	tcpAddr, err := net.ResolveTCPAddr("tcp", service)
	checkError(err)

	listener, err := net.ListenTCP("tcp", tcpAddr)
	checkError(err)

	for {
		conn, err := listener.Accept()
		if err != nil {
			continue
		}
		fmt.Println("Connected")
		// run as a coroutine
		go handleClient(conn)
	}
}

func handleClient(conn net.Conn) {
	// close connection on exit
        defer conn.Close()

	var data []byte
	data = make([]byte, 2048, 2048)
	for {
		n, err := conn.Read(data)
		if err != nil {
			fmt.Println("Disconnecting")
			return
		}
		_, err = conn.Write(data[0:n])
		if err != nil {
			return
		}
	}
}

func checkError(err error) {
	if err != nil {
		fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
		os.Exit(1)
	}
}

Socket options

Python

Level

The Python socket interface is very low level, based directly on the BSD C socket API. This makes it pretty gruesome to work with, just like the C version.

Socket address type

For IPv4, a socket address is a pair (host, port) where the host may be a hostname of an IPv4 address. For IPv6, a socket address is a 4-tuple (host, port, flowinfo, scopeid). The flowinfo filed seems to be still experimental and should be set to zero for now ( Flow Labels). The scopeid can be something like the interface name, I think, needed for link local addresses.

Client

Python uses the type socket to connect to a server. You need to specify the IP protocol, AF_INET for IPv4 or AF_INET6 for IPv6, and the socket such as SOCK_STREAM for TCP. Next, the connection has to be to a (address, port) for IPv4 or to a (address, port, flow info, scope id) tuple for IPv6.

We read strings from stdin. The socket reads and writes are in bytes. The other languages considered here have a default encoding of strings to and from bytes. Python prefers to make it explicit. The topic is discussed further in the chapter on Text but here is given explicit encoding as UTF-8.

The IPv4 client is EchoClient.py illustrates these:



# from https://realpython.com/python-sockets/#echo-client-and-server
#!/usr/bin/env python3

import socket
import fileinput
import sys

PORT = 2000       # The port used by the server

if len(sys.argv) < 2:
    print('Usage: comm hostname')
    exit(1)

host = sys.argv[1]

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((host, PORT))
    print('Enter line:', end='', flush=True)
    for line in sys.stdin: # fileinput.input():
        print("Read line '%s'" % line)
        if line.rstrip() == 'BYE':
            s.close()
            exit(0)

        s.sendall(line.encode('utf-8'))
        data = s.recv(1024)
        print("Received '%s'" % str(data,  encoding='utf-8'))
        print('Enter line:', end='', flush=True)



For IPv6, simply change AF_INET to AF_INET6. This will work in cases that do not involve a scopeid (which will probably only occur if you use ambiguous link local addresses).

Server

There are two mechanisms to write servers. One uses the socket type already considered. The other uses a slightly higher level serversocket module. For the sequential server, we use socket. EchoServer.py illustrates this:


# https://realpython.com/python-sockets/#echo-server
# sequential server

import socket

HOST = '' # INADDR_ANY
PORT = 2000

with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    while True:
        conn, addr = s.accept()
        with conn:
            print('Connected by', addr)
            while True:
                data = conn.recv(1024)
                if not data:
                    print('Disconnecting')
                    break
                conn.sendall(data)

Multi-threaded server

For the multi-threaded server, there are three choices:

We take this code from Write a Multithreaded Server in Python , using threads.

The server is MutiEchoServer.py:


# from https://www.techbeamers.com/python-tutorial-write-multithreaded-python-server/

import socket 
from threading import Thread 
from socketserver import ThreadingMixIn 

# Multithreaded Python server : TCP Server Socket Thread Pool
class ClientThread(Thread): 
 
    def __init__(self,ip,port): 
        Thread.__init__(self) 
        self.ip = ip 
        self.port = port 
        print('Connecing')
 
    def run(self): 
        while True : 
            data = conn.recv(2048) 
            if not data:
                    print('Disconnecting')
                    break
            conn.send(data)

# Multithreaded Python server : TCP Server Socket Program Stub
TCP_IP = '0.0.0.0' 
TCP_PORT = 2000 
BUFFER_SIZE = 20  # Usually 1024, but we need quick response 

tcpServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
tcpServer.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
tcpServer.bind((TCP_IP, TCP_PORT)) 
threads = [] 
 
while True: 
    tcpServer.listen(4) 
    print("Multithreaded Python server : Waiting for connections from TCP clients...")
    (conn, (ip,port)) = tcpServer.accept() 
    newthread = ClientThread(ip,port) 
    newthread.start() 
    threads.append(newthread) 
 
for t in threads: 
    t.join() 

Socket options

Resources

Javascript

Socket address type

Client

Node.js uses the type net.Socket. It uses an event driven callback model, so data ead is handled by Socket.on('data', function(data)) {...}. The client is EchoClient.js illustrates this:


//from https://www.hacksparrow.com/nodejs/tcp-socket-programming-in-node-js.html

var net = require('net');

if (process.argv.length < 3) {
    concole.log('Usage: command hostname')
    process.exit(1)
}
hostname = process.argv[2]

var HOST = hostname;
var PORT = 2000;

var client = new net.Socket();
client.connect(PORT, HOST, function() {
  //console.log('CONNECTED TO: ' + HOST + ':' + PORT);
});

// Add a 'data' event handler for the client socket
// data is what the server sent to this socket
client.on('data', function(data) {
    console.log(`Echoed: '${data}'`);
    process.stdout.write("Enter line:")
});

// Add a 'close' event handler for the client socket
client.on('close', function() {
  console.log('Connection closed');
});

// now read from stdin and echo to/from server
const readline = require('readline');

// no newline after prompt
process.stdout.write("Enter line:")

const con = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

con.on('line', (input) => {
    console.log(`Read line: '${input}'`);
    client.write(input);
    if (input == 'BYE') {
        con.close()
        client.destroy()
	process.exit(0);
    }
});

Multi-threaded server

The server is pretty straightforward, following similar lines to the client: EchoServer.js illustrates this:


//from https://www.hacksparrow.com/nodejs/tcp-socket-programming-in-node-js.html



const net = require('net');
const server = net.createServer((c) => {
    // 'connection' listener.
    console.log('client connected');

    c.on('end', () => {
	console.log('client disconnected');
    });

    c.on('data', function(data) {
	c.write(data);
    });

    c.on('close', function() {
	console.log('Connection closed');
    });

    c.on('error', function() {
	c.destroy();
	console.log('Connection error');
    });
});

server.on('error', (err) => {
    throw err;
});

server.listen(2000, () => {
	console.log('server bound');
    });

Socket options

JavaScript Resources

Rust

Socket address type

Client

A client connects to a service using TcpStream::connect() where the service is specified as host:port. It can then read and write bytes to the returned stream. The client is /EchoClient.rs:


use std::io::{self, Read, Write, BufRead};
use std::net::TcpStream;
use std::env;
use std::str;
use std::io::stdout;

fn main() -> std::io::Result<()> {

    let args: Vec<String> = env::args().collect();
    if args.len() < 2 {
        println!("Usage {} hostname", args[0]);
        std::process::exit(1);
    }
    let hostname = &args[1];

    let mut stream = TcpStream::connect(hostname.to_string() + &":2000")
	.expect("Couldn't connect to the server...");

    // from https://stackoverflow.com/questions/30186037/how-can-i-read-a-single-line-from-stdin
    print!("Enter line:"); stdout().flush();
    let stdin = io::stdin();
    for line in stdin.lock().lines() {
	let line = line.unwrap();
	println!("Read line: '{}'", line);
	if &line == "BYE" {
	    break;
	}

	stream.write(line.as_bytes())
	    .expect("Error on write");

	let mut echo_bytes: [u8; 2048] = [0; 2048];
	stream.read(&mut echo_bytes[..])
	    .expect("Error on read");

	let echo = str::from_utf8(&echo_bytes).unwrap();
	println!("Echoed: '{}'", echo);
	print!("Enter line:"); stdout().flush();
    }
    Ok(())
} // the stream is closed here

Multi-threaded Server

The server binds to ANY host and listens by TcpListener::bind(). It accepts a connection using incoming and can then read and write from the resultant stream. If the server wants to listen to IPv4 only, then the ANY host is "0.0.0.0". If it is running a dual IPv4/IPv6 stack, then the ANY host should be "::" (enclosed as "[::}" to avoid confusion with the ':' in "host:port") for both IPv4 and IPv6. The program is /EchoServer.rs:


// From https://gist.github.com/fortruce/828bcc3499eb291e7e17

use std::net::{TcpListener, TcpStream};
use std::thread;
use std::io::Read;
use std::io::Write;

fn handle_client(mut stream: TcpStream) {
    println!("Connected");
    // read 2048 bytes at a time from stream echoing back to stream
    loop {
        let mut read = [0; 2048];
        match stream.read(&mut read) {
            Ok(n) => {
                if n == 0 { 
                    // connection was closed
                    break;
                }
                stream.write(&read[0..n]).unwrap();
            }
            Err(_err) => {
                //panic!(err);
		break;
            }
        }
    }
    println!("Disconnected");
}

fn main() {
    //let listener = TcpListener::bind("0.0.0.0:2000").unwrap();
    let listener = TcpListener::bind("[::]:2000").unwrap();

    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                thread::spawn(move || {
                    handle_client(stream);
                });
            }
            Err(_) => {
                println!("Error");
            }
        }
    }
}

Socket options

Julia

Socket address type

Client

A client connects using the Sockets.connect() function. It can then read and write (newline terminated) strings. The client is EchoClient.jl illustrates these:



using Sockets

if size(ARGS)[1] < 1
   println("Usage: IP hostname")
   exit(1)
end

hostname = ARGS[1]

clientside = connect(hostname, 2000)

print("Enter line:")
for line in eachline(stdin)
    print("Read line: '$line'\n")
    if line == "BYE"
       exit(0)
    end
    write(clientside, string(line, "\n"))
    write(stdout, "Echoed: '", readline(clientside, keep=true), "'")

    print("\nEnter line:")
end

It can be run in a number of ways such as


      julia EchoClient.jl localhost
      julia EchoClient.jl 127.0.0.1
  
but not

      julia EchoClient.jl ::1
  
The implementation currently uses getaddrinfo() with the default of IPv4 address only and won't accept a literal IPv6 address.

Multi-threaded server

A server listens to the zero (ANY) address on the port to accept requests. While the socket remains open, it reads the available bytes and writes the resultant vector back to the socket.

The server is EchoServer.jl illustrates these:



using Sockets

server = listen(IPv6(0), 2000)

while true
    sock = accept(server)
    write(stdout, "Connected")
    @async while isopen(sock)
        bytes = readavailable(sock)
	write(sock, bytes)
    end
end

Socket options

Resources


Copyright © Jan Newmarch, jan@newmarch.name
Creative Commons License
" Network Programming using Java, Go, Python, Rust, JavaScript and Julia" by Jan Newmarch is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License .
Based on a work at https://jan.newmarch.name/NetworkProgramming/ .

If you like this book, please contribute using PayPal