One of the many reasons for Go's popularity, as a system language, is its inherent support for creating networked programs. The standard library exposes APIs ranging from low-level socket primitives to higher-level service abstractions such as HTTP and RPC. This chapter explores fundamental topics about creating connected applications including the following:
The starting point for all networked programs in Go is the net package (https://golang.org/pkg/net). It provides a rich API to handle low-level networking primitives as well as application-level protocols such as HTTP. Each logical component of a network is represented by a Go type including hardware interfaces, networks, packets, addresses, protocols, and connections. Furthermore, each type exposes a multitude of methods giving Go one of the most complete standard libraries for network programming supporting both IPv4 and IPv6.
Whether creating a client or a server program, Go programmers will need, at a minimum, the network primitives covered in the following sections. These primitives are offered as functions and types to facilitate clients connecting to remote services and servers to handle incoming requests.
One of the basic primitives, when doing network programming, is the address. The types and functions of the net
package use a string literal to represent an address such as "127.0.0.1"
. The address can also include a service port separated by a colon such as "74.125.21.113:80"
. Functions and methods in the net
package also support string literal representation for IPv6 addresses such as "::1"
or "[2607:f8b0:4002:c06::65]:80"
for an address with a service port of 80.
The net.Conn
interface represents a generic connection established between two nodes on the network. It implements io.Reader
and io.Writer
interfaces which allow connected nodes to exchange data using streaming IO primitives. The net
package offers network protocol-specific implementations of the net.Conn
interface such as IPConn, UDPConn, and TCPConn. Each implementation exposes additional methods specific to its respective network and protocol. However, as we will see in this chapter, the default method set defined in net.Conn is adequate for most uses.
Client programs use the net.Dial
function, which has the following signature, to connect to a host service over the network:
func Dial(network, address string) (Conn, error)
The function takes two parameters where the first parameter, network, specifies the network protocol for the connection which can be:
tcp
, tcp4
, tcp6
: tcp
defaults to tcp4
udp
, udp4
, udp6
: udp
defaults to udp4
ip
, ip4
, ip6
: ip
defaults to ip4
unix
, unixgram
, unixpacket
: for Unix domain socketsThe latter parameter of the net.Dial
function specifies a string value for the host address to which to connect. The address can be provided as IPv4 or IPv6 addresses as discussed earlier. The net.Dial
function returns an implementation of the net.Conn
interface that matches the specified network parameter.
For instance, the following code snippet dials a "tcp"
network at the host address, www.gutenberg.org:80, which returns a TCP connection of the *net.TCPConn
type. The abbreviated code uses the TCP connection to issue an "HTTP GET"
request to retrieve the full text of the literary classic Beowulf from the Project Gutenberg's website (http://gutenberg.org/). The raw and unparsed HTTP response is subsequently written to a local file, beowulf.txt
:
func main() {
host, port := "www.gutenberg.org", "80"
addr := net.JoinHostPort(host, port)
httpRequest:="GET /cache/epub/16328/pg16328.txt HTTP/1.1
" +
"Host: " + host + "
"
conn, err := net.Dial("tcp", addr)
if err != nil {
fmt.Println(err)
return
}
defer conn.Close()
if _, err = conn.Write([]byte(httpRequest)); err != nil {
fmt.Println(err)
return
}
file, err := os.Create("beowulf.txt")
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
io.Copy(file, conn)
fmt.Println("Text copied to file", file.Name())
}
golang.fyi/ch11/dial0.go
Because the net.Conn
type implements the io.Reader
and io.Writer
, it can be used to both send data and receive data using streaming IO semantics. In the preceding example, conn.Write([]byte(httpRequest))
sends the HTTP request to the server. The response returned by the host is copied from the conn
variable to the file
variable using io.Copy(file, conn)
.
The net
package also makes available network specific dialing functions such as DialUDP
, DiapTCP
, or DialIP
, each returning its respective connection implementation. In most cases, the net.Dial
function and the net.Conn
interface provide adequate capabilities to connect and manage connections to a remote host.
When creating a service program, one the first steps is to announce the port which the service will use to listen for incoming requests from the network. This is done by invoking the net.Listen
function which has the following signature:
func Listen(network, laddr string) (net.Listener, error)
It takes two parameters where the first parameter specifies a protocol with valid values of "tcp"
, "tcp4"
, "tcp6"
, "unix"
, or "unixpacket"
.
The second parameter is the local host address for the service. The local address can be specified without an IP address such as ":4040"
. Omitting the IP address of the host means that the service is bound to all network card interfaces installed on the host. As an alternative, the service can be bound to a specific network hardware interface on the host by specifying its IP address on the network, that is, "10.20.130.240:4040"
.
A successful call to the net.Listen
function returns a value of the net.Listener
type (or a non-nil error if it fails). The net.Listener
interface exposes methods used to manage the life cycle of incoming client connections. Depending on the value of the network
parameter ("tcp"
, "tcp4"
, "tcp6"
, and so on.), net.Listen
will return either a net.TCPListener
or net.UnixListener
, both of which are concrete implementations of the net.Listener
interface.
The net.Listener
interface uses the Accept method to block indefinitely until a new connection arrives from a client. The following abbreviated code snippet shows a simple server that returns the string "Nice to meet you!" to each client connection and then disconnects immediately:
func main() { listener, err := net.Listen("tcp", ":4040") if err != nil { fmt.Println(err) return } defer listener.Close() for { conn, err := listener.Accept() if err != nil { fmt.Println(err) return } conn.Write([]byte("Nice to meet you!")) conn.Close() } }
golang.fyi/ch11/listen0.go
In the code, the listener.Accept
method returns a value of the net.Conn
type to handle data exchange between the server and the client (or it returns a non-nil error
if it fails). The conn.Write([]byte("Nice to meet you!"))
method call is used to write the response to the client. When the server program is running, it can be tested using a telnet client as shown in the following output:
$> go run listen0.go &
[1] 83884
$> telnet 127.0.0.1 4040
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Nice to meet you! Connection closed by foreign host.
To ensure that the server program continues to run and handle subsequent client connections, the call to the Accept
method is wrapped within an infinite for-loop. As soon as a connection is closed, the loop restarts the cycle to wait for the next client connection. Also notice that it is a good practice to close the listener when the server process is shutting down with a call to Listener.Close()
.
3.133.157.142