Skip to content
Snippets Groups Projects
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
			}
		}
	}
}