Francisco1844
Francisco1844

Reputation: 1208

Making Golang TCP server concurrent

New to Go and trying to make a TCP server concurrent. I found multiple examples of this, including this one, but what I am trying to figure out is why some changes I made to a non concurrent version are not working.

This is the original sample code I started from

package main
import "bufio"
import "fmt"
import "log"
import "net"
import "strings" // only needed below for sample processing

func main() {
  fmt.Println("Launching server...")
  fmt.Println("Listen on port")
  ln, err := net.Listen("tcp", "127.0.0.1:8081")
  if err != nil {
      log.Fatal(err)
  }
  defer ln.Close()

  fmt.Println("Accept connection on port")
  conn, err := ln.Accept()
  if err != nil {
      log.Fatal(err)
  }

  fmt.Println("Entering loop")
  // run loop forever (or until ctrl-c)
  for {
    // will listen for message to process ending in newline (\n)
    message, _ := bufio.NewReader(conn).ReadString('\n')
    // output message received
    fmt.Print("Message Received:", string(message))
    // sample process for string received
    newmessage := strings.ToUpper(message)
    // send new string back to client
    conn.Write([]byte(newmessage + "\n"))
  }
}

The above works, but it is not concurrent.

This is the code after I modified it

package main
import "bufio"
import "fmt"
import "log"
import "net"
import "strings" // only needed below for sample processing

func handleConnection(conn net.Conn) {
  fmt.Println("Inside function")
  // run loop forever (or until ctrl-c)
  for {
    fmt.Println("Inside loop")
    // will listen for message to process ending in newline (\n)
    message, _ := bufio.NewReader(conn).ReadString('\n')
    // output message received
    fmt.Print("Message Received:", string(message))
    // sample process for string received
    newmessage := strings.ToUpper(message)
    // send new string back to client
    conn.Write([]byte(newmessage + "\n"))
  }

}

func main() {
  fmt.Println("Launching server...")
  fmt.Println("Listen on port")
  ln, err := net.Listen("tcp", "127.0.0.1:8081")
  if err != nil {
      log.Fatal(err)
  }
  //defer ln.Close()

  fmt.Println("Accept connection on port")
  conn, err := ln.Accept()
  if err != nil {
      log.Fatal(err)
  }
  fmt.Println("Calling handleConnection")
  go handleConnection(conn)

}

I based my code on several other examples I found of concurrent servers, but yet when I run the above the server seems to exit instead of running the handleConnection function

Launching server...

Listen on port

Accept connection on port

Calling handleConnection

Would appreciate any feedback as similar code examples I found and tested using the same approach, concurrently calling function to handle connections, worked; so, would like to know what is different with my modified code from the other samples I saw since they seem to be the same to me.

I was not sure if it was the issue, but I tried commenting the defer call to close. That did not help.

Thanks.

Upvotes: 9

Views: 13658

Answers (4)

Toundey
Toundey

Reputation: 11

This is what you want

Server

package main

import (
    "bufio"
)
import "fmt"
import "log"
import "net"
import "strings" // only needed below for sample processing

func handleConnection(conn net.Conn) {
    fmt.Println("Inside function")
    // will listen for message to process ending in newline (\n)
    message, _ := bufio.NewReader(conn).ReadString('\n')
    // output message received
    fmt.Print("Message Received:", string(message))
    // sample process for string received
    newmessage := strings.ToUpper(message)
    // send new string back to client
    conn.Write([]byte(newmessage + "\n"))
    conn.Close()

}

func main() {
    fmt.Println("Launching server...")
    fmt.Println("Listen on port")
    ln, err := net.Listen("tcp", "127.0.0.1:8081")
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("Accept connection on port")
    for {
        conn, err := ln.Accept()
        if err != nil {
            log.Fatal(err)
        }
        fmt.Println("Calling handleConnection")
        go handleConnection(conn)
    }


}

Client

package main

import (
    "bufio"
    "fmt"
    "net"
)

func main() {
    addr, _ := net.ResolveTCPAddr("tcp", ":8081")
    conn, err := net.DialTCP("tcp", nil, addr)
    if err != nil {
        panic(err.Error())
    }
    fmt.Fprintf(conn, "From the client\n")
    message, _ := bufio.NewReader(conn).ReadString('\n')
    fmt.Print(message)
    conn.Close()
}

Upvotes: 1

gipsy
gipsy

Reputation: 3859

The reason you see this behavior is the fact that your main method exits even though your go routine is still running. Make sure to block the main method to achieve what you are trying to achieve.

May be add something like this in the main:

c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
<-c // This will block until you manually exists with CRl-C

Also you can bring back your defer

Upvotes: 4

kars7e
kars7e

Reputation: 786

When you run function using go func() syntax, you are executing a new goroutine without blocking the main one. However, the program will exit when the main goroutine finishes, so in short, you need to block the main goroutine for as long as you want your child goroutines to execute.

I often find myself checking how similar problems are solved in go standard library. For example, Server.Serve() from http package does something similar. Here is the extracted version (shortened, follow the link to see full version):

func (srv *Server) Serve(l net.Listener) error {
   defer l.Close()

   ctx := context.Background() 
   for {
      rw, e := l.Accept()
      if e != nil {
        select {
        case <-srv.getDoneChan():
            return ErrServerClosed
        default:
        }
        if ne, ok := e.(net.Error); ok && ne.Temporary() {
            // handle the error
        }
        return e
      }
      c := srv.newConn(rw)
      c.setState(c.rwc, StateNew) // before Serve can return
      go c.serve(ctx)
   }
}

To stop the above function, we could close the listener (e.g. via interrupt signal), which in turn would generate an error on Accept(). The above implementation checks whether serv.GetDoneChan() channel returns a value as an indicator that the error is expected and the server is closed.

Upvotes: 2

Mr_Pink
Mr_Pink

Reputation: 109337

Your main function is returning immediately after accepting a new connection, so your program exits before the connection can be handled. Since you probably also want to receive more than one single connection (or else there would be no concurrency), you should put this in a for loop.

You are also creating a new buffered reader in each iteration of the for loop, which would discard any buffered data. You need to do that outside the for loop, which I demonstrate here by creating a new bufio.Scanner which is a simpler way to read newline delimited text.

import (
    "bufio"
    "fmt"
    "log"
    "net"
    "strings"
)

func handleConnection(conn net.Conn) {
    defer conn.Close()
    scanner := bufio.NewScanner(conn)
    for scanner.Scan() {
        message := scanner.Text()
        fmt.Println("Message Received:", message)
        newMessage := strings.ToUpper(message)
        conn.Write([]byte(newMessage + "\n"))
    }

    if err := scanner.Err(); err != nil {
        fmt.Println("error:", err)
    }
}

func main() {
    ln, err := net.Listen("tcp", "127.0.0.1:8081")
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("Accept connection on port")

    for {
        conn, err := ln.Accept()
        if err != nil {
            log.Fatal(err)
        }
        fmt.Println("Calling handleConnection")
        go handleConnection(conn)
    }
}

Upvotes: 11

Related Questions