19. TCP programming using Go

19.1 TCP Sockets

If you are a client you need an API that will allow you to connect to a service and then to send messages to that service and read replies back from the service.

If you are a server, you need to be able to bind to a port and listen at it. When a message comes in you need to be able to read it and write back to the client.

19.2 Package net

Usage: import "net"

Package net provides a portable interface for network I/O, including TCP/IP and UDP.

Type net.Conn1 interface is a generic stream-oriented network connection. Multiple goroutines may invoke methods on a net.Conn simultaneously.

Type net.TCPConn2 is an implementation of the net.Conn interface for TCP network connections. net.TCPConn has two major methods of interest:

  • func (c *TCPConn) Write(b []byte) (n int, err error)
  • func (c *TCPConn) Read(b []byte) (n int, err error)

A net.TCPConn is used by both a client and a server to read and write messages.

19.2.1 TCP Client

Once a client has established a TCP address for a service, it “dials” the service. If succesful, the dial returns a net.TCPConn for communication. The client and the server exchange messages on this. Typically a client writes a request to the server using the net.TCPConn, and reads a response from the net.TCPConn. This continues until either (or both) sides close the connection. A TCP connection is established by the client using the function:

func DialTCP(net string, laddr, raddr *TCPAddr) (*TCPConn, error)

net.DialTCP connects to the remote address raddr on the network net, which must be “tcp”, “tcp4” (IPv4-only i.e. Internet Protocol version 4), or “tcp6” (IPv6-only i.e. Internet Protocol version 6). If laddr is not nil, it is used as the local address for the connection.

Note: Use “tcp” or “tcp4” or “tcp6” depending upon your computer.

A simple example can be provided by a client to a web (HTTP) server.

One of the possible messages that a client can send is the “GET” message. This queries a server for information about the server and a document on that server. The server’s response contains header as well as body, and the connection closes after the response. The request sent to query an HTTP server could be:

GET / HTTP/1.0\r\n\r\n

Program: get_info.go


 1 package main
 2 
 3 import (
 4 	"fmt"
 5 	"io/ioutil"
 6 	"log"
 7 	"net"
 8 	"os"
 9 )
10 
11 func check(e error, str string) {
12 	if e != nil {
13 		log.Fatal(str, " ", e)
14 		return
15 	}
16 }
17 
18 func main() {
19 	if len(os.Args) != 2 {
20 		fmt.Fprintf(os.Stderr, "Usage: %s host:port ", os.Args[0])
21 		os.Exit(1)
22 	}
23 	service := os.Args[1]
24 	tcpAddr, err := net.ResolveTCPAddr("tcp", service)
25 	check(err, "ResolveTCPAddr:")
26 
27 	conn, err := net.DialTCP("tcp", nil, tcpAddr)
28 	check(err, "DialTCP:")
29 
30 	// response contains header as well as body, and the connection closes after the response.
31 	_, err = conn.Write([]byte("GET / HTTP/1.0\r\n\r\n"))
32 	check(err, "Write:")
33 
34 	result, err := ioutil.ReadAll(conn)
35 	check(err, "ReadAll:")
36 
37 	fmt.Println(string(result))
38 }

To run this program, type:

go run get_info.go www.golang.com:80

The output is:

HTTP/1.0 302 Found
Cache-Control: no-store, no-cache, must-revalidate, max-age=0
Content-Type: text/html; charset=UTF-8
Location: http://www.google.co.in/?gfe_rd=cr&ei=CkNdVf-iMYT5vQS_w4H4CQ
Content-Length: 261
Date: Thu, 21 May 2015 02:29:30 GMT
Server: GFE/2.0
Alternate-Protocol: 80:quic,p=0
Pragma: no-cache
Expires: Tue, 01 Jan 1971 02:00:00 GMT

<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>302 Moved</TITLE></HEAD><BODY>
<H1>302 Moved</H1>
The document has moved
<A HREF="http://www.google.co.in/?gfe_rd=cr&amp;ei=CkNdVf-iMYT5vQS_w4H4CQ">here</A>.
</BODY></HTML>

In this program:

  • We use the host:port as www.golang.com:80
  • net.ResolveTCPAddr3 parses www.golang.com:80 as a TCP address of the form “host:port” and resolves a pair of domain name and port name on the network net, which must be “tcp”, “tcp4” or “tcp6”.
  • net.DialTCP4 connects to the remote address www.golang.com:80 (as a TCP address) on the network tcp (which we have used), which must be “tcp”, “tcp4”, or “tcp6”. This way we have now established a connection with the remote host.
  • conn.Write5 sends the request string.
  • Next we read and print the response. We read essentially a single response from the server. This will be terminated by end-of-file on the connection. However, it may consist of several TCP packets, so we need to keep reading till the end of file. The io/ioutil function ReadAll will look after these issues and return the complete response.

19.2.2 A Daytime server

A server registers itself on a port, and listens on that port. Then it blocks on an “accept” operation, waiting for clients to connect. When a client connects, the accept call returns, with a connection object. The daytime service is very simple and just writes the current time to the client, closes the connection, and resumes waiting for the next client.

Program: echo.go


 1 package main
 2 
 3 import (
 4         "fmt"
 5         "log"
 6 	"net"
 7 	"time"
 8 )
 9 
10 func check(e error, str string) {
11 	if e != nil {
12 		log.Fatal(str, " ", e)
13 		return
14 	}
15 }
16 
17 func main() {
18 	listener, err := net.Listen("tcp", ":6000")
19 	check(err, "Listen:")
20 
21 	for {
22 		conn, err := listener.Accept()
23 		if err != nil {
24 			log.Println(err)
25 			conn.Close()
26 			break
27 		}
28                 fmt.Println("Connected to client...")
29                 
30                 daytime := fmt.Sprintf("The Date Time is: %s", time.Now())
31 
32 		conn.Write([]byte(daytime)) // don't care about return value
33 		conn.Close()                // we're finished with this client
34 		fmt.Println("Client connection closed.")
35 	}
36 }

  • net.Listen6 listens on port 6000.
  • Accept7 implements the Accept method in the Listener interface; it waits for the next call and returns a generic Conn.
  • When a client connects to it, it will respond by sending the daytime string to it and then return to waiting for the next client.
  • The server should run forever, so that if any error occurs with a client, the server just ignores that client and carries on. A client could otherwise try to mess up the connection with the server, and bring it down.

Run the telnet client as:

telnet localhost 6000

you should see the date-time at the server location.

  1. http://golang.org/pkg/net/#Conn
  2. http://golang.org/pkg/net/#TCPConn
  3. http://golang.org/pkg/net/#ResolveTCPAddr
  4. http://golang.org/pkg/net/#DialTCP
  5. http://golang.org/pkg/net/#TCPConn.Write
  6. http://golang.org/pkg/net/#Listen
  7. http://golang.org/pkg/net/#TCPListener.Accept