DEV Community

birowo
birowo

Posted on

CONCURRENT TCP SERVER

simple concurrent tcp server in go language

package main

import (
    "net"
    "net/http"
    "runtime"
    "strconv"
    "time"
)

type Handler func(net.Conn)

func Server(routineNum int, addr string, hndlr Handler) {
    lstnr, err := net.Listen("tcp", addr) //create TCP listener
    if err != nil {
        println("a", err.Error())
        return
    }
    defer lstnr.Close()
    println("TCP server start listen on address:", addr, "\n")
    for i := 0; i < routineNum; i++ {
        // prepare several routine to handle accepted new connection
        go accept(lstnr, hndlr)
    }
    select {} // <-make(chan struct{}) //prevent Server func. to return
}
func accept(lstnr net.Listener, hndlr Handler) {
    conn, err := lstnr.Accept() // create new connection
    if err != nil {
        println("b", err.Error())
        return
    }
    // this routine will be exited if connection closed, so we need to
    go accept(lstnr, hndlr) // prepare new routine to replace this exited one
    // without replacement routine if connection closed,
    // next incoming connection could not be accepted
    defer func() {
        conn.Close()
        //recover will prevent terminating this server if uncaught error happened
        if rcvr := recover(); rcvr != nil {
            switch err := rcvr.(type) {
            case string:
                println("1 recover", err)
            case runtime.Error:
                println("2 recover", err.Error())
            case error:
                println("3 recover", err.Error())
            default:
                println("4 recover")
            }
        }
    }()
    hndlr(conn)
}

func main() {
    statusLine := "HTTP/1.1 200 OK\r\n" +
        "Content-Type: text/plain; charset=UTF-8\r\n" +
        "Date: "
    httpResHdrs := statusLine +
        http.TimeFormat + "\r\n" +
        "Content-Length: "

    hhmmssTmFmt := "03:04:05"
    routineNum := runtime.NumCPU()
    address := ":8080"
    handler := func(conn net.Conn) {
        timeReq := "time request: " + time.Now().Format(hhmmssTmFmt) + "\r\n"
        var buf [1024]byte          // create buffer for holding req/res data
        n, err := conn.Read(buf[:]) // read request data from client to buf
        if err != nil {
            println("main:", err.Error())
            return
        }
        reqData := string(buf[:n])
        //giving 10 sec. enough time to make several concurrent request from client
        time.Sleep(10 * time.Second)

        timeRes := "time response: " + time.Now().Format(hhmmssTmFmt)
        contentLen := strconv.Itoa(len(timeReq) + n + len(timeRes))
        //build response data in buffer
        n = copy(buf[:], httpResHdrs)
        copy(buf[len(statusLine):], time.Now().Format(http.TimeFormat))
        n += copy(buf[n:], contentLen)
        n += copy(buf[n:], "\r\n\r\n")
        n += copy(buf[n:], timeReq)
        n += copy(buf[n:], reqData)
        n += copy(buf[n:], timeRes)
        resData := buf[:n]
        //write response data to client (in this case, contain http request)
        conn.Write(resData)
    }

    Server(routineNum, address, handler)
}
Enter fullscreen mode Exit fullscreen mode

on the client/browser make several requests in 10 sec., so that the concurrency effect can be seen

simple_tcp_server_golang

the response will be displayed after 10 sec.

https://gitlab.com/birowo/tcpsrvr

Discussion (0)