From e6da93f48f05935bc48dba95e1ab76bcb775518b Mon Sep 17 00:00:00 2001 From: Miroslav Vasilev Date: Thu, 19 Jun 2025 16:22:55 +0300 Subject: [PATCH] TCP server works --- .vscode/launch.json | 2 +- cmd/lastmudserver/main.go | 37 ++++++++ go.mod | 5 ++ go.sum | 2 + .../command}/command.go | 2 +- {src/CommandLib => internal/command}/error.go | 2 +- .../command}/lastmud.w3c-ebnf | 0 .../command}/parameter.go | 2 +- .../command}/registry.go | 2 +- .../command}/tokenizer.go | 2 +- internal/server/connection.go | 75 ++++++++++++++++ internal/server/error.go | 15 ++++ internal/server/server.go | 86 +++++++++++++++++++ src/CommandLib/go.mod | 3 - src/CoreLib/go.mod | 6 -- src/Server/go.mod | 13 --- src/Server/main.go | 80 ----------------- 17 files changed, 226 insertions(+), 108 deletions(-) create mode 100644 cmd/lastmudserver/main.go create mode 100644 go.mod create mode 100644 go.sum rename {src/CommandLib => internal/command}/command.go (98%) rename {src/CommandLib => internal/command}/error.go (95%) rename {src/CommandLib => internal/command}/lastmud.w3c-ebnf (100%) rename {src/CommandLib => internal/command}/parameter.go (95%) rename {src/CommandLib => internal/command}/registry.go (98%) rename {src/CommandLib => internal/command}/tokenizer.go (99%) create mode 100644 internal/server/connection.go create mode 100644 internal/server/error.go create mode 100644 internal/server/server.go delete mode 100644 src/CommandLib/go.mod delete mode 100644 src/CoreLib/go.mod delete mode 100644 src/Server/go.mod delete mode 100644 src/Server/main.go diff --git a/.vscode/launch.json b/.vscode/launch.json index a7c43c0..fa86425 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,7 @@ "type": "go", "request": "launch", "mode": "debug", - "program": "${workspaceFolder}/src/Server/main.go" + "program": "${workspaceFolder}/cmd/lastmudserver/main.go" }, ] } \ No newline at end of file diff --git a/cmd/lastmudserver/main.go b/cmd/lastmudserver/main.go new file mode 100644 index 0000000..edd31f5 --- /dev/null +++ b/cmd/lastmudserver/main.go @@ -0,0 +1,37 @@ +package main + +import ( + "bufio" + "fmt" + "log" + "os" + "strings" + + "code.haedhutner.dev/mvv/LastMUD/internal/server" +) + +func main() { + fmt.Println(`\\\\---------------------////`) + fmt.Println(`|||| LastMUD Server ||||`) + fmt.Println(`////---------------------\\\\`) + + lastMudServer, err := server.CreateServer(":8000") + + if err != nil { + log.Fatal(err) + } + + go lastMudServer.Listen() + + reader := bufio.NewReader(os.Stdin) + + for { + text, _ := reader.ReadString('\n') + text = strings.ReplaceAll(text, "\n", "") + + if strings.Compare("exit", text) == 0 { + lastMudServer.Stop() + return + } + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4890150 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module code.haedhutner.dev/mvv/LastMUD + +go 1.24.4 + +require github.com/google/uuid v1.6.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7790d7c --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= diff --git a/src/CommandLib/command.go b/internal/command/command.go similarity index 98% rename from src/CommandLib/command.go rename to internal/command/command.go index b9e96fa..0e82e54 100644 --- a/src/CommandLib/command.go +++ b/internal/command/command.go @@ -1,4 +1,4 @@ -package commandlib +package command type Command struct { commandDefinition CommandDefinition diff --git a/src/CommandLib/error.go b/internal/command/error.go similarity index 95% rename from src/CommandLib/error.go rename to internal/command/error.go index b52aa94..3e7ffea 100644 --- a/src/CommandLib/error.go +++ b/internal/command/error.go @@ -1,4 +1,4 @@ -package commandlib +package command import "fmt" diff --git a/src/CommandLib/lastmud.w3c-ebnf b/internal/command/lastmud.w3c-ebnf similarity index 100% rename from src/CommandLib/lastmud.w3c-ebnf rename to internal/command/lastmud.w3c-ebnf diff --git a/src/CommandLib/parameter.go b/internal/command/parameter.go similarity index 95% rename from src/CommandLib/parameter.go rename to internal/command/parameter.go index e3fa447..ae88608 100644 --- a/src/CommandLib/parameter.go +++ b/internal/command/parameter.go @@ -1,4 +1,4 @@ -package commandlib +package command import "strconv" diff --git a/src/CommandLib/registry.go b/internal/command/registry.go similarity index 98% rename from src/CommandLib/registry.go rename to internal/command/registry.go index 8894fd8..7ae8711 100644 --- a/src/CommandLib/registry.go +++ b/internal/command/registry.go @@ -1,4 +1,4 @@ -package commandlib +package command import "log" diff --git a/src/CommandLib/tokenizer.go b/internal/command/tokenizer.go similarity index 99% rename from src/CommandLib/tokenizer.go rename to internal/command/tokenizer.go index ff0c7bd..1549440 100644 --- a/src/CommandLib/tokenizer.go +++ b/internal/command/tokenizer.go @@ -1,4 +1,4 @@ -package commandlib +package command import ( "fmt" diff --git a/internal/server/connection.go b/internal/server/connection.go new file mode 100644 index 0000000..3266898 --- /dev/null +++ b/internal/server/connection.go @@ -0,0 +1,75 @@ +package server + +import ( + "bufio" + "fmt" + "net" + "time" + + "github.com/google/uuid" +) + +type Connection struct { + identity uuid.UUID + + conn net.Conn + + inputChannel chan string + closeChannel chan struct{} +} + +func CreateConnection(conn net.Conn) *Connection { + return &Connection{ + identity: uuid.New(), + conn: conn, + inputChannel: make(chan string), + closeChannel: make(chan struct{}), + } +} + +func (c *Connection) listen() (err error) { + defer c.conn.Close() + + c.conn.SetReadDeadline(time.Time{}) + + for { + if c.shouldClose() { + break + } + + message, err := bufio.NewReader(c.conn).ReadString('\n') + + if err != nil { + fmt.Println(err) + return err + } + + c.inputChannel <- message + + c.conn.Write([]byte(message)) + } + + return +} + +func (c *Connection) shouldClose() bool { + select { + case <-c.closeChannel: + return true + default: + return false + } +} + +func (c *Connection) Close() { + c.closeChannel <- struct{}{} +} + +func (c *Connection) NextInput() (next string, err error) { + select { + case val := <-c.inputChannel: + return val, nil + default: + return "", newInputEmptyError() + } +} diff --git a/internal/server/error.go b/internal/server/error.go new file mode 100644 index 0000000..ea43f68 --- /dev/null +++ b/internal/server/error.go @@ -0,0 +1,15 @@ +package server + +type inputEmptyError struct { + msg string +} + +func newInputEmptyError() *inputEmptyError { + return &inputEmptyError{ + msg: "No input available at this moment", + } +} + +func (err *inputEmptyError) Error() string { + return err.msg +} diff --git a/internal/server/server.go b/internal/server/server.go new file mode 100644 index 0000000..eca8399 --- /dev/null +++ b/internal/server/server.go @@ -0,0 +1,86 @@ +package server + +import ( + "fmt" + "net" + "time" +) + +type Server struct { + listener *net.TCPListener + + connections []*Connection + + stopChannel chan struct{} +} + +func CreateServer(port string) (srv *Server, err error) { + addr, err := net.ResolveTCPAddr("tcp", port) + + if err != nil { + fmt.Println(err) + return nil, err + } + + ln, err := net.ListenTCP("tcp", addr) + + if err != nil { + fmt.Println(err) + return nil, err + } + + fmt.Println("Listening on port", port) + + srv = &Server{ + listener: ln, + connections: []*Connection{}, + stopChannel: make(chan struct{}), + } + + return +} + +func (srv *Server) Listen() { + // Wait for 200 millis for a new connection, and loop if none found in that time + srv.listener.SetDeadline(time.Now().Add(1 * time.Second)) + + for { + if srv.shouldStop() { + break + } + + conn, err := srv.listener.Accept() + + if err != nil { + continue + } + + c := CreateConnection(conn) + srv.connections = append(srv.connections, c) + + go c.listen() + } + + for _, v := range srv.connections { + v.Close() + } + + err := srv.listener.Close() + + if err != nil { + fmt.Println(err) + } +} + +func (srv *Server) shouldStop() bool { + select { + case <-srv.stopChannel: + return true + default: + return false + } +} + +func (srv *Server) Stop() { + srv.stopChannel <- struct{}{} +} diff --git a/src/CommandLib/go.mod b/src/CommandLib/go.mod deleted file mode 100644 index 61b9aed..0000000 --- a/src/CommandLib/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module code.haedhutner.dev/mvv/LastMUD/CommandLib - -go 1.24.4 diff --git a/src/CoreLib/go.mod b/src/CoreLib/go.mod deleted file mode 100644 index 27a45be..0000000 --- a/src/CoreLib/go.mod +++ /dev/null @@ -1,6 +0,0 @@ -module code.haedhutner.dev/mvv/LastMUD/CoreLib - -require code.haedhutner.dev/mvv/LastMUD/CommandLib v0.0.0 -replace code.haedhutner.dev/mvv/LastMUD/CommandLib => ../CommandLib - -go 1.24.4 diff --git a/src/Server/go.mod b/src/Server/go.mod deleted file mode 100644 index a2f390c..0000000 --- a/src/Server/go.mod +++ /dev/null @@ -1,13 +0,0 @@ -module code.haedhutner.dev/mvv/LastMUD/Server - -require ( - code.haedhutner.dev/mvv/LastMUD/CommandLib v0.0.0 - code.haedhutner.dev/mvv/LastMUD/CoreLib v0.0.0 -) - -replace ( - code.haedhutner.dev/mvv/LastMUD/CommandLib => ../CommandLib - code.haedhutner.dev/mvv/LastMUD/CoreLib => ../CoreLib -) - -go 1.24.4 diff --git a/src/Server/main.go b/src/Server/main.go deleted file mode 100644 index b346b5f..0000000 --- a/src/Server/main.go +++ /dev/null @@ -1,80 +0,0 @@ -package main - -import ( - "bufio" - "fmt" - "log" - "net" -) - -type Command interface { - Name() string -} - -func main() { - ln, err := net.Listen("tcp", ":8000") - - if err != nil { - log.Fatal(err) - } - - fmt.Println("Listening on port 8000") - - conn, err := ln.Accept() - - if err != nil { - log.Fatal(err) - } - - // cmdRegistry := commandlib.CreateCommandRegistry( - // commandlib.CreateCommandDefinition( - // "exit", - // func(tokens []commandlib.Token) bool { - // return tokens[0].Lexeme() == "exit" - // }, - // func(tokens []commandlib.Token) []commandlib.Parameter { - // return nil - // }, - // func(parameters ...commandlib.Parameter) (err error) { - // err = conn.Close() - // return - // }, - // ), - // ) - - for { - message, _ := bufio.NewReader(conn).ReadString('\n') - response := "" - - // if err != nil { - - // if err == io.EOF { - // fmt.Println("Client disconnected") - // break - // } - - // log.Println("Read error:", err) - - // continue - // } - - conn.Write([]byte(message + "\n")) - - // cmdContext, err := commandlib.CreateCommandContext(cmdRegistry, message) - - // if err != nil { - // log.Println(err) - // response = err.Error() - // } else { - // // err = cmdContext.ExecuteCommand() - - // // if err != nil { - // // log.Println(err) - // // response = err.Error() - // // } - // } - - conn.Write([]byte(response + "\n> ")) - } - -}