The building blocks for the basic TCP client is explained in the net package overview.
net.Dial is the general-purpose connect command.
- First parameter is a string specifying the network. In this case we are using
tcp
. - Second parameter is a string with the address of the endpoint in format of
host:port
.
// 04.1-01-basic-tcp1.go
package main
import (
"bufio"
"flag"
"fmt"
"net"
)
var (
host, port string
)
func init() {
flag.StringVar(&port, "port", "80", "target port")
flag.StringVar(&host, "host", "example.com", "target host")
}
func main() {
flag.Parse()
// Converting host and port to host:port
t := net.JoinHostPort(host, port)
// Create a connection to server
conn, err := net.Dial("tcp", t)
if err != nil {
panic(err)
}
// Write the GET request to connection
// Note we are closing the HTTP connection with the Connection: close header
// Fprintf writes to an io.writer
req := "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"
fmt.Fprintf(conn, req)
// Another way to do it to directly write bytes to conn with conn.Write
// However we must first convert the string to bytes with []byte("string")
// reqBytes := []byte(req)
// conn.Write(reqBytes)
// Reading the response
// Create a new reader from connection
connReader := bufio.NewReader(conn)
// Create a scanner
scanner := bufio.NewScanner(connReader)
// Combined into one line
// scanner := bufio.NewScanner(bufio.NewReader(conn))
// Read from the scanner and print
// Scanner reads until an I/O error
for scanner.Scan() {
fmt.Printf("%s\n", scanner.Text())
}
// Check if scanner has quit with an error
if err := scanner.Err(); err != nil {
fmt.Println("Scanner error", err)
}
}
The only drawback with scanner
is having to close the HTTP connection with the Connection: close
header. Otherwise we have to manually kill the application.
$ go run 04.1-01-basic-tcp1.go -host example.com -port 80
HTTP/1.1 200 OK
Cache-Control: max-age=604800
Content-Type: text/html
Date: Sat, 16 Dec 2017 05:21:33 GMT
Etag: "359670651+gzip+ident"
Expires: Sat, 23 Dec 2017 05:21:33 GMT
Last-Modified: Fri, 09 Aug 2013 23:54:35 GMT
Server: ECS (dca/53DB)
Vary: Accept-Encoding
X-Cache: HIT
Content-Length: 1270
Connection: close
<!doctype html>
<html>
<head>
<title>Example Domain</title>
<meta charset="utf-8" />
...
Instead of using a scanner
we can use ReadString(0x00)
and stop when we reach an error (in this case EOF
):
// 04.1-02-basic-tcp2.go
...
// Read until a null byte (not safe in general)
// Response will not be completely read if it has a null byte
if status, err := connReader.ReadString(byte(0x00)); err != nil {
fmt.Println(err)
fmt.Println(status)
}
...
Using 0x00
as delimiter is not ideal. If the response payload contains NULL bytes, we are not reading everything. But it works in this case.
net.DialTCP is the TCP specific version of Dial
. It's a bit more complicated to call:
func DialTCP(network string, laddr, raddr *TCPAddr) (*TCPConn, error)
network
is the same asnet.Dial
but can only betcp
,tcp4
andtcp6
.laddr
is local address and can be chosen. Ifnil
, a local address is automatically chosen.raddr
is remote address and is the endpoint.
The type for both local and remote address is *TCPAddr
:
type TCPAddr struct {
IP IP
Port int
Zone string // IPv6 scoped addressing zone
}
We can pass the network
(e.g. "tcp") along with host:port
or ip:port
string to net.ResolveTCPAddr to get a *TCPAddr
.
DialTCP
returns a *TCPConn. It's a normal connection but with extra methods like SetLinger
, SetKeepAlive
or SetKeepAlivePeriod
.
Let's re-write the TCP client with TCP-specific methods:
// 04.1-03-basic-tcp-dialtcp.go
// Basic TCP client using TCPDial and TCP specific methods
package main
import (
"bufio"
"flag"
"fmt"
"net"
)
var (
host, port string
)
func init() {
flag.StringVar(&port, "port", "80", "target port")
flag.StringVar(&host, "host", "example.com", "target host")
}
// CreateTCPAddr converts host and port to *TCPAddr
func CreateTCPAddr(target, port string) (*net.TCPAddr, error) {
return net.ResolveTCPAddr("tcp", net.JoinHostPort(host, port))
}
func main() {
// Converting host and port
a, err := CreateTCPAddr(host, port)
if err != nil {
panic(err)
}
// Passing nil for local address
tcpConn, err := net.DialTCP("tcp", nil, a)
if err != nil {
panic(err)
}
// Write the GET request to connection
// Note we are closing the HTTP connection with the Connection: close header
// Fprintf writes to an io.writer
req := "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"
fmt.Fprintf(tcpConn, req)
// Reading the response
// Create a scanner
scanner := bufio.NewScanner(bufio.NewReader(tcpConn))
// Read from the scanner and print
// Scanner reads until an I/O error
for scanner.Scan() {
fmt.Printf("%s\n", scanner.Text())
}
// Check if scanner has quit with an error
if err := scanner.Err(); err != nil {
fmt.Println("Scanner error", err)
}
}
This is a bit better.
Similar to TCP, we can make a UDP client with both net.Dial
and net.DialUDP
.
Creating a UDP client is very similar. We will just call net.Dial("udp", t)
. Being UDP, we will use net.DialTimeout
to pass a timeout value.
// 04.1-04-basic-udp.go
// Create a connection to server with 5 second timeout
conn, err := net.DialTimeout("udp", t, 5*time.Second)
if err != nil {
panic(err)
}
Each second is one time.Second
(remember to import the time
package).
net.DialUDP is similar to the TCP equivalent:
func DialUDP(network string, laddr, raddr *UDPAddr) (*UDPConn, error)
*UDPAddr
is acquired through net.ResolveUDPAddr.network
should beudp
.
// 04.1-05-udp-dialudp.go
package main
import (
"bufio"
"flag"
"fmt"
"net"
)
var (
host, port string
)
func init() {
flag.StringVar(&port, "port", "80", "target port")
flag.StringVar(&host, "host", "example.com", "target host")
}
// CreateUDPAddr converts host and port to *UDPAddr
func CreateUDPAddr(target, port string) (*net.UDPAddr, error) {
return net.ResolveUDPAddr("udp", net.JoinHostPort(host, port))
}
func main() {
// Converting host and port to host:port
a, err := CreateUDPAddr(host, port)
if err != nil {
panic(err)
}
// Create a connection with DialUDP
connUDP, err := net.DialUDP("udp", nil, a)
if err != nil {
panic(err)
}
// Write the GET request to connection
// Note we are closing the HTTP connection with the Connection: close header
// Fprintf writes to an io.writer
req := "UDP PAYLOAD"
fmt.Fprintf(connUDP, req)
// Reading the response
// Create a scanner
scanner := bufio.NewScanner(bufio.NewReader(connUDP))
// Read from the scanner and print
// Scanner reads until an I/O error
for scanner.Scan() {
fmt.Printf("%s\n", scanner.Text())
}
// Check if scanner has quit with an error
if err := scanner.Err(); err != nil {
fmt.Println("Scanner error", err)
}
}
- Convert
int
tostring
using strconv.Itoa. strconv.Atoi does the opposite (noteAtoi
also returns anerr
so check for errors after using it. String(int)
converts the integer to corresponding Unicode character.- Create TCP connections with net.Dial.
- We can read/write bytes directly to connections returned by
net.Dial
or create aScanner
. - Convert a string to bytes with
[]byte("12345")
. - Get seconds of type
Duration
withtime.Second
. net
package has TCP and UDP specific methods.