package main import ( "flag" "log" "net" "strconv" "strings" "sync" "time" "github.com/xtaci/gaio" ) func main() { port := flag.Int("port", 3333, "Port to accept connections on.") host := flag.String("host", "127.0.0.1", "Host or IP to bind to") flag.Parse() w, err := gaio.NewWatcher() if err != nil { log.Fatal(err) } defer w.Close() l, err := net.Listen("tcp", *host+":"+strconv.Itoa(*port)) if err != nil { log.Panicln(err) } log.Println("Listening to connections at '"+*host+"' on port", strconv.Itoa(*port)) defer l.Close() for { conn, err := l.Accept() if err != nil { log.Panicln(err) } log.Println("new client: ", conn.RemoteAddr()) // submit the first async write IO request err = w.Write(nil, conn, welcomeHandler()) if err != nil { log.Printf("err sending welcomeHandler: %v\n", err) return } // hand off channel to menuHandler // menuHandler(w, conn) wg := new(sync.WaitGroup) // chan to terminate watcher goroutine when necessary WatcherControl := make(chan string) // watcher.WaitIO goroutine wg.Add(1) go func(WatcherControl *chan string) { log.Println("starting menu watchercontrol") defer wg.Done() ControlLoop: for { log.Println("checking watcher waitio") select { case msg := <-*WatcherControl: if msg == "stop" { log.Println("closing menu watchercontrol") break ControlLoop } default: log.Println("menu watchercontrol waiting for IO") inBuf := make([]byte, 256) err := w.ReadTimeout(nil, conn, inBuf, time.Now().Add(time.Second)) if err != nil { log.Printf("err on watcher readtimeout in menu: %v\n", err) continue } results, err := w.WaitIO() if err != nil { log.Println("watcher wait err: ", err) continue } log.Println("menu received IO!") for _, res := range results { if res.Operation == gaio.OpRead && res.Size > 0 { log.Println("menu receive: ", strings.TrimSpace(string(res.Buffer[:res.Size-2]))) switch string(strings.TrimSpace(string(res.Buffer[:res.Size-2]))) { case "welcome": if err := w.Write(nil, conn, welcomeHandler()); err != nil { log.Printf("error sending welcomeHandler from cmd `welcome`: %v", err) } case "adventure": // start the door and wait log.Printf("starting door handler...") /* if err := doorHandler(&c); err != nil { log.Printf("error from cmd `adventure`: %v", err) } log.Printf("returning from door") n, err := c.Write([]byte("\n\n> ")) if err != nil { log.Printf("error writing to connection: %v", err) } else { log.Printf("wrote %d byte prompt to connection", n) } */ case "exit": /* if err := exitHandler(); err != nil { log.Printf("error sending exitHandler from cmd `exit`: %v", err) } */ default: err := w.Write(nil, conn, []byte("huh?\n\n> ")) if err != nil { log.Printf("error writing to connection: %v", err) } } } else { log.Println("watcher confirming write:", string(res.Buffer)) } } } time.Sleep(time.Second) } log.Println("watcher controlloop closed!") }(&WatcherControl) log.Println("main thread waiting on wg") wg.Wait() log.Printf("Goodbye %s!", conn.RemoteAddr()) } } func menuHandler(w *gaio.Watcher, c net.Conn) { wg := new(sync.WaitGroup) // chan to terminate watcher goroutine when necessary WatcherControl := make(chan string) // watcher.WaitIO goroutine wg.Add(1) go func(WatcherControl *chan string) { log.Println("starting menu watchercontrol") defer wg.Done() ControlLoop: for { log.Println("checking watcher waitio") select { case msg := <-*WatcherControl: if msg == "stop" { log.Println("closing menu watchercontrol") break ControlLoop } default: log.Println("menu watchercontrol waiting for IO") results, err := w.WaitIO() if err != nil { log.Println(err) return } log.Println("menu received IO!") for _, res := range results { if res.Operation == gaio.OpRead { log.Println("menu receive: ", string(res.Buffer)) switch string(res.Buffer) { case "": // noop case "welcome": if err := w.Write(nil, c, welcomeHandler()); err != nil { log.Printf("error sending welcomeHandler from cmd `welcome`: %v", err) } case "adventure": // start the door and wait log.Printf("starting door handler...") /* if err := doorHandler(&c); err != nil { log.Printf("error from cmd `adventure`: %v", err) } log.Printf("returning from door") n, err := c.Write([]byte("\n\n> ")) if err != nil { log.Printf("error writing to connection: %v", err) } else { log.Printf("wrote %d byte prompt to connection", n) } */ case "exit": /* if err := exitHandler(); err != nil { log.Printf("error sending exitHandler from cmd `exit`: %v", err) } */ default: err := w.Write(nil, c, []byte("huh?\n\n> ")) if err != nil { log.Printf("error writing to connection: %v", err) } } } else { log.Println("watcher confirming write:", string(res.Buffer)) } } } time.Sleep(time.Second) } log.Println("watcher controlloop closed!") }(&WatcherControl) log.Println("main thread waiting on wg") wg.Wait() log.Printf("Goodbye %s!", c.RemoteAddr()) } func sendClientText(w *gaio.Watcher, c net.Conn, s string) error { err := w.Write(nil, c, []byte(s)) if err != nil { log.Printf("error writing to connection: %v", err) return err } return nil } func welcomeHandler() []byte { const bannerText = "\x1b[2J\x1b[H\x1b[31m\x1b[J\r\n" + ` _ __ _ ` + "" + ` | | / /__ / /________ ____ ___ ___` + "" + ` | | /| / / _ \/ / ___/ __ \/ __ '__ \/ _ \` + "" + ` | |/ |/ / __/ / /__/ /_/ / / / / / / __/` + "" + ` |__/|__/\___/_/\___/\____/_/ /_/ /_/\___/` + "\r\n\x1b[33m" + ` __` + "" + ` / /_____ ` + "" + ` / __/ __ \` + "" + ` / /_/ /_/ /` + "" + ` \__/\____/` + "\x1b[35m\r\n" + ` _ __` + "" + ` | | / /__ _____________` + "" + ` | | / / _ \/ ___/ ___/ _ \` + "" + ` | |/ / __/ / (__ ) __/` + "" + ` |___/\___/_/ /____/\___/` + "\x1b[37m\r\n" + ` _____ __ ___` + "" + ` / ___// /___ ______/ (_)___ _____` + "" + ` \__ \/ __/ / / / __ / / __ \/ ___/` + "" + ` ___/ / /_/ /_/ / /_/ / / /_/ (__ ) ` + "" + ` /____/\__/\__,_/\__,_/_/\____/____/ ` + "\x1b[32m\r\n\r\n\r\n" + ` Help is available: type help` + "\r\n\r\n> " return []byte(bannerText) } /* func exitHandler(c net.Conn) error { const exitMessage = "\x1b[2J\x1b[H\x1b[31m\x1b[J\r\n" + ` ___| | | \___ \ _ \ _ \ | | _ \ | | __| __ \ _' | __| _ \ __| _ \\ \ \ / __ \ _ \ | | | | __/ __/ | | ( | | | \__ \ | | ( | ( __/ ( ( |\ \ \ / | | ( | | |_| _____/ \___|\___| \__, |\___/ \__,_| ) ____/ .__/ \__,_|\___|\___| \___|\___/ \_/\_/ _.__/ \___/ \__, |_) ____/ / _| ____/ ` err := sendClientText(c, exitMessage) if err != nil { log.Printf("error writing to connection: %v", err) } time.Sleep(500 * time.Millisecond) return c.Close() } func doorHandler(c *net.Conn) error { const bannerText = "\r\nCOLOSSAL CAVE\r\n\r\n" err := sendClientText(*c, bannerText) if err != nil { log.Printf("error writing to connection: %v\n", err) } cmd := exec.Command("/usr/games/adventure") stdout, _ := cmd.StdoutPipe() stdin, _ := cmd.StdinPipe() cmd.Start() Terminator := make(chan bool) wg := new(sync.WaitGroup) wg.Add(1) go func(c net.Conn, Terminator *chan bool, wg *sync.WaitGroup) { log.Println("starting stdin reader goroutine") defer wg.Done() Check: for { select { case <-*Terminator: break Check default: time.Sleep(time.Second) inBuf := make([]byte, 80) var l int if l, err = c.Read(inBuf); err != nil { if err == io.EOF { log.Println("connection closed?") break Check } else { log.Printf("error reading conn: %v\n", err) break Check } } if l > 0 { log.Printf("writing %d bytes to stdin: %v\n", l, string(inBuf[:l])) stdin.Write(inBuf[:l]) } } } log.Println("exiting stdin reader goroutine") }(*c, &Terminator, wg) log.Println("starting stdout reader goroutine") scanner := bufio.NewScanner(stdout) scanner.Split(bufio.ScanLines) for scanner.Scan() { m := scanner.Bytes() (*c).Write(m) (*c).Write([]byte{'\n'}) log.Printf("wrote %d bytes to stdout: %v\n", len(m)+1, string(m)) } log.Println("exiting stdout reader goroutine") Terminator <- true log.Println("waiting for wg") wg.Wait() log.Println("waiting for cmd") exitErr := cmd.Wait() if exitErr != nil { log.Printf("cmd exited with err: %v\n", exitErr) } return nil } */