Browse Source

Parts 1 to 7 solved.

Frederic G. MARAND 9 years ago
commit
a9217923bf
13 changed files with 719 additions and 0 deletions
  1. 35 0
      part1/part1.go
  2. BIN
      part2/part2
  3. 60 0
      part2/part2.go
  4. BIN
      part3/part3
  5. 73 0
      part3/part3.go
  6. BIN
      part4/part4
  7. 98 0
      part4/part4.go
  8. BIN
      part5/part5
  9. 108 0
      part5/part5.go
  10. 102 0
      part6/main.go
  11. BIN
      part6/part6
  12. 160 0
      part7/main.go
  13. 83 0
      part7/peers_test.go

+ 35 - 0
part1/part1.go

@@ -0,0 +1,35 @@
+package main
+
+import (
+	"os"
+	"log"
+	"encoding/json"
+	"bufio"
+)
+
+type Message struct {
+	Body string
+}
+
+func main() {
+	// TODO: Create a new bufio.Scanner reading from the standard input.
+	s := bufio.NewScanner(os.Stdin)
+
+	// TODO: Create a new json.Encoder writing into the standard output.
+	e := json.NewEncoder(os.Stdout)
+
+	/* TODO: Iterate over every line in the scanner */
+	for s.Scan() {
+		// TODO: Create a new message with the read text.
+		// TODO: Encode the message, and check for errors!
+		err := e.Encode(Message{Body: s.Text()})
+		if err != nil {
+			log.Fatal(err)
+		}
+	}
+
+		// TODO: Check for a scan error.
+	if err := s.Err(); err != nil {
+		log.Fatal(err)
+	}
+}

BIN
part2/part2


+ 60 - 0
part2/part2.go

@@ -0,0 +1,60 @@
+// Solution to part 2 of the Whispering Gophers code lab.
+//
+// This program extends part 1.
+//
+// It makes a connection the host and port specified by the -dial flag, reads
+// lines from standard input and writes JSON-encoded messages to the network
+// connection.
+//
+// You can test this program by installing and running the dump program:
+// 	$ go get code.google.com/p/whispering-gophers/util/dump
+// 	$ dump -listen=localhost:8000
+// And in another terminal session, run this program:
+// 	$ part2 -dial=localhost:8000
+// Lines typed in the second terminal should appear as JSON objects in the
+// first terminal.
+//
+package main
+
+import (
+	"bufio"
+	"flag"
+	"log"
+	"os"
+	"net"
+	"encoding/json"
+)
+
+var dialAddr = flag.String("dial", "localhost:8000", "host:port to dial")
+
+type Message struct {
+	Body string
+}
+
+func main() {
+	// TODO: Parse the flags.
+	flag.Parse()
+
+	// TODO: Open a new connection using the value of the "dial" flag.
+	c, err := net.Dial("tcp", *dialAddr)
+
+	// TODO: Don't forget to check the error.
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	s := bufio.NewScanner(os.Stdin)
+	// TODO: Create a json.Encoder writing into the connection you created before.
+	e := json.NewEncoder(c)
+
+	for s.Scan() {
+		m := Message{Body: s.Text()}
+		err := e.Encode(m)
+		if err != nil {
+			log.Fatal(err)
+		}
+	}
+	if err := s.Err(); err != nil {
+		log.Fatal(err)
+	}
+}

BIN
part3/part3


+ 73 - 0
part3/part3.go

@@ -0,0 +1,73 @@
+// Solution to part 3 of the Whispering Gophers code lab.
+//
+// This program listens on the host and port specified by the -listen flag.
+// For each incoming connection, it launches a goroutine that reads and decodes
+// JSON-encoded messages from the connection and prints them to standard
+// output.
+//
+// You can test this program by running it in one terminal:
+// 	$ part3 -listen=localhost:8000
+// And running part2 in another terminal:
+// 	$ part2 -dial=localhost:8000
+// Lines typed in the second terminal should appear as JSON objects in the
+// first terminal.
+//
+package main
+
+import (
+	"encoding/json"
+	"flag"
+	"fmt"
+	"io"
+	"log"
+	"net"
+)
+
+var listenAddr = flag.String("listen", "localhost:8000", "host:port to listen on")
+
+type Message struct {
+	Body string
+}
+
+func main() {
+	flag.Parse()
+
+	// TODO: Create a net.Listener listening from the address in the "listen" flag.
+	l, err := net.Listen("tcp", *listenAddr)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	for {
+		// TODO: Accept a new connection from the listener.
+		c, err := l.Accept()
+		if err != nil {
+			log.Fatal(err)
+		}
+		go serve(c)
+	}
+}
+
+func serve(c net.Conn) {
+	// TODO: Use defer to Close the connection when this function returns.
+	defer c.Close()
+
+	// TODO: Create a new json.Decoder reading from the connection.
+	e := json.NewDecoder(c)
+	for {
+		// TODO: Create an empty message.
+		m := Message{}
+		// TODO: Decode a new message into the variable you just created.
+		err := e.Decode(&m)
+		switch err {
+		case nil:
+			// TODO: Print the message to the standard output.
+			fmt.Printf("%+v\n", m)
+		case io.EOF:
+			log.Println("End of input")
+			return
+		default:
+			log.Fatal(err)
+		}
+	}
+}

BIN
part4/part4


+ 98 - 0
part4/part4.go

@@ -0,0 +1,98 @@
+// Solution to part 4 of the Whispering Gophers code lab.
+//
+// This program is a combination of parts 2 and 3.
+//
+// It listens on the host and port specified by the -listen flag.
+// For each incoming connection, it launches a goroutine that reads and decodes
+// JSON-encoded messages from the connection and prints them to standard
+// output.
+// It concurrently makes a connection the host and port specified by the -dial
+// flag, reads lines from standard input, and writes JSON-encoded messages to
+// the network connection.
+//
+// You can test it by running part3 in one terminal:
+// 	$ part3 -listen=localhost:8000
+// Running this program in another terminal:
+// 	$ part4 -listen=localhost:8001 -dial=localhost:8000
+// And running part2 in another terminal:
+// 	$ part2 -dial=localhost:8001
+// Lines typed in the second terminal should appear as JSON objects in the
+// first terminal, and those typed at the third terminal should appear in the
+// second.
+//
+package main
+
+import (
+	"encoding/json"
+	"flag"
+	"fmt"
+	"log"
+	"net"
+	"bufio"
+	"os"
+)
+
+var (
+	listenAddr = flag.String("listen", "", "host:port to listen on")
+	dialAddr   = flag.String("dial", "", "host:port to dial")
+)
+
+type Message struct {
+	Body string
+}
+
+func main() {
+	flag.Parse()
+
+	// TODO: Launch dial in a new goroutine, passing in *dialAddr.
+  go dial(*dialAddr)
+
+	l, err := net.Listen("tcp", *listenAddr)
+	if err != nil {
+		log.Fatal("Listen error: ", err)
+	}
+	for {
+		c, err := l.Accept()
+		if err != nil {
+			log.Fatal("Accept error: ", err)
+		}
+		go serve(c)
+	}
+}
+
+func serve(c net.Conn) {
+	defer c.Close()
+	d := json.NewDecoder(c)
+	for {
+		var m Message
+		err := d.Decode(&m)
+		if err != nil {
+			log.Println(err)
+			return
+		}
+		fmt.Printf("%#v\n", m)
+	}
+}
+
+func dial(addr string) {
+	// TODO: put the contents of the main function from part 2 here.
+
+	c, err := net.Dial("tcp", addr)
+	if err != nil {
+		log.Fatal("Dial error: ", err)
+	}
+
+	s := bufio.NewScanner(os.Stdin)
+	e := json.NewEncoder(c)
+
+	for s.Scan() {
+		m := Message{Body: s.Text()}
+		err := e.Encode(m)
+		if err != nil {
+			log.Fatal("Encoding error: ", err)
+		}
+	}
+	if err := s.Err(); err != nil {
+		log.Fatal("Scan error: ", err)
+	}
+}

BIN
part5/part5


+ 108 - 0
part5/part5.go

@@ -0,0 +1,108 @@
+// Solution to part 5 of the Whispering Gophers code lab.
+//
+// This program extends part 4.
+//
+// It listens on an available public IP and port, and for each incoming
+// connection it decodes JSON-encoded messages and writes them to standard
+// output.
+// It simultaneously makes a connection to the host and port specified by -peer,
+// reads lines from standard input, and writes JSON-encoded messages to the
+// network connection.
+// The messages include the listen address. For example:
+// 	{"Addr": "127.0.0.1:41232", "Body": "Hello"!}
+//
+// You can test this program by listening with the dump program:
+// 	$ dump -listen=localhost:8000
+// In another terminal, running this program:
+// 	$ part5 -peer=localhost:8000
+// And in a third terminal, running part2 with the address printed by part5:
+// 	$ part2 -dial=192.168.1.200:54312
+// Lines typed in the third terminal should appear in the second, and those
+// typed in the second window should appear in the first.
+//
+package main
+
+import (
+	"bufio"
+	"encoding/json"
+	"flag"
+	"fmt"
+	"log"
+	"net"
+	"os"
+
+	"code.google.com/p/whispering-gophers/util"
+)
+
+var (
+	peerAddr = flag.String("peer", "", "peer host:port")
+	self     string
+)
+
+type Message struct {
+	Addr string
+	Body string
+}
+
+func main() {
+	flag.Parse()
+
+	// TODO: Create a new listener using util.Listen and put it in a variable named l.
+	l, err := util.Listen()
+	if err != nil {
+		log.Fatal("Listen error: ", err)
+	}
+	// TODO: Set the global variable self with the address of the listener.
+	self = l.Addr().String()
+
+	// TODO: Print the address to the standard output
+	fmt.Printf("Listening on %s\n", self)
+
+	go dial(*peerAddr)
+
+	for {
+		c, err := l.Accept()
+		if err != nil {
+			log.Fatal(err)
+		}
+		go serve(c)
+	}
+}
+
+func serve(c net.Conn) {
+	defer c.Close()
+	d := json.NewDecoder(c)
+	for {
+		var m Message
+		err := d.Decode(&m)
+		if err != nil {
+			log.Println(err)
+			return
+		}
+		fmt.Printf("%#v\n", m)
+	}
+}
+
+func dial(addr string) {
+	c, err := net.Dial("tcp", addr)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	s := bufio.NewScanner(os.Stdin)
+	e := json.NewEncoder(c)
+	for s.Scan() {
+		m := Message{
+			// TODO: Put the self variable in the new Addr field.
+			Addr: self,
+			Body: s.Text(),
+		}
+		err := e.Encode(m)
+		if err != nil {
+			log.Fatal(err)
+		}
+	}
+	if err := s.Err(); err != nil {
+		log.Fatal(err)
+	}
+}

+ 102 - 0
part6/main.go

@@ -0,0 +1,102 @@
+// Solution to part 6 of the Whispering Gophers code lab.
+//
+// This program is functionally equivalent to part 5,
+// but the reading from standard input and writing to the
+// network connection are done by separate goroutines.
+//
+package main
+
+import (
+	"bufio"
+	"encoding/json"
+	"flag"
+	"fmt"
+	"log"
+	"net"
+	"os"
+
+	"code.google.com/p/whispering-gophers/util"
+)
+
+var (
+	peerAddr = flag.String("peer", "", "peer host:port")
+	self     string
+)
+
+type Message struct {
+	Addr string
+	Body string
+}
+
+func main() {
+	flag.Parse()
+
+	l, err := util.Listen()
+	if err != nil {
+		log.Fatal(err)
+	}
+	self = l.Addr().String()
+	log.Println("Listening on", self)
+
+	go dial(*peerAddr)
+	go readInput()
+
+	for {
+		c, err := l.Accept()
+		if err != nil {
+			log.Fatal(err)
+		}
+		go serve(c)
+	}
+}
+
+func serve(c net.Conn) {
+	defer c.Close()
+	d := json.NewDecoder(c)
+	for {
+		var m Message
+		err := d.Decode(&m)
+		if err != nil {
+			log.Println(err)
+			return
+		}
+		fmt.Printf("%#v\n", m)
+	}
+}
+
+// TODO: Make a new channel of Messages.
+var messages = make(chan Message)
+
+func readInput() {
+	s := bufio.NewScanner(os.Stdin)
+	for s.Scan() {
+		m := Message{
+			Addr: self,
+			Body: s.Text(),
+		}
+		// TODO: Send the message to the channel of messages.
+		messages <- m
+	}
+	if err := s.Err(); err != nil {
+		log.Fatal(err)
+	}
+}
+
+func dial(addr string) {
+	c, err := net.Dial("tcp", addr)
+	if err != nil {
+		log.Println(addr, err)
+		return
+	}
+	defer c.Close()
+	e := json.NewEncoder(c)
+
+	/* TODO: Receive messages from the channel using range, storing them in the variable m. */
+	for m := range messages {
+		err := e.Encode(m)
+		if err != nil {
+			log.Println(addr, err)
+			return
+		}
+	}
+}

BIN
part6/part6


+ 160 - 0
part7/main.go

@@ -0,0 +1,160 @@
+// Skeleton to part 7 of the Whispering Gophers code lab.
+//
+// This program extends part 6 by adding a Peers type.
+// The rest of the code is left as-is, so functionally there is no change.
+//
+// However we have added a peers_test.go file, so that running
+//   go test
+// from the package directory will test your implementation of the Peers type.
+//
+package main
+
+import (
+	"bufio"
+	"encoding/json"
+	"flag"
+	"fmt"
+	"log"
+	"net"
+	"os"
+	"sync"
+
+	"code.google.com/p/whispering-gophers/util"
+)
+
+var (
+	peerAddr = flag.String("peer", "", "peer host:port")
+	self     string
+)
+
+type Message struct {
+	Addr string
+	Body string
+}
+
+func main() {
+	flag.Parse()
+
+	l, err := util.Listen()
+	if err != nil {
+		log.Fatal(err)
+	}
+	self = l.Addr().String()
+	log.Println("Listening on", self)
+
+	go dial(*peerAddr)
+	go readInput()
+
+	for {
+		c, err := l.Accept()
+		if err != nil {
+			log.Fatal(err)
+		}
+		go serve(c)
+	}
+}
+
+type Peers struct {
+	m  map[string]chan<- Message
+	mu sync.RWMutex
+}
+
+// Add creates and returns a new channel for the given peer address.
+// If an address already exists in the registry, it returns nil.
+func (p *Peers) Add(addr string) <-chan Message {
+	// TODO: Take the write lock on p.mu. Unlock it before returning (using defer).
+	defer p.mu.Unlock()
+	p.mu.Lock()
+
+	// TODO: Check if the address is already in the peers map under the key addr.
+	if _, ok := p.m[addr] ; ok {
+	  // TODO: If it is, return nil.
+		return nil
+	}
+
+	// TODO: Make a new channel of messages
+	messages := make(chan Message)
+
+	// TODO: Add it to the peers map
+	p.m[addr] = messages
+
+	// TODO: Return the newly created channel.
+	return messages
+}
+
+// Remove deletes the specified peer from the registry.
+func (p *Peers) Remove(addr string) {
+	// TODO: Take the write lock on p.mu. Unlock it before returning (using defer).
+	defer p.mu.Unlock()
+	p.mu.Lock()
+
+	// TODO: Delete the peer from the peers map.
+	delete(p.m, addr)
+}
+
+// List returns a slice of all active peer channels.
+func (p *Peers) List() []chan<- Message {
+	// TODO: Take the read lock on p.mu. Unlock it before returning (using defer).
+	defer p.mu.RUnlock()
+	p.mu.RLock()
+
+	// TODO: Declare a slice of chan<- Message.
+	s := make([]chan <- Message, 0)
+
+	/* TODO: Iterate over the map using range */
+	for _, v := range p.m {
+		// TODO: Append each channel into the slice.
+		s = append(s, v)
+	}
+	// TODO: Return the slice.
+	return s
+}
+
+func serve(c net.Conn) {
+	defer c.Close()
+	d := json.NewDecoder(c)
+	for {
+		var m Message
+		err := d.Decode(&m)
+		if err != nil {
+			log.Println(err)
+			return
+		}
+		fmt.Printf("%#v\n", m)
+	}
+}
+
+var peer = make(chan Message)
+
+func readInput() {
+	s := bufio.NewScanner(os.Stdin)
+	for s.Scan() {
+		m := Message{
+			Addr: self,
+			Body: s.Text(),
+		}
+		peer <- m
+	}
+	if err := s.Err(); err != nil {
+		log.Fatal(err)
+	}
+}
+
+func dial(addr string) {
+	c, err := net.Dial("tcp", addr)
+	if err != nil {
+		log.Println(addr, err)
+		return
+	}
+	defer c.Close()
+
+	e := json.NewEncoder(c)
+
+	for m := range peer {
+		err := e.Encode(m)
+		if err != nil {
+			log.Println(addr, err)
+			return
+		}
+	}
+}

+ 83 - 0
part7/peers_test.go

@@ -0,0 +1,83 @@
+package main
+
+import (
+	"testing"
+	"time"
+)
+
+func TestPeers(t *testing.T) {
+	peers := &Peers{m: make(map[string]chan<- Message)}
+	done := make(chan bool, 1)
+
+	var chA, chB <-chan Message
+	go func() {
+		defer func() { done <- true }()
+		if chA = peers.Add("a"); chA == nil {
+			t.Fatal(`peers.Add("a") returned nil, want channel`)
+		}
+	}()
+	go func() {
+		defer func() { done <- true }()
+		if chB = peers.Add("b"); chB == nil {
+			t.Fatal(`peers.Add("b") returned nil, want channel`)
+		}
+	}()
+	<-done
+	<-done
+	if chA == chB {
+		t.Fatal(`peers.Add("b") returned same channel as "a"!`)
+	}
+	if ch := peers.Add("a"); ch != nil {
+		t.Fatal(`second peers.Add("a") returned non-nil channel, want nil`)
+	}
+	if ch := peers.Add("b"); ch != nil {
+		t.Fatal(`second peers.Add("b") returned non-nil channel, want nil`)
+	}
+
+	list := peers.List()
+	if len(list) != 2 {
+		t.Fatalf("peers.List() returned a list of length %d, want 2", len(list))
+	}
+
+	go func() {
+		for _, ch := range list {
+			select {
+			case ch <- Message{Body: "foo"}:
+			case <-time.After(10 * time.Millisecond):
+			}
+		}
+		done <- true
+	}()
+	select {
+	case m := <-chA:
+		if m.Body != "foo" {
+			t.Fatal("received message %q, want %q", m.Body, "foo")
+		}
+	case <-done:
+		t.Fatal(`didn't receive message on "a" channel`)
+	}
+	<-done
+
+	peers.Remove("a")
+
+	list = peers.List()
+	if len(list) != 1 {
+		t.Fatalf("peers.List() returned a list of length %d, want 1", len(list))
+	}
+
+	go func() {
+		select {
+		case list[0] <- Message{Body: "bar"}:
+		case <-time.After(10 * time.Millisecond):
+		}
+		done <- true
+	}()
+	select {
+	case m := <-chB:
+		if m.Body != "bar" {
+			t.Fatalf("received message %q, want %q", m.Body, "bar")
+		}
+	case <-done:
+		t.Fatal(`didn't receive message on "b" channel`)
+	}
+}