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&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:80as 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 networktcp(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/ioutilfunctionReadAllwill 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
Acceptmethod in theListenerinterface; it waits for the next call and returns a genericConn. - 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.