Compare commits

...

10 Commits

5 changed files with 202 additions and 42 deletions

View File

@ -1,14 +1,14 @@
package main package main
import ( import (
"fmt"
"time" "time"
"git.qowevisa.me/Qowevisa/tcpmachine/tcpclient" "git.qowevisa.me/Qowevisa/tcpmachine/tcpclient"
) )
func main() { func main() {
conf := tcpclient.GetDefaultConfig() client := tcpclient.CreateClient("127.0.0.1:10000")
client := tcpclient.CreateClient(conf)
go func() { go func() {
for { for {
if client.IsConnected { if client.IsConnected {
@ -19,8 +19,11 @@ func main() {
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
client.Server.Write([]byte("PING\n")) client.Server.Write([]byte("PING\n"))
time.Sleep(time.Second) time.Sleep(time.Second)
client.ErrorsChannel <- fmt.Errorf("test err")
} }
}() }()
go client.ErrorResolver(client.ErrorsChannel) err := client.StartClient()
client.StartClient("127.0.0.1:10000") if err != nil {
fmt.Printf("ERROR: StartClient: %v\n", err)
}
} }

View File

@ -6,14 +6,26 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"strings"
"git.qowevisa.me/Qowevisa/tcpmachine/tcpcommand"
) )
type ClientConfiguration struct { type ClientConfiguration struct {
MessageEndRune rune
MessageSplitRune rune
//
Status uint32
ErrorResolver func(chan error) ErrorResolver func(chan error)
//
ServerHandlerFunc func(server net.Conn)
//
} }
func GetDefaultConfig() ClientConfiguration { func GetDefaultConfig() *ClientConfiguration {
return ClientConfiguration{ return &ClientConfiguration{
MessageEndRune: '\n',
MessageSplitRune: ' ',
ErrorResolver: func(c chan error) { ErrorResolver: func(c chan error) {
for err := range c { for err := range c {
fmt.Printf("DefConfig:Error: %v\n", err) fmt.Printf("DefConfig:Error: %v\n", err)
@ -22,50 +34,129 @@ func GetDefaultConfig() ClientConfiguration {
} }
} }
type ErrorResolverFunc func(errors chan error)
type Client struct { type Client struct {
MessageEndRune rune
MessageSplitRune rune
//
addr string
Status uint32
exit chan bool exit chan bool
Server net.Conn Server net.Conn
IsConnected bool IsConnected bool
// //
Messages chan []byte ServerHandlerFunc func(server net.Conn)
//
ErrorsChannel chan error ErrorsChannel chan error
ErrorResolver func(chan error) ErrorResolver ErrorResolverFunc
//
Commands []tcpcommand.Command
} }
func CreateClient(conf ClientConfiguration) *Client { func CreateClient(addr string, options ...ClientOption) *Client {
return &Client{ conf := GetDefaultConfig()
Messages: make(chan []byte, 16),
ErrorResolver: conf.ErrorResolver, for _, opt := range options {
ErrorsChannel: make(chan error, 8), opt(conf)
exit: make(chan bool, 1), }
c := &Client{
MessageEndRune: conf.MessageEndRune,
MessageSplitRune: conf.MessageSplitRune,
addr: addr,
ErrorResolver: conf.ErrorResolver,
ErrorsChannel: make(chan error, 8),
exit: make(chan bool, 1),
ServerHandlerFunc: conf.ServerHandlerFunc,
}
if c.ServerHandlerFunc == nil {
c.ServerHandlerFunc = GetDefaultServerHandlerFunc(c)
}
return c
}
var (
ERROR_CLIENT_ERRRSL_NIL = errors.New("Error Resolver is nil")
ERROR_CLIENT_ERRCHL_NIL = errors.New("Error Channel is nil")
ERROR_CLIENT_SRVHND_NIL = errors.New("Server Handler Func is nil")
)
func GetDefaultServerHandlerFunc(c *Client) func(server net.Conn) {
return func(server net.Conn) {
serverReader := bufio.NewReader(server)
for {
select {
case <-c.exit:
return
default:
rawMsg, err := serverReader.ReadString(byte(c.MessageEndRune))
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", rawMsg)
msg := strings.TrimRight(rawMsg, string(c.MessageEndRune))
parts := strings.Split(msg, string(c.MessageSplitRune))
// ???
if len(parts) == 0 {
continue
}
cmd := parts[0]
found := false
for _, _cmd := range c.Commands {
if cmd == _cmd.Command {
found = true
_cmd.Action(parts[1:], server)
break
}
}
if !found {
fmt.Printf("Command %s was not handled\n", cmd)
}
}
}
} }
} }
func (c *Client) StartClient(addr string) error { func (c *Client) StartClient() error {
server, err := net.Dial("tcp", addr) if c.Status&statusBitCustomErrorHandling == 0 {
if c.ErrorResolver == nil {
return fmt.Errorf("Can't start client: %w", ERROR_CLIENT_ERRRSL_NIL)
}
if c.ErrorResolver == nil {
return fmt.Errorf("Can't start client: %w", ERROR_CLIENT_ERRCHL_NIL)
}
go c.ErrorResolver(c.ErrorsChannel)
}
if c.ServerHandlerFunc == nil {
return fmt.Errorf("Can't start client: %w", ERROR_CLIENT_SRVHND_NIL)
}
server, err := net.Dial("tcp", c.addr)
if err != nil { if err != nil {
return fmt.Errorf("net.Dial: %w", err) return fmt.Errorf("net.Dial: %w", err)
} }
serverReader := bufio.NewReader(server)
c.IsConnected = true c.IsConnected = true
c.Server = server c.Server = server
loop: c.ServerHandlerFunc(server)
for { return nil
select { }
case <-c.exit:
break loop var commandDuplicateError = errors.New("Command already exists in server")
default: var commandNotHandledError = errors.New("Command was not handled")
msg, err := serverReader.ReadString('\n')
if err != nil { func (c *Client) On(command string, action func(args []string, server net.Conn)) error {
if errors.Is(err, io.EOF) { for _, cmd := range c.Commands {
c.exit <- true if cmd.Command == command {
break return fmt.Errorf("Failed addding command %s: %w ", command, commandDuplicateError)
} }
c.ErrorsChannel <- fmt.Errorf("serverReader.ReadString: %w", err) }
} c.Commands = append(c.Commands, tcpcommand.Command{
fmt.Printf("Server send us a message: %s", msg) Command: command,
c.Messages <- []byte(msg) Action: action,
} })
}
return nil return nil
} }

23
tcpclient/options.go Normal file
View File

@ -0,0 +1,23 @@
package tcpclient
import "net"
type ClientOption func(conf *ClientConfiguration)
const (
statusBitNothing = 0
statusBitCustomErrorHandling = 1 << iota
)
func WithCustomErrorHandling(fun ErrorResolverFunc) ClientOption {
return func(conf *ClientConfiguration) {
conf.Status |= statusBitCustomErrorHandling
conf.ErrorResolver = fun
}
}
func WithServerHandler(fun func(server net.Conn)) ClientOption {
return func(conf *ClientConfiguration) {
conf.ServerHandlerFunc = fun
}
}

View File

@ -2,7 +2,7 @@ package tcpserver
import "net" import "net"
type ServerOption func(*ServerConfiguration) type ServerOption func(conf *ServerConfiguration)
// WithMessageEndRune sets the MessageEndRune in the server configuration. // WithMessageEndRune sets the MessageEndRune in the server configuration.
func WithMessageEndRune(r rune) ServerOption { func WithMessageEndRune(r rune) ServerOption {
@ -31,3 +31,9 @@ func WithHandleClientFunc(handler func(client net.Conn)) ServerOption {
conf.HandleClientFunc = handler conf.HandleClientFunc = handler
} }
} }
func WithLoggingLevel(level ServerLoggingLevel) ServerOption {
return func(conf *ServerConfiguration) {
conf.LogLevel = level
}
}

View File

@ -12,10 +12,23 @@ import (
"git.qowevisa.me/Qowevisa/tcpmachine/tcpcommand" "git.qowevisa.me/Qowevisa/tcpmachine/tcpcommand"
) )
type ServerLoggingLevel int
const (
LogLevel_Nothing = 0
LogLevel_Connection = 1 << iota
LogLevel_Messages
)
const (
LogLevel_ALL = LogLevel_Connection | LogLevel_Messages
)
type ServerConfiguration struct { type ServerConfiguration struct {
MessageEndRune rune MessageEndRune rune
MessageSplitRune rune MessageSplitRune rune
HandleClientFunc func(client net.Conn) HandleClientFunc func(client net.Conn)
LogLevel ServerLoggingLevel
// //
ErrorResolver func(chan error) ErrorResolver func(chan error)
} }
@ -46,20 +59,28 @@ func CreateHandleClientFuncFromCommands(bundle *tcpcommand.CommandBundle, conf S
} }
type Server struct { type Server struct {
addr string addr string
HandleClientFunc func(client net.Conn) PreHandlerClientFunc func(client net.Conn)
Exit chan bool HandleClientFunc func(client net.Conn)
// Use PostHandlerClientFunc in your HandleClientFunc if you
// use custom HandleClientFunc
PostHandlerClientFunc func(client net.Conn)
Exit chan bool
// //
MessageEndRune rune MessageEndRune rune
MessageSplitRune rune MessageSplitRune rune
ErrorsChannel chan error ErrorsChannel chan error
ErrorResolver func(chan error) ErrorResolver func(chan error)
LogLevel ServerLoggingLevel
// //
Commands []tcpcommand.Command Commands []tcpcommand.Command
} }
func defaultHandleClientFunc(server *Server) func(net.Conn) { func defaultHandleClientFunc(server *Server) func(net.Conn) {
return func(client net.Conn) { return func(client net.Conn) {
if server.PostHandlerClientFunc != nil {
defer server.PostHandlerClientFunc(client)
}
defer client.Close() defer client.Close()
connReader := bufio.NewReader(client) connReader := bufio.NewReader(client)
for { for {
@ -73,6 +94,9 @@ func defaultHandleClientFunc(server *Server) func(net.Conn) {
msgWoNl := strings.Trim(msg, string(server.MessageEndRune)) msgWoNl := strings.Trim(msg, string(server.MessageEndRune))
parts := strings.Split(msgWoNl, string(server.MessageSplitRune)) parts := strings.Split(msgWoNl, string(server.MessageSplitRune))
commandFound := false commandFound := false
if server.LogLevel&LogLevel_Messages > 0 {
log.Printf("Message received from %s : %s\n", client.RemoteAddr(), msgWoNl)
}
for _, cmd := range server.Commands { for _, cmd := range server.Commands {
if cmd.Command == parts[0] { if cmd.Command == parts[0] {
cmd.Action(parts[1:], client) cmd.Action(parts[1:], client)
@ -87,7 +111,7 @@ func defaultHandleClientFunc(server *Server) func(net.Conn) {
} }
} }
// HandleClientFunc is NOT created by this function // NOTE: HandleClientFunc is NOT created by this function
// see: CreateHandleClientFuncFromCommands(bundle) // see: CreateHandleClientFuncFromCommands(bundle)
func GetDefaultConfig() ServerConfiguration { func GetDefaultConfig() ServerConfiguration {
return ServerConfiguration{ return ServerConfiguration{
@ -118,6 +142,7 @@ func CreateServer(addr string, options ...ServerOption) *Server {
MessageSplitRune: conf.MessageSplitRune, MessageSplitRune: conf.MessageSplitRune,
ErrorsChannel: make(chan error, 8), ErrorsChannel: make(chan error, 8),
ErrorResolver: conf.ErrorResolver, ErrorResolver: conf.ErrorResolver,
LogLevel: conf.LogLevel,
// //
Commands: cmds, Commands: cmds,
} }
@ -158,12 +183,18 @@ loop:
case <-s.Exit: case <-s.Exit:
break loop break loop
default: default:
newCLient, err := listener.Accept() newClient, err := listener.Accept()
if err != nil { if err != nil {
s.ErrorsChannel <- fmt.Errorf("listener.Accept: %w", err) s.ErrorsChannel <- fmt.Errorf("listener.Accept: %w", err)
break break
} }
go s.HandleClientFunc(newCLient) if s.LogLevel&LogLevel_Connection > 0 {
fmt.Printf("New Connection from %s is accepted\n", newClient.RemoteAddr())
}
if s.PreHandlerClientFunc != nil {
s.PreHandlerClientFunc(newClient)
}
go s.HandleClientFunc(newClient)
} }
} }
return nil return nil
@ -172,6 +203,12 @@ loop:
var commandDuplicateError = errors.New("Command already exists in server") var commandDuplicateError = errors.New("Command already exists in server")
var commandNotHandledError = errors.New("Command was not handled") var commandNotHandledError = errors.New("Command was not handled")
// On registers an action for a specific command. The action will be executed
// when the command is received from a client. For example, given the input
// string `TEST 1 2 3 4`, the command would be `TEST`, and the args would be
// []string{"1", "2", "3", "4"}.
//
// NOTE: This behavior applies only if `MessageSplitRune` is set to the default value (' ').
func (s *Server) On(command string, action func(args []string, client net.Conn)) error { func (s *Server) On(command string, action func(args []string, client net.Conn)) error {
for _, cmd := range s.Commands { for _, cmd := range s.Commands {
if cmd.Command == command { if cmd.Command == command {