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.
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.
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
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.
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 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.
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
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
}
}
}
}
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
}
}
}
}
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"
.
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
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)
}
}
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.
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.
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).
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)
For the multi-threaded server, there are three choices:
select
system call
(see
Socket Programming HOWTO)
asyncio
module
(see
Streams (coroutine based API)
or
Python simple socket client/server using asyncio)
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()
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);
}
});
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');
});
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
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");
}
}
}
}
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.
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
Copyright © Jan Newmarch, jan@newmarch.name
" 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/
.