/
client.go
209 lines (177 loc) · 5.26 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
package main
import (
"bufio"
"fmt"
"log"
"os"
"strings"
"sync"
"github.com/00kristian/MiniProject_2/proto"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
// Global variable for our client
var client proto.ChatClient
// Global wait group
var wait *sync.WaitGroup
// Mutex for locking
var mu sync.Mutex
// lamport time for given client
var lamport uint64 = 0
// Init func to initialize the wait group
func init(){
wait = &sync.WaitGroup{}
}
// Lets the client join into the server
func join(id string, name string) error {
// Stream error to be used if something fails
var sError error
user := &proto.User{
Id: id,
Name: name,
Active: true,
}
// join event increments lamport by one
mu.Lock()
lamport += 1
mu.Unlock()
joinMessage := &proto.Message{
Id: "",
Text: user.Name + " joined Chitty-Chat at Lamport time ",
Lamport: lamport,
}
// Creates the stream, that is return when a user joins the server
stream, err := client.Join(context.Background(), user)
// Publish the join message
_, joinMessageErr := client.Publish(context.Background(), joinMessage)
if err != nil {
log.Fatalf("Connection failed: %v", err)
}
if joinMessageErr != nil {
log.Fatalf("Error occured when publishing join message: %v", joinMessageErr)
}
// Increments the wait group by one
wait.Add(1)
// Go rotuine that spawns an anonymous function, that takes the newly created stream as input.
// The proto.Chat_JoinClient allows us to define the behaviour in order to implement the streaming part of our client
go func(str proto.Chat_JoinClient) {
// Decrements the wait group when mehtod exits
defer wait.Done()
// Infinite for loop
for{
// Wait until a message is recieved in the stream
msg, err := str.Recv()
mu.Lock()
lamport = max(lamport, msg.Lamport) + 1
mu.Unlock()
// If an error occurs, the goroutine and the for loop must terminate.
// Error is passed to the local sError variable
if err != nil {
sError = fmt.Errorf("Error occured when reading message: %v", err)
break;
}
// If id == "", it is a join message
if msg.Id == "" {
log.Printf("[%s: %d] %s", user.Id, lamport, msg.Text)
}else{
log.Printf("[%s: %d] %s: %s", user.Id, lamport, msg.Id, msg.Text)
}
}
}(stream)
return sError
}
func main(){
// Reader to read user input
reader := bufio.NewReader(os.Stdin)
// Dummy channel to ensure all go routines are finished
done := make(chan int)
// Reads and parse name into id and name, which is used to connect
fmt.Print("Please enter you name: ")
temp, _ := reader.ReadString('\n')
name := strings.TrimSpace(temp)
id := name
// Connect to our server - no https, so connect with grpc.WithInsecure()
conn, err := grpc.Dial(":8080", grpc.WithInsecure())
if err != nil{
log.Fatalf("Could not connect: %s", err)
}
// When method is done close the connection
defer conn.Close()
// Creates the client on our connection
client = proto.NewChatClient(conn)
// Show welcome message
welcome()
// Join the server with the given name and id
join(id, name)
//Increment wait gorup before go routine
wait.Add(1)
// Go routine that spawns an anonymous function
go func(){
defer wait.Done()
// Create scanner in order to scan user messages
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan(){
msgContent := strings.TrimSpace(scanner.Text())
if !validateMsg(msgContent) {
fmt.Println("Please type a valid message. A valid message is a UTF-8 encoded string consisting of max 128 characters.")
continue
}
mu.Lock()
lamport += 1
mu.Unlock()
msg := &proto.Message{
Id: id,
Text: msgContent,
Lamport: lamport,
}
// Check if said message is a command
if strings.Contains(msg.Text, "\\leave"){
_ , errLeave := client.Leave(context.Background(), &proto.Id{Id: msg.Id, Lamport: msg.Lamport})
if errLeave != nil{
log.Fatalf("Error occured when trying to leave: %v", errLeave)
}
wait.Done()
break
} else if strings.Contains(msg.Text, "\\help"){
fmt.Println("------------------------------------")
fmt.Println("Following commands are available:")
fmt.Println("\\leave - Exits Chitty-Chat.")
fmt.Println("\\help - Shows this menu again.")
fmt.Println("------------------------------------")
} else{
// Call the broadcast message and distibute the message through all active useres
_, err := client.Publish(context.Background(), msg)
if err != nil {
log.Fatalf("Error sending message: %v", err)
break
}
}
}
}()
// Go routine that spawns anonymous function that ensures that the wait group waits for the go routines to exit
go func(){
wait.Wait()
// Closes our done dummy channel
close(done)
}()
// Acts as a blocker - code will not proceed from this until our done channel has been closed. That happens after all our go routines are done.
<- done
}
func welcome(){
fmt.Println("Welcome to Chitty-chat! =^.^=")
fmt.Println("------------------------------------")
fmt.Println("Following commands are available:")
fmt.Println("\\leave - Exits Chitty-Chat.")
fmt.Println("\\help - Shows this menu again.")
fmt.Println("------------------------------------")
}
func max(x, y uint64) uint64{
if x >= y{
return x
}else {
return y
}
}
func validateMsg(x string) bool {
return len(x) <= 128
}