client.go 5.57 KiB
package main
import (
"bufio"
"encoding/gob"
"fmt"
"net"
"os"
"strconv"
"strings"
"time"
)
type Client struct {
Name string // unique identifier for client
Timestamp time.Time // timestamp of creation
Connection net.Conn // tcp connection to server
Connected bool // true if established connection
Servers []string // server ip addresses and ports
ResponseChan chan Response // channel for responses from server
Encoder *gob.Encoder
}
type Request struct {
ClientName string
PeerName string
Transaction string // type of transaction
Branch string
Account string
Amount int
}
type Response struct {
Result bool
Message string
}
var NUM_SERVERS int = 5 // number of servers
// fills client response channel
func (client *Client) run_server() {
// place server responses into channel
for {
response := Response{}
dec := gob.NewDecoder(client.Connection)
dec.Decode(&response)
client.ResponseChan <- response
}
}
// finds random server to elect as coordinator
func (client *Client) connect() {
request := Request{}
response := Response{}
request.ClientName = client.Name
request.PeerName = ""
request.Transaction = "BEGIN"
request.Branch = client.Name
request.Account = client.Name
for _, server := range client.Servers {
ip := strings.Split(server, ":")[0]
port, _ := strconv.Atoi(strings.Split(server, ":")[1])
server := ip + ":" + strconv.Itoa(port+10)
connection, err := net.Dial("tcp", server)
if err != nil {
continue
}
// send begin request
client.Encoder = gob.NewEncoder(connection)
client.Encoder.Encode(&request)
// receive connection response
dec := gob.NewDecoder(connection)
dec.Decode(&response)
if response.Result {
client.Connection = connection
client.ResponseChan <- response
fmt.Println("Client connected to", server)
go client.run_server() // start server if valid connection response
return
}
}
client.ResponseChan <- response
}
// send request to coordinator server
func (client *Client) send_request(transaction string, account string, amount int) {
request := Request{}
request.ClientName = client.Name
request.PeerName = ""
request.Transaction = transaction
request.Amount = amount
if transaction == "COMMIT" || transaction == "ABORT" {
request.Account = ""
request.Branch = ""
} else {
substrings := strings.Split(account, ".")
request.Account = substrings[1]
request.Branch = substrings[0]
}
client.Encoder.Encode(&request)
}
// read file and fill list of server ip:port
func (client *Client) read_file(config_name string) {
config, error := os.Open(config_name)
if error != nil {
fmt.Println("failed to open config")
return
}
reader := bufio.NewReader(config)
// assumption that only ever have 5 servers (no more, no less)
for i := 0; i < NUM_SERVERS; i++ {
text, _ := reader.ReadString('\n')
substrings := strings.Split(text, " ")
// remove new line char from port
substrings[2] = strings.ReplaceAll(substrings[2], "\n", "")
// formatted server address as sp25-cs425-0601.cs.illinois.edu:1234
client.Servers = append(client.Servers, substrings[1]+":"+substrings[2])
}
config.Close()
}
func main() {
// init client
client := Client{
Name: "",
Timestamp: time.Time{},
ResponseChan: make(chan Response),
}
clientName := os.Args[1]
configPath := os.Args[2]
// read config and attach server:port
client.Name = clientName
client.read_file(configPath)
reader := bufio.NewReader(os.Stdin)
// get user inputs
for {
input, _ := reader.ReadString('\n')
substrings := strings.Split(input, " ")
if len(substrings) == 1 {
substrings[0] = strings.ReplaceAll(substrings[0], "\n", "")
} else if len(substrings) == 2 {
substrings[1] = strings.ReplaceAll(substrings[1], "\n", "")
} else if len(substrings) == 3 {
substrings[2] = strings.ReplaceAll(substrings[2], "\n", "")
}
// handle user input
if substrings[0] == "BEGIN" {
// check if connection exists
if client.Connected {
continue
}
// setup server connection
go client.connect()
response := <-client.ResponseChan
if response.Result {
client.Connected = true
client.Timestamp = time.Now()
fmt.Println("OK")
} else {
fmt.Println("ABORTED")
return
}
} else if substrings[0] == "COMMIT" && client.Connected {
go client.send_request(substrings[0], "", 0)
response := <-client.ResponseChan
if response.Result {
fmt.Println("COMMIT OK")
return
} else {
fmt.Println("ABORTED")
return
}
} else if substrings[0] == "ABORT" && client.Connected {
go client.send_request(substrings[0], "", 0)
<-client.ResponseChan
fmt.Println("ABORTED")
return
} else if substrings[0] == "DEPOSIT" && client.Connected {
amount, _ := strconv.Atoi(substrings[2])
go client.send_request(substrings[0], substrings[1], amount)
response := <-client.ResponseChan
if response.Result {
fmt.Println("OK")
} else {
fmt.Println("ABORTED")
return
}
} else if substrings[0] == "BALANCE" && client.Connected {
go client.send_request(substrings[0], substrings[1], 0)
response := <-client.ResponseChan
if response.Result {
fmt.Println(response.Message)
} else {
fmt.Println("NOT FOUND, ABORTED")
return
}
} else if substrings[0] == "WITHDRAW" && client.Connected {
amount, _ := strconv.Atoi(substrings[2])
go client.send_request(substrings[0], substrings[1], amount)
response := <-client.ResponseChan
if response.Result {
fmt.Println("OK")
} else {
fmt.Println("NOT FOUND, ABORTED")
return
}
}
}
}