TCP

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


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