TCP
Transmission Control Protocol (TCP) provides built-in handshaking, error detection, and reconnection features.
TCP uses a reliablity method called Go-Back-N to ensure there is no data loss in a connection. If a packet is lost, the sender rolls back and retransmits from the lost packet onward. This means that even received packets are retransmitted.
TCP server
TCP ensures a reliable and ordered delivery of birectional data with messag acknowledgements and sequences of data packets. A simple TCP server has the following parts:
- Listen for incoming connections.
- Accept the connection.
- Read and optionally write data to the connection.
Netcat TCP server
To test TCP programs, you need a TCP server. Netcat is a simple command line utility that accepts simple test messages on the specified port and writes them to the console.
Run this command in a terminal to start a TCP server that listens continuously on port 1902:
-6: Listen to IPv6-l: Listen-k: Keep listening-u: UDP protocol
nc -lk 1902
Simple server
A socket is a combination of the protocol, IP address or hostname, and port number. The following simple TCP server reads from an infinite loop and handles each connection in a separate goroutine.
- Establish the connection:
net.Listenreturns a generic network listener for stream-oriented protocols. It accepts two arguments: the protocol, and an address to listen on inhost:portformat. If there is no hostname or IP address provided, it will listen on any interface for IPv4 or IPv6. If you provide the host, it listens only for IPv4. If you set the port to0, then a random port is chosen and you have to retrieve it withnet.Addr.- Handle any errors.
- Close the listener.
- Listen for connections:
- Listen for connections in an infinite
forloop. Acceptis a method on theListenerinterface. It blocks until a new connection arrives, and then returns aConnstruct that represents the next connection.- Handle any errors for
Accept.
- Listen for connections in an infinite
- Handle the connections. This logic could be extracted into a function and called in a goroutine, such as
handleConnection(c Conn).- Handle each connection in a separate goroutine so the main thread can continue accepting connections. This IIFL accepts a
Connobject namedc. This ensures that each goroutine operates on a different connection variable. Passingconndirectly to the closure would overwrite the connection in each goroutine. - Close the connection when the goroutine exits.
- Create a buffer to store connection data.
Readreads data from the connection intobuf.- Handle errors for
Read. - Log the contents of
bufto the console. It is stored as a slice of bytes, so make sure you caste it as a string. Writewrites a slice of bytes to the connection. This sends a trivial response back to the server. If you wanted to log the contents of the buffer, you would replace this line withWrite(buf[:n]).- Pass to the IIFL the
connvalue returned fromAccept.
- Handle each connection in a separate goroutine so the main thread can continue accepting connections. This IIFL accepts a
func main() {
listener, err := net.Listen("tcp", "localhost:9000") // 1.1
if err != nil { // 1.2
log.Fatal(err)
}
defer listener.Close() // 1.3
for { // 2.1
conn, err := listener.Accept() // 2.2
if err != nil { // 2.3
log.Fatal(err)
}
go func(c net.Conn) { // 3.1
defer c.Close() // 3.2
buf := make([]byte, 1024) // 3.3
_, err := c.Read(buf) // 3.4
if err != nil { // 3.5
log.Fatal(err)
}
log.Print(string(buf)) // 3.6
c.Write([]byte("Hello from TCP server\n")) // 3.7
}(conn) // 3.8
}
}
Production server
Here is a more complicated TCP server that uses channels to handle connections. The code is explained in detail by Matthew Slipper in a blog post. Here are some more examples:
- Linode TCP and UDP Client and Server in Go
- go-tcp-proxy
- Build a Blazing-Fast TCP Server in Go: A Practical Guide
const MaxLineLenBytes = 1024
const ReadWriteTimeout = time.Minute
func main() {
lis, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatalf("failed to start listener: %v", err)
}
for {
conn, err := lis.Accept()
if err != nil {
log.Printf("failed to accept conn: %v", err)
continue
}
go handleConn(conn)
}
}
func handleConn(conn net.Conn) {
log.Printf("accepted connection from %s", conn.RemoteAddr())
defer func() {
_ = conn.Close()
log.Printf("closed connection from %s", conn.RemoteAddr())
}()
done := make(chan struct{})
// time out one minute from now if no
// data is received
_ = conn.SetReadDeadline(time.Now().Add(ReadWriteTimeout))
go func() {
// limit the maximum line length (in bytes)
lim := &io.LimitedReader{
R: conn,
N: MaxLineLenBytes,
}
scan := bufio.NewScanner(lim)
for scan.Scan() {
input := scan.Text()
output := strings.ToUpper(input)
if _, err := conn.Write([]byte(output + "\n")); err != nil {
log.Printf("failed to write output: %v", err)
return
}
log.Printf("wrote response: %s", output)
// reset the number of bytes remaining in the LimitReader
lim.N = MaxLineLenBytes
// reset the read deadline
_ = conn.SetReadDeadline(time.Now().Add(ReadWriteTimeout))
}
done <- struct{}{}
}()
<-done
}
TCP client
A TCP client is much simpler than a TCP server. All you have to do is Dial the server, write to the connection, and close the connection:
- Open the connection to the server with the
Dialfunction.Dialreturns a genericnet.Conninterface for packet-oriented connections. It is a Reader and a Writer. - Handle any errors.
- Close the connection.
- Write to the connection with
Write.
func main() {
conn, err := net.Dial("tcp", ":9000") // 1
if err != nil { // 2
log.Fatal(err)
}
defer conn.Close() // 3
conn.Write([]byte("Hello TCP client")) // 4
}
UDP server
UDP is connectionless—the client sends data packets to the server, and the server sends data packets to the client. There is no acknowledgement that the packets are received on either end. The following example is a simple UDP server:
- Listen for UDP connections on port 9001.
ListenPacketreturns anet.PacketConninterface for packet-oriented connections. This is not a Reader or a Writer. Rather, it hasReadFromandWriteTomethods to read and write to the connection - Handle any errors.
- Close the connection when
mainfinishes. - Create a 1KB buffer for data you read from the connection.
- Create an infinite loop that listens for packets on the connection.
ReadFromblocks until it reads a packet from the connection. It returns the number of bytes read, the return IP address on the packet, and any error.- Handle any errors.
- (Optional) Log a message to the console. Make sure you cast the buffer to a
string. - Write a response on the connection back to the address returned by
ReadFrom.
func main() {
conn, err := net.ListenPacket("udp", ":9001") // 1
if err != nil { // 2
log.Fatal(err)
}
defer conn.Close() // 3
buf := make([]byte, 1024) // 4
for { // 5
_, addr, err := conn.ReadFrom(buf) // 6
if err != nil { // 7
log.Fatal(err)
}
log.Printf("Received %s from %s", string(buf), addr) // 8
conn.WriteTo([]byte("Hello from the UDP server"), addr) // 9
}
}
UDP client
A UDP client is almost identical to the TCP client, but you pass the "udp" protocol to the Dial function. A simple UDP client connects to the server with Dial, writes to the connection, and closes the connection:
- Open the connection to the server with the
Dialfunction.Dialreturns a genericnet.Conninterface for packet-oriented connections. It is a Reader and a Writer. - Handle any errors.
- Close the connection.
- Write to the connection with
Write.
func main() {
conn, err := net.Dial("udp", ":9001") // 1
if err != nil { // 2
log.Fatal(err)
}
defer conn.Close() // 3
conn.Write([]byte("Hello from UDP client")) // 4
}
Network logging
The 12-factor app paradigm explains that you should treat logs as event streams. You can connect to a remote port that is designated for network logging and write logs there.
The following example is simple, but it demonstrates how to connect to a server, flush the network buffer when the connection closes, and create a network logger:
- Establish a TCP connection on the listening port.
- Close the connection. If a panic occurs, the network buffer is flushed to the destination so you don’t lose important messages.
- Define the log options.
- Create the logger. The
Newfunction takes aWriter, prefix, and log flags. - Log a regular message.
- Log a panic. Always use
Paniclnrather thanlog.Fatal, because theFatalfunctions do not call the deferred functions—they immediately return withos.Exit. This means that the network buffer is never properly flushed.
func main() {
conn, err := net.Dial("tcp", "localhost:1902") // 1
if err != nil {
panic(errors.New("Failed to connect to localhost:1902"))
}
defer conn.Close() // 2
flags := log.Ldate | log.Lshortfile // 3
logger := log.New(conn, "[example]", flags) // 4
logger.Println("This is a regular message") // 5
logger.Panicln("This is a panic.") // 6
}