forked from campoy/whispering-gophers
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtalk.slide
483 lines (250 loc) · 12.7 KB
/
talk.slide
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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
Whispering Gophers
Network programming in Go
Andrew Gerrand
Francesc Campoy
* Introduction
This code lab demonstrates network programming with Go.
The final goal is to build a "whispernet": a peer to peer mesh network for transmitting text messages.
* Design overview
.image talk/img/diag-overview.png
* Pre-requisites
You must have these tools installed and working:
- Go (see [[http://golang.org/doc/install]])
- git (see [[https://git-scm.com/downloads]])
This is not an introduction to the Go Programming Language;
*some*experience*writing*Go*code*is*required*. To learn Go:
- Take [[http://tour.golang.org][A Tour of Go]]
- Check out the [[http://golang.org/doc][Go documentation]]
Create a workspace (see [[http://golang.org/doc/code.html]]) and install the support libraries:
$ go get github.com/campoy/whispering-gophers/hello
$ $GOPATH/bin/hello
It works!
* Creating a workspace
To write Go code you need a workspace. Create one now if you have not already.
$ mkdir $HOME/gocode
$ export GOPATH=$HOME/gocode # or add this to ~/.profile
Build and install a `hello` example program:
$ go get github.com/campoy/whispering-gophers/hello
This installs the `hello` binary to `$GOPATH/bin`.
Check that this worked:
$ $GOPATH/bin/hello
It works!
* The exercises
This code lab is divided into 8 exercises. Each exercise builds on the previous one.
The `skeleton` directory in the `whispering-gophers` repository contains unfinished programs that you should complete yourself.
There is also a `solution` directory that contains the finished programs—only peek if you really need to! :-)
* Vital resources
Visit
.link http://github.com/campoy/whispering-gophers
for code samples and support libraries.
These slides are available at:
.link https://talks.godoc.org/github.com/campoy/whispering-gophers/talk.slide
* Part 1: reading and writing
Write a program that
- reads lines from standard input (`os.Stdin`)
- encodes each line as a JSON object, written to standard output (`os.Stdout`)
This line of input:
Hello, world
should produce this output:
{"Body":"Hello, world"}
This is our system's basic message format.
.image talk/img/diag-part1.png
* Readers and Writers
The `io` package provides fundamental I/O interfaces that are used throughout most Go code.
The most ubiquitous are the `Reader` and `Writer` types, which describe streams of data.
.code talk/code/io/io.go
`Reader` and `Writer` implementations include files, sockets, (de)compressors, image and JSON codecs, and many more.
* Chaining Readers
.play talk/code/reader.go
* Buffered I/O
The `bufio` package implements buffered I/O. Its `bufio.Scanner` type wraps an `io.Reader` and provides a means to consume it by line (or using a specified "split function").
.play talk/code/bufio.go /const/,$
* Encoding JSON objects
The `encoding/json` package converts JSON-encoded data to and from native Go data structures.
.play talk/code/json-encode.go /type/,$
* The Message type
Messages are sent as JSON objects like this:
{"Body":"This is a message!"}
Which corresponds to a Go data structure like this:
type Message struct {
Body string
}
* Error checking
Many functions in Go return an `error` value.
These values are your friends; they will tell you where you went wrong.
Ignore them at your peril!
Use [[http://golang.org/pkg/log/#Println][`log.Println`]] to print log messages, and [[http://golang.org/pkg/log/#Fatal][`log.Fatal`]] to print a message and exit the program printing a stack trace.
.play talk/code/log.go /func main/,$
* Part 1: reading and writing (recap)
Write a program that
- reads lines from standard input (`os.Stdin`)
- encodes each line as a JSON object, written to standard output (`os.Stdout`)
This line of input:
Hello, world
should produce this output:
{"Body":"Hello, world"}
This is our system's basic message format.
.image talk/img/diag-part1.png
* Part 2: Send messages to a peer
Extend your program:
- take an address string from the command line
- make a TCP connection to the remote host
- write the JSON-encoded messages to the connection instead of standard output
.image talk/img/diag-part2.png
* Flag
The `flag` package provides a simple API for parsing command-line flags.
.play talk/code/flag.go
$ flag -message 'Hold on...' -delay 5m
* Making a network connection
The `net` package provides talk/code for network operations.
The [[http://golang.org/pkg/net/#Dial][`net.Dial`]] function opens a nework connection and returns a [[http://golang.org/pkg/net/#Conn][`net.Conn`]], which implements `io.Reader`, `io.Writer`, and `io.Closer` (or `io.ReadWriteCloser`).
.play talk/code/dial.go /func main/,$
(Usually you would use the `net/http` package to make an HTTP request; the purpose of this example is to demonstrate the lower-level `net` package.)
* Part 2: Send messages to a peer (recap)
Extend your program:
- take an address string from the command line
- make a TCP connection to the remote host
- write the JSON-encoded messages to the connection instead of standard output
.image talk/img/diag-part2.png
* Part 3: Serving network connections
Write a new program:
- listen on a TCP port,
- accept incoming connections and launch a goroutine to handle each one,
- decode JSON messages from the incoming connections,
- print each message `Body` to standard output.
.image talk/img/diag-part3.png
* Listen/Accept/Serve (1/2)
The [[http://golang.org/pkg/net/#Listen][`net.Listen`]] function binds to a socket and returns a [[http://golang.org/pkg/net/#Listener][`net.Listener`]].
The [[http://golang.org/pkg/net/#Listener][`net.Listener`]] provides an `Accept` method that blocks until a client connects to the socket, and then returns a [[http://golang.org/pkg/net/#Conn][`net.Conn`]].
This server reads data from a connection and echoes it back:
.play talk/code/listen-single.go /func main/,$
* Goroutines
Goroutines are lightweight threads that are managed by the Go runtime. To run a function in a new goroutine, just put `"go"` before the function call.
.play talk/code/goroutines.go
* Listen/Accept/Serve (2/2)
To handle requests concurrently, serve each connection in its own goroutine:
.play talk/code/listen.go /func main/,$
* Decoding JSON
Decoding JSON from an `io.Reader` is just like writing them to an `io.Writer`, but in reverse.
.play talk/code/json-decode.go /type/,$
* Part 3: Serving network connections (recap)
Write a new program:
- listen on a TCP port,
- accept incoming connections and launch a goroutine to handle each one,
- decode JSON messages from the incoming connections,
- print each message `Body` to standard output.
.image talk/img/diag-part3.png
* Part 4: Listening and dialing
- Combine parts 2 to part 3 (Listen _and_ Dial)
- Modify the dialling code to use `log.Print` and `return` instead of `log.Fatal`
- Test by connecting two instances
.image talk/img/diag-part4.png
* Part 5: distributing the listen address
- Add an `Addr` field to the `Message` struct,
- When sending messages, put the listen address in the `Addr` field.
* Add an Addr field to Message
Add an `Addr` field to the `Message` type:
type Message struct {
Addr string
Body string
}
Now, when constructing `Message` values, populate the `Addr` field with the listen address:
{"Addr":"192.168.1.200:23542","Body":"This is a message!"}
* Obtaining the listener address (1/2)
The `net.Listener` interface provides an `Addr` method that returns a `net.Addr`.
.play talk/code/listen-addr.go /import/,$
When listening on all interfaces, as specified by the empty hostname in the string `":4000"` above, the `net.Addr` won't be that of our public IP address.
To complete our program, we need to find that IP.
* Obtaining the listener address (2/2)
The [[http://godoc.org/github.com/campoy/whispering-gophers/util][`"github.com/campoy/whispering-gophers/util"`]] package provides a `Listen` function that binds to a random port on the first available public interface.
.play talk/code/listen-addr-util.go /import/,$
* Part 5: sending the listen address (recap)
- Add an `Addr` field to the `Message` struct,
- Import `"github.com/campoy/whispering-gophers/util"`,
- Use `util.Listen` instead of `net.Listen`,
- Store the listen address string in a global variable named `self`,
- When sending messages, put the listen address in the `Addr` field.
* Part 6: separate reading and writing
- Separate reading from standard input and dialing into separate functions that run in separate goroutines.
* Channels
Goroutines communicate via channels. A channel is a typed conduit that may be synchronous (unbuffered) or asynchronous (buffered).
.play talk/code/chan.go
* Part 6: separate reading and writing (recap)
- Separate reading from standard input and dialing into separate functions that run in separate goroutines.
- Pass messages from one function to another using a channel.
* Part 7: tracking peer connections
- Implement the `Peers` type as per the skeleton code.
- Use `go test` to run the test suite against your implementation.
* Sharing state
Mutexes are a simple means to protect shared state from concurrent access.
.play talk/code/lock.go /START/,/END/
* Tracking peer connections (1/2)
Each peer connection runs in its own goroutine.
Each goroutine has its own `chan`Message`. It reads messages from the channel, and writes them to the connection as JSON objects.
A central peer registry associates each peer address with its corresponding `chan`Message`.
type Peers struct {
m map[string]chan<- Message
mu sync.RWMutex
}
* Tracking peer connections (2/2)
Before making a peer connection, ask the peer registry for a channel for this address:
// Add creates and returns a new channel for the given address.
// If an address already exists in the registry, it returns nil.
func (p *Peers) Add(addr string) <-chan Message
When a peer connection is dropped, remove the peer from the registry:
// Remove deletes the specified peer from the registry.
func (p *Peers) Remove(addr string)
To broadcast a `Message` to all peers, ask the peer registry for its list of `chan`Message` and send the `Message` to each channel
// List returns a slice of all active peer channels.
func (p *Peers) List() []chan<- Message
* Part 7: tracking peer connections (recap)
- Implement the `Peers` type as per the skeleton code.
- Use `go test` to run the test suite against your implementation.
* Part 8: connect to multiple peers
- Initialze a `Peers` value and store it in a global variable.
- As we see new peer addresses, open connections to those peers.
- For each line read from standard input, broadcast that message to all connected peers.
.image talk/img/diag-part7.png
* Sending without blocking
If a peer connection stalls or dies, we don't want to hold up the rest of our program. To avoid this problem we should do a non-blocking send to each peer when broadcasting messages. This means some messages may be dropped, but in our mesh network this is okay.
.play talk/code/select.go /START/,/END/
* Part 8: connect to multiple peers (recap)
- Initialze a `Peers` value and store it in a global variable.
- As we see new peer addresses, open connections to those peers.
- For each line read from standard input, broadcast that message to all connected peers.
.image talk/img/diag-part7.png
* Part 9: re-broadcast messages
- Add `ID` field to `Message`,
- When creating a `Message`, populate the `ID` field with a random string,
- Track the `ID` of each received message; drop duplicates,
- For each received `Message`, broadcast it to all connected peers.
* Add ID to Message
Add an `ID` field to the `Message` type:
type Message struct {
ID string
Addr string
Body string
}
Now, when constructing `Message` values, populate the `ID` field with a random string:
{"ID":"a09d2abb1ad536ada",Addr":"192.168.1.200:23542,"Body":"This is a message!"}
* Generating random strings
Use the [[http://godoc.org/github.com/campoy/whispering-gophers/util/#RandomID][`util.RandomID`]] function to generate a random string.
.play talk/code/randomid.go
* Tracking message IDs
To track messages IDs, use a `map[string]bool`. This works as a kind of set.
seen := make(map[string]bool)
To check if an id is in the map:
if seen[id] {
fmt.Println(id, "is in the map")
}
To put an id in the map:
seen[id] = true
You should implement a function named `Seen` — make sure it is thread safe!
// Seen returns true if the specified id has been seen before.
// If not, it returns false and marks the given id as "seen".
func Seen(id string) bool
* Part 9: re-broadcast messages (recap)
- Add `ID` field to `Message`
- When a `Message`, populate the `ID` field with a random string
- Track the `ID` of each received message; drop duplicates
- For each received `Message`, broadcast it to all connected peers