Making Golang TCP server concurrent - go

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.

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)
}
}

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

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.

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()
}

Related

Send request repeatedly to the tcp server at regular interval

This is the code taken from the go book. The client enters the message and the request is sent to the server. How to send the same request repeatedly without entering values every time? Also, the time interval between successive requests should be 3 seconds. Should I use goroutines?
package main
import (
"bufio"
"fmt"
"net"
"os"
)
func main() {
arguments := os.Args
if len(arguments) == 1 {
fmt.Println("Please provide host:port.")
return
}
CONNECT := arguments[1]
c, err := net.Dial("tcp", CONNECT)
if err != nil {
fmt.Println(err)
return
}
for {
reader := bufio.NewReader(os.Stdin)
fmt.Print(">>")
text, _ := reader.ReadString('\n')
fmt.Fprintf(c, text+"\n")
}
}
Use a time.Ticker to execute code at some specified interval:
t := time.NewTicker(3 * time.Second)
defer t.Stop()
for range t.C {
_, err := c.Write([]byte("Hello!\n"))
if err != nil {
log.Fatal(err)
}
}
Run it on the playground.

Program that listens for connections in background and also waits for user input

I want to create a program that waits for user input and at the same time, listens for connections in the background. Neither of my functions depends on the other. I'm new to Go so I'm not sure how I can accomplish this. Can this even be done?
func listen() {
listener, _ := net.Listen("tcp4", ":" + port)
defer listener.Close()
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println(err)
return
}
// Some stuff
}
}
func getNewTransaction() {
for {
reader := bufio.NewReader(os.Stdin)
fmt.Println("Receiver: ")
receiver, _ := reader.ReadString('\n')
var amount float32
fmt.Println("Amount: ")
fmt.Scanf("%f", &amount)
}
}
func main() {
go listen()
go getNewTransaction()
select{}
}
Your original code doesn't show how the port is being available inside the listen function. So I am pretty sure the code is erroring out in that function.
I updated the listen function, and the one that takes user input. Replaces the select{} with wait group.
Here's working code:
package main
import (
"bufio"
"fmt"
"net"
"os"
"sync"
)
func listen(port string) {
listener, _ := net.Listen("tcp4", ":"+port)
defer listener.Close()
fmt.Println("Telnet to: ", port)
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println(err)
} else {
conn.Write([]byte("kthx bye!"))
}
conn.Close()
}
}
func getNewTransaction() {
for {
reader := bufio.NewReader(os.Stdin)
fmt.Println("Type something and press enter: ")
input, _ := reader.ReadString('\n')
fmt.Println(input)
}
}
func main() {
go listen("8080")
go getNewTransaction()
wg := &sync.WaitGroup{}
wg.Add(1)
wg.Wait()
}

Print the console log to the screen of a webpage continuously using Go Routines

I got the go routine below to work but the problem is that it prints to the console instead of to the screen. My idea is to have a running log of what commands or output is happening in a script show on a webpage where it can be watched in real time. Using fmt.Fprint doesn't do the trick. All that happens is that my webpage will never fully load. Help please?
Running external python in Golang, Catching continuous exec.Command Stdout
go code
package main
import (
"log"
"net/http"
"time"
"os/exec"
"io"
"bufio"
"fmt"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
s := r.PathPrefix("/api/").Subrouter()
s.HandleFunc("/export", export).Methods("GET")
http.Handle("/", r)
log.Panic(http.ListenAndServe(":80", nil))
}
func export(w http.ResponseWriter, r *http.Request) {
cmd := exec.Command("python", "game.py")
stdout, err := cmd.StdoutPipe()
if err != nil {
panic(err)
}
stderr, err := cmd.StderrPipe()
if err != nil {
panic(err)
}
err = cmd.Start()
if err != nil {
panic(err)
}
go copyOutput(stdout)
go copyOutput(stderr)
cmd.Wait()
}
func copyOutput(r io.Reader, w http.ResponseWriter) {
scanner := bufio.NewScanner(r)
for scanner.Scan() {
fmt.Fprint(w, scanner.Text()) //line I expect to print to the screen, but doesn't
}
}
python script
import time
import sys
while True:
print "Hello"
sys.stdout.flush()
time.sleep(1)
There's a lot more to the site so I know the route is configured correctly because printing to the screen works when I'm not using the go routine'
UPDATE:
Here is my new update function which prints to the screen, but only after the entire script has ran, not as it goes
func export(w http.ResponseWriter, r *http.Request) {
cmd := exec.Command("python", "game.py")
cmd.Stdout = w
cmd.Start()
cmd.Wait()
}
I believe I may still need a go routine in order to get it to print as I go, but putting cmd.Start and/or cmd.Wait in one doesn't work
UPDATE:
So even with everything given, I have not been able to get having the outputs show on a browser as they are ran working. It simply locks up the browser, even with the headers and flush. I will hopefully have time to give a complete, working answer to this but for now, the code above prints the code to the browser correctly after it has ran. I found a repo that I think may be what I'm looking for and maybe it will help others who come across this question as well.
https://github.com/yudai/gotty
This is a very basic (naive) example but how can give you an idea of how to stream data continuously:
https://play.golang.org/p/vtXPEHSv-Sg
The code for game.py is:
import time
import sys
while True:
print("Hello")
sys.stdout.flush()
time.sleep(1)
The web app code:
package main
import (
"bufio"
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
"github.com/nbari/violetear"
)
func stream(w http.ResponseWriter, r *http.Request) {
cmd := exec.Command("python", "game.py")
rPipe, wPipe, err := os.Pipe()
if err != nil {
log.Fatal(err)
}
cmd.Stdout = wPipe
cmd.Stderr = wPipe
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
go writeOutput(w, rPipe)
cmd.Wait()
wPipe.Close()
}
func writeOutput(w http.ResponseWriter, input io.ReadCloser) {
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming not supported", http.StatusInternalServerError)
return
}
// Important to make it work in browsers
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
in := bufio.NewScanner(input)
for in.Scan() {
fmt.Fprintf(w, "data: %s\n", in.Text())
flusher.Flush()
}
input.Close()
}
func main() {
router := violetear.New()
router.HandleFunc("/", stream, "GET")
log.Fatal(http.ListenAndServe(":8080", router))
}
The key part here is the use of http.Flusher and some headers to make it work within a browser:
w.Header().Set("Content-Type", "text/event-stream")
Note the problem with this code is that once a request arrives it will exec the command that loops forever, so the wPipe.Close() will never be called
cmd.Wait()
wPipe.Close()
To be more verbose you could print the output the terminal beside the browser:
for in.Scan() {
data := in.Text()
log.Printf("data: %s\n", data)
fmt.Fprintf(w, "data: %s\n", data)
flusher.Flush()
}
If you have more than one request you will notice it will write faster in the terminal, not bad but you will also notice that if the client closed the connection/browser you will still see data going out.
A better way could execute the command within a context, from the example: https://golang.org/pkg/os/exec/#CommandContext
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
if err := exec.CommandContext(ctx, "sleep", "5").Run(); err != nil {
// This will fail after 100 milliseconds. The 5 second sleep
// will be interrupted.
}
Also take a look at the context (https://stackoverflow.com/a/44146619/1135424) not replaces http.CloseNotifier so could be usefull for terminate the process once the client close browser, disconetcs.
At the end depends on your needs but hope can give you an idea about how to stream data in an easy way by using the http.Flusher interface.
Just for fun here is an example using the context:
https://play.golang.org/p/V69BuDUceBA
Still very basic, but in this case if client closes the browser the program also terminates, as an exercice could be nice to improve it an share back ;-), notice the use of CommandContext and ctx.Done()
package main
import (
"bufio"
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
"github.com/nbari/violetear"
)
func stream(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ch := make(chan struct{})
cmd := exec.CommandContext(ctx, "python", "game.py")
rPipe, wPipe, err := os.Pipe()
if err != nil {
log.Fatal(err)
}
cmd.Stdout = wPipe
cmd.Stderr = wPipe
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
go writeOutput(w, rPipe)
go func(ch chan struct{}) {
cmd.Wait()
wPipe.Close()
ch <- struct{}{}
}(ch)
select {
case <-ch:
case <-ctx.Done():
err := ctx.Err()
log.Printf("Client disconnected: %s\n", err)
}
}
func writeOutput(w http.ResponseWriter, input io.ReadCloser) {
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming not supported", http.StatusInternalServerError)
return
}
// Important to make it work in browsers
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
in := bufio.NewScanner(input)
for in.Scan() {
data := in.Text()
log.Printf("data: %s\n", data)
fmt.Fprintf(w, "data: %s\n", data)
flusher.Flush()
}
input.Close()
}
func main() {
router := violetear.New()
router.HandleFunc("/", stream, "GET")
log.Fatal(http.ListenAndServe(":8080", router))
}

TCP Server listening in the background without blocking other operations

I'm writing a TCP Server and Client in Go, just as a working example to get familiar with this language. I want to write a server, let's call it MyServer, which does the following - it has a backend TCP Server, which listens for incoming messages, but it also has a Client which allows him to send other messages, independently on the received once. However, I don't know how to tell MyServer to listen "in the background", without blocking the main thread. Here is the code for my TCPServer:
package main
import (
"fmt"
"net"
"os"
)
func main() {
startListener()
doOtherThins()
}
func startListener() {
listener, err := net.Listen("tcp", "localhost:9998")
if err != nil {
fmt.Println("Error listening:", err.Error())
os.Exit(1)
}
defer listener.Close()
fmt.Println("Listening on " + "localhost:9998")
for {
// Listen for an incoming connection.
conn, err := listener.Accept()
if err != nil {
fmt.Println("Error during accepting: ", err.Error())
os.Exit(1)
}
go handleConnection(conn)
}
}
func handleConnection(conn net.Conn) {
buf := make([]byte, 1024)
_, err := conn.Read(buf)
if err != nil {
fmt.Println("Error reading:", err.Error())
}
conn.Write([]byte("Message correctly received."))
conn.Close()
}
Function startListener() blocks the main function, so the function doOtherThins() (which I want to independently send packets to other servers) is never triggered as long as the server is listening. I tried to change the main function and use the goroutine:
func main() {
go startListener()
doOtherThins()
}
But then the server is not listening for the incoming packets (it just triggers doOtherThins() and ends main()).
Is it possible to spin the listener in the background, to allow the main thread do also other operations?
Your last example should do what you want, the issue is that the main thread ends before you can do anything. There's 2 solutions start doOtherThins() on another goroutine and then call startListener() which blocks but the other goroutine is already running:
func main() {
go doOtherThins()
startListener()
}
Or use waitGroups to wait until the code ends before exiting.
Here is a cleaner way of achieving this using channels.
package main
import (
"net/http"
"fmt"
)
func main() {
// Create a channel to synchronize goroutines
finish := make(chan bool)
server8001 := http.NewServeMux()
server8001.HandleFunc("/foo", foo8001)
server8001.HandleFunc("/bar", bar8001)
go func() {
http.ListenAndServe(":8001", server8001)
}()
go func() {
//do other things in a separate routine
fmt.Println("doing some work")
// you can also start a new server on a different port here
}()
// do other things in the main routine
<-finish //wait for all the routines to finish
}
func foo8001(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Listening on 8001: foo "))
}
func bar8001(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Listening on 8001: bar "))
}
adding another variation of Anuruddha answer
package main
import (
"io"
"net/http"
"os"
"time"
)
func main() {
server8001 := http.NewServeMux()
server8001.HandleFunc("/foo", foo8001)
server8001.HandleFunc("/bar", bar8001)
unblock(func() error {
return http.ListenAndServe(":8001", server8001)
})//forgot err check, must be done!
res, err := http.Get("http://0.0.0.0:8001/foo")
if err != nil {
panic(err)
}
defer res.Body.Close()
io.Copy(os.Stdout, res.Body)
os.Exit(0)
}
func unblock(h func() error) error {
w := make(chan error)
go func() {
w <- h()
}()
select {
case err := <-w:
return err
case <-time.After(time.Millisecond * 50):
return nil
}
}
func foo8001(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Listening on 8001: foo "))
}
func bar8001(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Listening on 8001: bar "))
}

How to listen to a client continiously using gob in Golang

In my use case I would like to continuously listen to a TCP connection and receive the value. The expected value is an object. So I am using gob decoder to receive the value from the connection. I would like to continuously listen to the connection and receive the object using go routines. I have the code snippet here[It is part of the application. code snippet does not compile]. It is getting value for the first time but not receiving for the subsequent objects.
func main() {
//...
// SOME CODE
//...
// All hosts who are connected; a map wherein
// the keys are ip addreses and the values are
//net.Conn objects
allClients := make(map[string]net.Conn)
tMaps := make(chan map[string]int64)
for {
select {
// Accept new clients
//
case conn := <-newConnections:
log.Printf("Accepted new client, #%s", hostIp)
// Constantly read incoming messages from this
// client in a goroutine and push those onto
// the tMaps channel for broadcast to others.
//
go func(conn net.Conn) {
dec := gob.NewDecoder(conn)
for {
var tMap map[string]int64
err := dec.Decode(&tMap)
if err != nil {
fmt.Println("Error in decoding ", err)
break
}
log.Printf("Received values: %+v", tMap)
//update throttle map based on the received value
tMaps <- throttleMap
}
}(conn)
}
}
Could anyone help me on this?
Let's look at the basics of a TCP server in Go.
First, there is the "listening" part. We can set that up like this:
package main
import (
"fmt"
"io"
"net"
"time"
)
func main() {
ln, err := net.Listen("tcp", ":9000")
if err != nil {
panic(err)
}
defer ln.Close()
for {
conn, err := ln.Accept()
if err != nil {
panic(err)
}
io.WriteString(conn, fmt.Sprint("Hello World\n", time.Now(), "\n"))
conn.Close()
}
}
Notice the infinite for loop. It is always running and looping over that code. What does the code that is being looped over do? If a connection comes in on the port which is being listened on, then that connection is accepted. We then do something with that connection. In this case, we write back to it with io.WriteString. To this one connection, we are sending a response. We then close the connection. And if there are more connections, we're ready to accept them.
Now let's create a client to connect to the TCP server. This is known as "dialing" in to the TCP server.
To run all of this code on your machine, run the TCP server code above. To run the code, go to your terminal and enter: go run main.go
Now put the code directly below into another file. Launch another tab in your terminal. Run that code also by entering: go run main.go
The code below which "dials" in to your TCP server will connect to the server and the TCP server will respond, then close the connection.
Here is the code for dialing into a TCP server as a client:
package main
import (
"fmt"
"io/ioutil"
"net"
)
func main() {
conn, err := net.Dial("tcp", "localhost:9000")
if err != nil {
panic(err)
}
defer conn.Close()
bs, _ := ioutil.ReadAll(conn)
fmt.Println(string(bs))
}
We can take these basics and start having fun.
Let's create an "echo" server.
This will illustrate accepting many connections.
package main
import (
"io"
"net"
)
func main() {
ln, err := net.Listen("tcp", ":9000")
if err != nil {
panic(err)
}
defer ln.Close()
for {
conn, err := ln.Accept()
if err != nil {
panic(err)
}
// handles unlimited connections
go func() {
io.Copy(conn, conn)
conn.Close()
}()
}
}
Run the file above the same way as before: go run main.go
If you get an error, make sure you have closed the TCP server we were running from the previous example. You close the TCP server with ctrl+c in the terminal.
Now that your new TCP server is running, let's connect to it using Telnet.
On windows you can install telnet; on Mac, it should already be there. Use this command to run telnet and connect to your TCP server: telnet localhost 9000
Now for one more example - an in-memory database like Redis:
package main
import (
"bufio"
"fmt"
"io"
"log"
"net"
"strings"
)
var data = make(map[string]string)
func handle(conn net.Conn) {
defer conn.Close()
scanner := bufio.NewScanner(conn)
for scanner.Scan() {
ln := scanner.Text()
fs := strings.Fields(ln)
if len(fs) < 2 {
io.WriteString(conn, "This is an in-memory database \n" +
"Use SET, GET, DEL like this: \n" +
"SET key value \n" +
"GET key \n" +
"DEL key \n\n" +
"For example - try these commands: \n" +
"SET fav chocolate \n" +
"GET fav \n\n\n")
continue
}
switch fs[0] {
case "GET":
key := fs[1]
value := data[key]
fmt.Fprintf(conn, "%s\n", value)
case "SET":
if len(fs) != 3 {
io.WriteString(conn, "EXPECTED VALUE\n")
continue
}
key := fs[1]
value := fs[2]
data[key] = value
case "DEL":
key := fs[1]
delete(data, key)
default:
io.WriteString(conn, "INVALID COMMAND "+fs[0]+"\n")
}
}
}
func main() {
li, err := net.Listen("tcp", ":9000")
if err != nil {
log.Fatalln(err)
}
defer li.Close()
for {
conn, err := li.Accept()
if err != nil {
log.Fatalln(err)
}
handle(conn)
}
}
And adding in concurrency:
package main
import (
"bufio"
"fmt"
"io"
"log"
"net"
"strings"
)
type Command struct {
Fields []string
Result chan string
}
func redisServer(commands chan Command) {
var data = make(map[string]string)
for cmd := range commands {
if len(cmd.Fields) < 2 {
cmd.Result <- "Expected at least 2 arguments"
continue
}
fmt.Println("GOT COMMAND", cmd)
switch cmd.Fields[0] {
// GET <KEY>
case "GET":
key := cmd.Fields[1]
value := data[key]
cmd.Result <- value
// SET <KEY> <VALUE>
case "SET":
if len(cmd.Fields) != 3 {
cmd.Result <- "EXPECTED VALUE"
continue
}
key := cmd.Fields[1]
value := cmd.Fields[2]
data[key] = value
cmd.Result <- ""
// DEL <KEY>
case "DEL":
key := cmd.Fields[1]
delete(data, key)
cmd.Result <- ""
default:
cmd.Result <- "INVALID COMMAND " + cmd.Fields[0] + "\n"
}
}
}
func handle(commands chan Command, conn net.Conn) {
defer conn.Close()
scanner := bufio.NewScanner(conn)
for scanner.Scan() {
ln := scanner.Text()
fs := strings.Fields(ln)
result := make(chan string)
commands <- Command{
Fields: fs,
Result: result,
}
io.WriteString(conn, <-result+"\n")
}
}
func main() {
li, err := net.Listen("tcp", ":9000")
if err != nil {
log.Fatalln(err)
}
defer li.Close()
commands := make(chan Command)
go redisServer(commands)
for {
conn, err := li.Accept()
if err != nil {
log.Fatalln(err)
}
go handle(commands, conn)
}
}
See my lectures from my CSUF class describing all of this here. And one more great resource.

Resources