diff --git a/tcpclient/client.go b/tcpclient/client.go new file mode 100644 index 0000000..98996da --- /dev/null +++ b/tcpclient/client.go @@ -0,0 +1,71 @@ +package tcpclient + +import ( + "bufio" + "errors" + "fmt" + "io" + "net" +) + +type ClientConfiguration struct { + ErrorResolver func(chan error) +} + +func GetDefaultConfig() ClientConfiguration { + return ClientConfiguration{ + ErrorResolver: func(c chan error) { + for err := range c { + fmt.Printf("DefConfig:Error: %v\n", err) + } + }, + } +} + +type Client struct { + exit chan bool + Server net.Conn + IsConnected bool + // + Messages chan []byte + ErrorsChannel chan error + ErrorResolver func(chan error) +} + +func CreateClient(conf ClientConfiguration) *Client { + return &Client{ + Messages: make(chan []byte, 16), + ErrorResolver: conf.ErrorResolver, + ErrorsChannel: make(chan error, 8), + exit: make(chan bool, 1), + } +} + +func (c *Client) StartClient(addr string) error { + server, err := net.Dial("tcp", addr) + if err != nil { + return fmt.Errorf("net.Dial: %w", err) + } + serverReader := bufio.NewReader(server) + c.IsConnected = true + c.Server = server +loop: + for { + select { + case <-c.exit: + break loop + default: + msg, err := serverReader.ReadString('\n') + if err != nil { + if errors.Is(err, io.EOF) { + c.exit <- true + break + } + c.ErrorsChannel <- fmt.Errorf("serverReader.ReadString: %w", err) + } + fmt.Printf("Server send us a message: %s", msg) + c.Messages <- []byte(msg) + } + } + return nil +} diff --git a/tcpcommand/command.go b/tcpcommand/command.go new file mode 100644 index 0000000..ccd57e0 --- /dev/null +++ b/tcpcommand/command.go @@ -0,0 +1,43 @@ +package tcpcommand + +import ( + "errors" + "net" +) + +type Command struct { + // Command is what server/client will RECEIVE + Command string + // Action is what to DO when such command is RECEIVED + Action func([]string, net.Conn) +} + +type CommandBundle struct { + Commands []Command +} + +// implementation is O(n) +func (cb *CommandBundle) alreadyHaveCommand(cmd string) bool { + for _, command := range cb.Commands { + if command.Command == cmd { + return true + } + } + return false +} + +var ErrCommandBundleDuplicateCommands = errors.New("Found two commands with the same Command value") + +func CreateCommandBundle(commands []Command) (*CommandBundle, error) { + cb := &CommandBundle{ + Commands: []Command{}, + } + for _, cmd := range commands { + // cb.alreadyHaveCommand is O(n) but it's ok since we won't have commandBundle with over 100k commands + if cb.alreadyHaveCommand(cmd.Command) { + return nil, ErrCommandBundleDuplicateCommands + } + cb.Commands = append(cb.Commands, cmd) + } + return cb, nil +} diff --git a/tcpserver/server.go b/tcpserver/server.go new file mode 100644 index 0000000..668328d --- /dev/null +++ b/tcpserver/server.go @@ -0,0 +1,110 @@ +package tcpserver + +import ( + "bufio" + "errors" + "fmt" + "io" + "log" + "net" + "strings" + + "git.qowevisa.me/Qowevisa/tcpmachine/tcpcommand" +) + +type ServerConfiguration struct { + MessageEndRune rune + MessageSplitRune rune + HandleClientFunc func(client net.Conn) + // + ErrorResolver func(chan error) +} + +func CreateHandleClientFuncFromCommands(bundle *tcpcommand.CommandBundle, conf ServerConfiguration) (func(client net.Conn), chan error) { + clientErrors := make(chan error, 16) + return func(client net.Conn) { + connReader := bufio.NewReader(client) + for { + msg, err := connReader.ReadString(byte(conf.MessageEndRune)) + if err != nil { + if errors.Is(err, io.EOF) { + break + } + clientErrors <- err + } + msgWoNl := strings.Trim(msg, string(conf.MessageEndRune)) + parts := strings.Split(msgWoNl, string(conf.MessageSplitRune)) + for _, cmd := range bundle.Commands { + if cmd.Command == parts[0] { + cmd.Action(parts[1:], client) + break + } + } + } + }, clientErrors +} + +type Server struct { + HandleClientFunc func(client net.Conn) + Exit chan bool + // + ErrorsChannel chan error + ErrorResolver func(chan error) +} + +// HandleClientFunc is NOT created by this function +// see: CreateHandleClientFuncFromCommands(bundle) +func GetDefaultConfig() ServerConfiguration { + return ServerConfiguration{ + ErrorResolver: func(c chan error) { + for err := range c { + log.Printf("DefConfig:Error: %v\n", err) + } + }, + } +} + +func CreateServer(conf ServerConfiguration) *Server { + return &Server{ + HandleClientFunc: conf.HandleClientFunc, + Exit: make(chan bool, 1), + // + ErrorsChannel: make(chan error, 8), + ErrorResolver: conf.ErrorResolver, + } +} + +func (s *Server) StartServer(address string) error { + if s.Exit == nil { + return fmt.Errorf("server's Exit channel is nil. Can't start server\n") + } + listener, err := net.Listen("tcp", address) + if err != nil { + return fmt.Errorf("net.Listen: %w", err) + } + defer listener.Close() + if s.ErrorsChannel == nil { + return fmt.Errorf("server's ErrorsChannel is nil. Can't start server\n") + } + if s.ErrorResolver == nil { + return fmt.Errorf("server's ErrorResolver is nil. Can't start server\n") + } + go s.ErrorResolver(s.ErrorsChannel) + log.Printf("Server started listening on %s\n", address) + +loop: + for { + select { + case <-s.Exit: + break loop + default: + newCLient, err := listener.Accept() + if err != nil { + s.ErrorsChannel <- fmt.Errorf("listener.Accept: %w", err) + break + } + go s.HandleClientFunc(newCLient) + } + } + return nil +}