A concurrent TCP server

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.66.156