Golang: Echo Protocol (RFC862)
The Echo Protocol is one of the simplest
possible internet protocols. The server listens on port 7
and any data that is read is sent back to the client. It can run either
over TCP or UDP. In this article we’ll see how to implement a server and
a client for the echo protocol in golang.
The Server (TCP)
First let’s see the server over TCP. Create a file named
echoservertcp.go
, open it in your favorite editor, and add
the following lines:
package main
import (
"log"
)
func main() {
.SetFlags(log.Lshortfile)
log}
We import the needed packages and then set the log flags to Lshortfile
to better spot errors when they happen. We then need to establish a TCP
connection on localhost port
7
. Add net
to the import list, and then add
these lines to main
:
, err := net.Listen("tcp", ":7")
lnif err != nil {
.Fatal(err)
log}
Now we need to listen for all the incoming connections and reply to each one. We’ll use a goroutine to process each connection so as to make our server concurrent:
for {
, err := ln.Accept()
conif err != nil {
.Fatal(err)
log}
go echo(con)
}
echo
simply reads all the data from the client and sends
it back to the client, we do this with the handy io.Copy
function
(don’t forget to import io
):
func echo(con net.Conn) {
, err := io.Copy(con, con)
_if err != nil {
.Print(err)
log}
= con.Close()
err if err != nil {
.Print(err)
log}
}
We’re done. Here is the final result (echoservertcp.go):
package main
import (
"io"
"log"
"net"
)
func echo(con net.Conn) {
, err := io.Copy(con, con)
_if err != nil {
.Print(err)
log}
= con.Close()
err if err != nil {
.Print(err)
log}
}
func main() {
.SetFlags(log.Lshortfile)
log, err := net.Listen("tcp", ":7")
lnif err != nil {
.Fatal(err)
log}
for {
, err := ln.Accept()
conif err != nil {
.Fatal(err)
log}
go echo(con)
}
= ln.Close()
err if err != nil {
.Fatal(err)
log}
}
The Client (TCP)
The client is simple as well. We:
- open a connection to
localhost
port7
- read from
stdin
untilEOF
and send everything over the connection - read the response and write it to
stdout
.
As simple as this is, there’s one little gotcha. After writing the data, we need to tell the server that we have nothing more to write, otherwise it will block. So we check (trought type assertion) that our connection is effectivly a TCP connection and if it is so we close the writing end. Here’s the code (echoclienttcp.go):
package main
import (
"io"
"log"
"net"
"os"
)
func main() {
.SetFlags(log.Lshortfile)
log, err := net.Dial("tcp", ":7")
conif err != nil {
.Fatal(err)
log}
, err = io.Copy(con, os.Stdin)
_if err != nil {
.Fatal(err)
log}
if tcpcon, ok := con.(*net.TCPConn); ok {
.CloseWrite()
tcpcon}
, err = io.Copy(os.Stdout, con)
_if err != nil {
.Fatal(err)
log}
= con.Close()
err if err != nil {
.Fatal(err)
log}
}
Let’s try it!
Build the server and run it:
$ go build echoservertcp.go
$ ./echoservertcp
echoservertcp.go:21: listen tcp :7: bind: permission denied
We get an error because all ports below 1023
are
reserved and can only be accessed by root (see list
of ports). So you’ll need to login as root
trought
su
or sudo
and rerun the command:
# ./echoservertcp
In another terminal build and run the client:
$ go build echoclienttcp.go
$ echo 'Hey, GNU rocks!' | ./echoclienttcp
Hey, GNU rocks!
Hey, it works!
The Server (UDP)
In UDP we work with datagrams. We don’t need to listen or accept any
incoming connection. We read a datagram and send back the response. In
the TCP code we block with net.Accept
but here we’ll use a semaphore to limit the number of goroutines
running. Here’s the code (echoserverudp.go):
package main
import (
"log"
"net"
)
var sem = make(chan int, 100)
func echo(con net.PacketConn) {
defer func() { <-sem }()
:= make([]byte, 4096)
buf , addr, err := con.ReadFrom(buf)
nrif err != nil {
.Print(err)
logreturn
}
, err := con.WriteTo(buf[:nr], addr)
nwif err != nil {
.Print(err)
logreturn
}
if nw != nr {
.Printf("received %d bytes but sent %d\n", nr, nw)
log}
}
func main() {
.SetFlags(log.Lshortfile)
log, err := net.ListenPacket("udp", ":7")
conif err != nil {
.Fatal(err)
log}
for {
<- 1
sem go echo(con)
}
= con.Close()
err if err != nil {
.Fatal(err)
log}
}
The Client (UDP)
In the client we:
- read data from stdin
- send it with a datagram
- read the response
- and write it to stdout
Code (echoclientudp.go):
package main
import (
"io/ioutil"
"log"
"net"
"os"
)
func main() {
.SetFlags(log.Lshortfile)
log, err := net.Dial("udp", ":7")
conif err != nil {
.Fatal(err)
log}
, err := ioutil.ReadAll(os.Stdin)
buf
, err := con.Write(buf)
nwif err != nil {
.Fatal(err)
log}
, err := con.Read(buf)
nrif err != nil {
.Fatal(err)
log}
if nr != nw {
.Fatalf("sent %d bytes but received %d\n", nw, nr)
log}
, err = os.Stdout.Write(buf[:nr])
_if err != nil {
.Fatal(err)
log}
= con.Close()
err if err != nil {
.Fatal(err)
log}
}
Get the source code
Security
Please don’t use the echo protocol. It’s useless and what’s more it’s dangerous: see CA-1996-01. It’s appropriate only for learning purposes.
Golang Weekly
Checkout Go Weekly for the latest articles, tutorials and projects about Go.