In this section, you will learn how to develop a concurrent TCP server, using goroutines. For each incoming connection to the TCP server, the TCP server will start a new goroutine to handle that request. This allows it to accept more requests, which means that a concurrent TCP server can serve multiple clients simultaneously.
The job of the TCP concurrent server is to accept a positive integer and return a natural number from the Fibonacci sequence. If there is an error in the input, the return value will be -1. As the calculation of numbers of the Fibonacci sequence can be slow, we will use an algorithm that was first presented in Chapter 11, Code Testing, Optimization and Profiling, and was included in benchmarkMe.go. Additionally, this time the algorithm used will be explained a bit further.
The name of the program is fiboTCP.go, and its code is presented in five parts. As it is considered good practice to be able to define the port number of a web service as a command-line parameter, fiboTCP.go will do exactly that.
The first part of fiboTCP.go contains the following Go code:
package main import ( "bufio" "fmt" "net" "os" "strconv" "strings" "time" )
The second code portion of fiboTCP.go contains the following Go code:
func f(n int) int { fn := make(map[int]int) for i := 0; i <= n; i++ { var f int if i <= 2 { f = 1 } else { f = fn[i-1] + fn[i-2] } fn[i] = f } return fn[n] }
In the preceding code, you can see the implementation of the f() function that generates natural numbers that belong to the Fibonacci sequence. The algorithm used is difficult to understand at first, but it is very efficient and therefore fast. First, the f() function uses a Go map named fn, which is pretty unusual when calculating numbers of the Fibonacci sequence. Second, the f() function uses a for loop, which is also fairly unusual. Finally, the f() function does not use recursion, which is the main reason for the speed of its operation.
The idea behind the algorithm used in f(), which uses a Dynamic Programming technique, is that whenever a Fibonacci number is computed, it is put into the fn map so that it will not be computed again. This simple idea saves a lot of time, especially when large Fibonacci numbers need to be calculated because you do not have to calculate the same Fibonacci number multiple times.
The third code segment of fiboTCP.go is as follows:
func handleConnection(c net.Conn) { for { netData, err := bufio.NewReader(c).ReadString(' ') if err != nil { fmt.Println(err) os.Exit(100) } temp := strings.TrimSpace(string(netData)) if temp == "STOP" { break } fibo := "-1 " n, err := strconv.Atoi(temp) if err == nil { fibo = strconv.Itoa(f(n)) + " " } c.Write([]byte(string(fibo))) } time.Sleep(5 * time.Second) c.Close() }
The handleConnection() function deals with each client of the concurrent TCP server.
The fourth part of fiboTCP.go follows next:
func main() { arguments := os.Args if len(arguments) == 1 { fmt.Println("Please provide a port number!") return } PORT := ":" + arguments[1] l, err := net.Listen("tcp4", PORT) if err != nil { fmt.Println(err) return } defer l.Close()
The remaining Go code of fiboTCP.go is as follows:
for { c, err := l.Accept() if err != nil { fmt.Println(err) return } go handleConnection(c) } }
The concurrency of the program is implemented by the go handleConnection(c) statement, which begins a new goroutine each time a new TCP client comes. The goroutine is executed concurrently, which gives the server the opportunity to serve even more clients!
Executing fiboTCP.go and interacting with it using both netcat(1) and TCPclient.go on two different Terminal windows will generate the following output:
$ go run fiboTCP.go 9000 n: 10 fibo: 55 n: 0 fibo: 1 n: -1 fibo: 0 n: 100 fibo: 3736710778780434371 n: 12 fibo: 144 n: 12 fibo: 144
The output will be as follows on the TCPclient.go side:
$ go run TCPclient.go localhost:9000 >> 12 ->: 144 >> a ->: -1 >> STOP ->: TCP client exiting...
On the netcat(1) side, the output will be as follows:
$ nc localhost 9000 10 55 0 1 -1 0 100 3736710778780434371 ads -1 STOP
When you send the STOP string to the server process, the goroutine that serves that particular TCP client will terminate, which will cause the connection to end.
Finally, the impressive thing here is that both clients are served at the same time, which can be verified by the output of the following command:
$ netstat -anp TCP | grep 9000 tcp4 0 0 127.0.0.1.9000 127.0.0.1.57309 ESTABLISHED tcp4 0 0 127.0.0.1.57309 127.0.0.1.9000 ESTABLISHED tcp4 0 0 127.0.0.1.9000 127.0.0.1.57305 ESTABLISHED tcp4 0 0 127.0.0.1.57305 127.0.0.1.9000 ESTABLISHED tcp4 0 0 *.9000 *.* LISTEN
The last line of the output of the preceding command tells us that there is a process that listens to port 9000, which means that you can still connect to port 9000. The first two lines of the output say that there is a client that uses port 57309 to talk to the server process. The third and fourth lines of the preceding output verify that there is another client that communicates with the server that listens to port 9000. This client uses the TCP port 57305.