Add Default working tcp files

This commit is contained in:
qowevisa 2024-10-10 08:17:51 +03:00
parent 9b6eb1b5d5
commit 1e395f69ef
3 changed files with 224 additions and 0 deletions

71
tcpclient/client.go Normal file
View File

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

43
tcpcommand/command.go Normal file
View File

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

110
tcpserver/server.go Normal file
View File

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