当前位置: 首页 > 工具软件 > Bible Reader > 使用案例 >

《go语言圣经》习题答案-第8章

戚勇
2023-12-01

目录

8.1

8.3

8.4

8.5

8.6

8.8

8.9

8.10

8.11

8.12

8.13

8.14

8.15


8.1

server代码

// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/

// See page 222.

// Clock is a TCP server that periodically writes the time.
package main

import (
	"flag"
	"io"
	"log"
	"net"
	"time"
)

var port = flag.String("port", "8000", "tcp port")

func handleConn(c net.Conn) {
	defer c.Close()
	for {
		_, err := io.WriteString(c, time.Now().Format("15:04:05\n"))
		if err != nil {
			return // e.g., client disconnected
		}
		time.Sleep(1 * time.Second)
	}
}

func main() {
	flag.Parse()
	listener, err := net.Listen("tcp", "localhost:"+(*port))
	if err != nil {
		log.Fatal(err)
	}
	//!+
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Print(err) // e.g., connection aborted
			continue
		}
		go handleConn(conn) // handle connections concurrently
	}
	//!-
}

client代码

// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/

// See page 221.
//!+

// Netcat1 is a read-only TCP client.
package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"strings"
	"time"
)

func main() {
	aeras := []string{}
	hosts := []string{}
	for _, arg := range os.Args[1:] {
		i := strings.Index(arg, "=")
		aeras = append(aeras, arg[0:i])
		hosts = append(hosts, arg[i+1:])
	}
	results := make([]string, len(aeras))
	for i := 0; i < len(results); i++ {
		i := i
		go func() {
			conn, err := net.Dial("tcp", hosts[i])
			defer conn.Close()
			if err != nil {
				log.Fatal(err)
			}
			s := bufio.NewScanner(conn)
			for s.Scan() {
				results[i] = s.Text()
			}
		}()
	}
	time.Sleep(500 * time.Millisecond)
	for {
		for i := 0; i < len(results); i++ {
			t := aeras[i] + "=" + hosts[i] + ":" + results[i]
			fmt.Println(t)
		}
		time.Sleep(1 * time.Second)
		fmt.Println()
	}

}

func mustCopy(dst io.Writer, src io.Reader) {
	if _, err := io.Copy(dst, src); err != nil {
		log.Fatal(err)
	}
}

//!-

8.3

注意服务端必须使用reverb1,也就是echo函数不能放在goroutine中

package main

import (
	"io"
	"log"
	"net"
	"os"
)

func main() {
	conn, err := net.Dial("tcp", "localhost:8000")
	if err != nil {
		log.Fatal(err)
	}
	done := make(chan struct{})
	go func() {
		_, err := io.Copy(os.Stdout, conn)
		if err != nil {
			log.Print(err)
		}
		log.Println("done")
		done <- struct{}{}
	}()
	MustCopy(conn, os.Stdin)
	tcpc := conn.(*net.TCPConn)
	err = tcpc.CloseWrite()
	if err != nil {
		log.Print(err)
	}
	<-done
	tcpc.Close()
}

func MustCopy(out io.Writer, in io.Reader) {
	if _, err := io.Copy(out, in); err != nil {
		log.Fatal(err)
	}
	log.Print("终止读入")
}

8.4

package main

import (
	"bufio"
	"fmt"
	"log"
	"net"
	"strings"
	"sync"
	"time"
)

func echo(c net.Conn, text string, delay time.Duration) {
	fmt.Fprintln(c, "\t", strings.ToUpper(text))
	time.Sleep(delay)
	fmt.Fprintln(c, "\t", text)
	time.Sleep(delay)
	fmt.Fprintln(c, "\t", strings.ToLower(text))
}

func handleConn(c net.Conn) {
	defer c.Close()
	sc := bufio.NewScanner(c)
	var wg sync.WaitGroup
	for sc.Scan() {
		wg.Add(1)
		go func(str string) {
			defer wg.Done()
			echo(c, str, 2*time.Second)
		}(sc.Text())
	}
	wg.Wait()
}

func main() {
	listener, err := net.Listen("tcp", "localhost:8000")
	if err != nil {
		log.Fatal(err)
	}
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Print(err)
			continue
		}
		go handleConn(conn)
	}
}

8.5

// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/

// See page 58.
//!+

// Surface computes an SVG rendering of a 3-D surface function.
package main

import (
	"fmt"
	"math"
	"sync"
	"time"
)

const (
	width, height = 600, 320            // canvas size in pixels
	cells         = 100                 // number of grid cells
	xyrange       = 30.0                // axis ranges (-xyrange..+xyrange)
	xyscale       = width / 2 / xyrange // pixels per x or y unit
	zscale        = height * 0.4        // pixels per z unit
	angle         = math.Pi / 6         // angle of x, y axes (=30°)
)

var sin30, cos30 = math.Sin(angle), math.Cos(angle) // sin(30°), cos(30°)

func main() {
	doPaint(1)
	doPaint(4)
	doPaint(8)
	doPaint(16)
	doPaint(32)
	doPaint(64)
	doPaint(128)

}

func doPaint(routinues int) {
	work := make(chan int, cells)
	var wg sync.WaitGroup
	for r := routinues; r > 0; r-- {
		wg.Add(1)
		go func() {
			for i := range work {
				for j := 0; j < cells; j++ {
					corner(i+1, j)
					corner(i, j)
					corner(i, j+1)
					corner(i+1, j+1)
				}
			}
			wg.Done()
		}()
	}
	start := time.Now()
	for i := 0; i < cells; i++ {
		work <- i
	}
	close(work)
	wg.Wait()
	fmt.Printf("routines is %d, timecostis %s\n ", routinues, time.Since(start).String())
}

func corner(i, j int) (float64, float64) {
	// Find point (x,y) at corner of cell (i,j).
	x := xyrange * (float64(i)/cells - 0.5)
	y := xyrange * (float64(j)/cells - 0.5)

	// Compute surface height z.
	z := f(x, y)

	// Project (x,y,z) isometrically onto 2-D SVG canvas (sx,sy).
	sx := width/2 + (x-y)*cos30*xyscale
	sy := height/2 + (x+y)*sin30*xyscale - z*zscale
	return sx, sy
}

func f(x, y float64) float64 {
	r := math.Hypot(x, y) // distance from (0,0)
	return math.Sin(r) / r
}

//!-

8.6

// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/

// See page 139.

// Findlinks3 crawls the web, starting with the URLs on the command line.
package main

import (
	"fmt"
	"log"
	"os"

	"gopl.io/ch5/links"
)

var tokens = make(chan struct{}, 20)

type linkT struct {
	url   string
	depth int
}

//!+crawl
func crawl(link linkT) []linkT {
	fmt.Println(link.url, "  ...... ", link.depth)
	tokens <- struct{}{}
	list, err := links.Extract(link.url)
	ls := []linkT{}
	<-tokens
	if err != nil {
		log.Print(err)
	}
	for _, l := range list {
		ls = append(ls, linkT{l, link.depth + 1})
	}
	return ls
}

//!-crawl

//!+main
func main() {
	workList := make(chan []linkT)
	seen := map[string]bool{}
	go func() {
		links := []linkT{}
		for _, l := range os.Args[1:] {
			links = append(links, linkT{l, 0})
		}
		workList <- links
	}()

	for n := 1; n > 0; n-- {
		list := <-workList
		for _, link := range list {
			if !seen[link.url] && link.depth <= 3 {
				seen[link.url] = true
				n++
				go func(link linkT) {
					workList <- crawl(link)
				}(link)
			}
		}

	}

}

//!-main

8.8

package main

import (
	"bufio"
	"fmt"
	"log"
	"net"
	"strings"
	"sync"
	"time"
)

func echo(c net.Conn, text string, delay time.Duration) {
	fmt.Fprintln(c, "\t", strings.ToUpper(text))
	time.Sleep(delay)
	fmt.Fprintln(c, "\t", text)
	time.Sleep(delay)
	fmt.Fprintln(c, "\t", strings.ToLower(text))
}

func handleConn(c net.Conn) {
	defer c.Close()
	sc := bufio.NewScanner(c)
	var wg sync.WaitGroup
	tick := make(chan struct{})
	go func() {
		for {
			select {
			case <-time.After(10 * time.Second):
				log.Printf("客户%s端超过10s未发送消息,准备关闭连接\n", c.RemoteAddr())
				wg.Wait()
				c.Close()
				log.Printf("服务端主动断开连接 客户地址%s\n", c.RemoteAddr())
				return
			case _, ok := <-tick:
				if !ok { // 退出主循环使得goroutine得以被释放
					log.Printf("客户端主动断开连接 客户地址%s\n", c.RemoteAddr())
					return
				}
			}
		}

	}()
	for sc.Scan() {
		wg.Add(1)
		tick <- struct{}{}
		go func(str string) {
			defer wg.Done()
			echo(c, str, 2*time.Second)
		}(sc.Text())
	}
	close(tick) // 注意关闭通道防止goroutine泄漏
	wg.Wait()
}

func main() {
	listener, err := net.Listen("tcp", "localhost:8000")
	if err != nil {
		log.Fatal(err)
	}
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Print(err)
			continue
		}
		go handleConn(conn)
	}
}

8.9

package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"
	"time"
)

func walkDir(dir string, n *sync.WaitGroup, fileSize chan<- int64) {
	defer n.Done()
	for _, entry := range decent(dir) {
		if entry.IsDir() {
			subdir := filepath.Join(dir, entry.Name())
			n.Add(1)
			go walkDir(subdir, n, fileSize)
		} else {
			fileSize <- entry.Size()
		}
	}
}

func decent(dir string) []os.FileInfo {
	entrys, err := ioutil.ReadDir(dir)
	if err != nil {
		fmt.Fprintf(os.Stderr, "du:%v\n", err)
		return nil
	}
	return entrys
}

func main() {
	root := []string{"/usr"}
	for {
		fileSize := make(chan int64)
		var n sync.WaitGroup
		go func() {
			for _, r := range root {
				n.Add(1)
				go walkDir(r, &n, fileSize)
			}
			n.Wait()
			close(fileSize)
		}()
		var nfiles, nbytes int64
		start := time.Now()
		fmt.Printf("\n开始计算%s目录的大小\n", root[0])
		for size := range fileSize {
			nfiles++
			nbytes += size
		}
		printDiskUsage(nfiles, nbytes)
		fmt.Printf("共耗时 %s\n", time.Since(start).String())
		time.Sleep(5 * time.Second)
	}

}

func printDiskUsage(nfiles, nbytes int64) {
	fmt.Printf("%d files %.1fGB\n", nfiles, float64(nbytes)/1e9)
}

8.10

findlinks.go

// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/

// See page 243.

// Crawl3 crawls web links starting with the command-line arguments.
//
// This version uses bounded parallelism.
// For simplicity, it does not address the termination problem.
//
package main

import (
	"context"
	"fmt"
	"log"
	"os"
)

func crawl(url string, ctx context.Context) []string {
	fmt.Println(url)
	list, err := Extract(url, ctx)
	if err != nil {
		log.Print(err)
	}
	return list
}

//!+
func main() {
	worklist := make(chan []string)  // lists of URLs, may have duplicates
	unseenLinks := make(chan string) // de-duplicated URLs

	// Add command-line arguments to worklist.
	go func() { worklist <- os.Args[1:] }()

	context, cancle := context.WithCancel(context.Background())

	go func() {
		os.Stdin.Read(make([]byte, 1))
		cancle()
	}()

	// Create 20 crawler goroutines to fetch each unseen link.
	for i := 0; i < 20; i++ {
		go func() {
			for link := range unseenLinks {
				foundLinks := crawl(link, context)
				go func() { worklist <- foundLinks }()
			}
		}()
	}

	// The main goroutine de-duplicates worklist items
	// and sends the unseen ones to the crawlers.
	seen := make(map[string]bool)
	for n := 1; n > 0; n-- {
		list := <-worklist
		for _, link := range list {
			if !seen[link] {
				seen[link] = true
				n++
				unseenLinks <- link
			}
		}
	}
}

//!-

links.go

// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/

// See page 138.
//!+Extract

// Package links provides a link-extraction function.
package main

import (
	"context"
	"fmt"
	"net/http"

	"golang.org/x/net/html"
)

// Extract makes an HTTP GET request to the specified URL, parses
// the response as HTML, and returns the links in the HTML document.
func Extract(url string, ctx context.Context) ([]string, error) {
	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
	if err != nil {
		return nil, err
	}
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != http.StatusOK {
		resp.Body.Close()
		return nil, fmt.Errorf("getting %s: %s", url, resp.Status)
	}

	doc, err := html.Parse(resp.Body)
	resp.Body.Close()
	if err != nil {
		return nil, fmt.Errorf("parsing %s as HTML: %v", url, err)
	}

	var links []string
	visitNode := func(n *html.Node) {
		if n.Type == html.ElementNode && n.Data == "a" {
			for _, a := range n.Attr {
				if a.Key != "href" {
					continue
				}
				link, err := resp.Request.URL.Parse(a.Val)
				if err != nil {
					continue // ignore bad URLs
				}
				links = append(links, link.String())
			}
		}
	}
	forEachNode(doc, visitNode, nil)
	return links, nil
}

//!-Extract

// Copied from gopl.io/ch5/outline2.
func forEachNode(n *html.Node, pre, post func(n *html.Node)) {
	if pre != nil {
		pre(n)
	}
	for c := n.FirstChild; c != nil; c = c.NextSibling {
		forEachNode(c, pre, post)
	}
	if post != nil {
		post(n)
	}
}

8.11

package main

import (
	"context"
	"fmt"
	"io/ioutil"
	"net/http"
	"time"
)

func mirroredQuery() {
	responses := make(chan string)

	getRes := func(req *http.Request) {
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			fmt.Println(err.Error())
		} else {
			text, _ := ioutil.ReadAll(resp.Body)
			select {
			case responses <- string(text):
			default:
			}
			resp.Body.Close()
		}
		fmt.Printf("go %s finished\n", req.URL.String())
	}

	ctx, cancle := context.WithCancel(context.Background())
	resq1, _ := http.NewRequestWithContext(ctx, "GET", "http://www.golang.com", nil)
	resq2, _ := http.NewRequestWithContext(ctx, "GET", "http://www.baidu.com", nil)
	resq3, _ := http.NewRequestWithContext(ctx, "GET", "http://www.sina.com", nil)

	go getRes(resq1)
	go getRes(resq2)
	go getRes(resq3)

	body := <-responses
	cancle()
	fmt.Println(body)
	time.Sleep(1 * time.Second)

}

func main() {
	mirroredQuery()
}

8.12

package main

import (
	"bufio"
	"fmt"
	"log"
	"net"
)

type client struct {
	name string
	ch   chan<- string
}

var (
	massages = make(chan string)
	entering = make(chan client)
	leaving  = make(chan client)
)

func main() {
	listener, err := net.Listen("tcp", "localhost:8000")
	if err != nil {
		log.Fatal(err)
	}
	go broadcast()
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Print(err)
			continue
		}
		go handleConn(conn)
	}
}

func broadcast() {
	clients := make(map[client]bool)
	for {
		select {
		case msg := <-massages:
			for cli := range clients {
				cli.ch <- msg
			}
		case cli := <-entering:
			{
				clients[cli] = true
				online := []string{}
				for c := range clients {
					online = append(online, c.name)
				}
				s := fmt.Sprintf("当前在线成员%v\n", online)
				cli.ch <- s
			}
		case cli := <-leaving:
			{
				delete(clients, cli)
				close(cli.ch)
			}
		}
	}
}

func handleConn(conn net.Conn) {
	ch := make(chan string)
	go clientWriter(conn, ch)

	who := conn.RemoteAddr().String()
	ch <- "you are " + who
	massages <- who + " has arrived"
	entering <- client{who, ch}

	input := bufio.NewScanner(conn)
	for input.Scan() {
		massages <- input.Text()
	}

	leaving <- client{who, ch}
	massages <- who + " has left"
	conn.Close()
}

func clientWriter(conn net.Conn, ch <-chan string) {
	for msg := range ch {
		fmt.Fprintln(conn, msg)
	}
}

8.13

package main

import (
	"bufio"
	"fmt"
	"log"
	"net"
	"time"
)

type client struct {
	name string
	ch   chan<- string
}

var (
	massages = make(chan string)
	entering = make(chan client)
	leaving  = make(chan client)
)

func main() {
	listener, err := net.Listen("tcp", "localhost:8000")
	if err != nil {
		log.Fatal(err)
	}
	go broadcast()
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Print(err)
			continue
		}
		go handleConn(conn)
	}
}

func broadcast() {
	clients := make(map[client]bool)
	for {
		select {
		case msg := <-massages:
			for cli := range clients {
				cli.ch <- msg
			}
		case cli := <-entering:
			{
				clients[cli] = true
				online := []string{}
				for c := range clients {
					online = append(online, c.name)
				}
				s := fmt.Sprintf("当前在线成员%v\n", online)
				cli.ch <- s
			}
		case cli := <-leaving:
			{
				delete(clients, cli)
				close(cli.ch)
			}
		}
	}
}

func handleConn(conn net.Conn) {
	ch := make(chan string)
	go clientWriter(conn, ch)

	who := conn.RemoteAddr().String()
	ch <- "you are " + who
	massages <- who + " has arrived"
	entering <- client{who, ch}

	input := bufio.NewScanner(conn)
	alive := make(chan struct{})
	go func() {
		for {
			select {
			case <-time.After(10 * time.Second):
				conn.Close()
				return
			case _, ok := <-alive:
				if !ok {
					return
				}
			}
		}
	}()
	for input.Scan() {
		massages <- input.Text()
		alive <- struct{}{}
	}

	leaving <- client{who, ch}
	massages <- who + " has left"
	conn.Close()
}

func clientWriter(conn net.Conn, ch <-chan string) {
	for msg := range ch {
		fmt.Fprintln(conn, msg)
	}
}

8.14

package main

import (
	"bufio"
	"fmt"
	"log"
	"net"
	"time"
)

type client struct {
	name string
	ch   chan<- string
}

var (
	massages = make(chan string)
	entering = make(chan client)
	leaving  = make(chan client)
)

func main() {
	listener, err := net.Listen("tcp", "localhost:8000")
	if err != nil {
		log.Fatal(err)
	}
	go broadcast()
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Print(err)
			continue
		}
		go handleConn(conn)
	}
}

func broadcast() {
	clients := make(map[client]bool)
	for {
		select {
		case msg := <-massages:
			for cli := range clients {
				cli.ch <- msg
			}
		case cli := <-entering:
			{
				clients[cli] = true
				online := []string{}
				for c := range clients {
					online = append(online, c.name)
				}
				s := fmt.Sprintf("当前在线成员%v\n", online)
				cli.ch <- s
			}
		case cli := <-leaving:
			{
				delete(clients, cli)
				close(cli.ch)
			}
		}
	}
}

func handleConn(conn net.Conn) {
	ch := make(chan string)
	go clientWriter(conn, ch)

	input := bufio.NewScanner(conn)
	input.Scan()
	who := input.Text()
	ch <- "you are " + who
	massages <- who + " has arrived"
	entering <- client{who, ch}

	alive := make(chan struct{})
	go func() {
		for {
			select {
			case <-time.After(10 * time.Second):
				conn.Close()
				return
			case _, ok := <-alive:
				if !ok {
					return
				}
			}
		}
	}()
	for input.Scan() {
		massages <- input.Text()
		alive <- struct{}{}
	}

	leaving <- client{who, ch}
	massages <- who + " has left"
	conn.Close()
}

func clientWriter(conn net.Conn, ch <-chan string) {
	for msg := range ch {
		fmt.Fprintln(conn, msg)
	}
}

8.15

package main

import (
	"bufio"
	"fmt"
	"log"
	"net"
	"time"
)

type client struct {
	name string
	ch   chan<- string
}

var (
	massages = make(chan string)
	entering = make(chan client)
	leaving  = make(chan client)
)

func main() {
	listener, err := net.Listen("tcp", "localhost:8000")
	if err != nil {
		log.Fatal(err)
	}
	go broadcast()
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Print(err)
			continue
		}
		go handleConn(conn)
	}
}

func broadcast() {
	clients := make(map[client]bool)
	for {
		select {
		case msg := <-massages:
			for cli := range clients {
				sendMsg(msg, cli)
			}
		case cli := <-entering:
			{
				clients[cli] = true
				online := []string{}
				for c := range clients {
					online = append(online, c.name)
				}
				s := fmt.Sprintf("当前在线成员%v\n", online)
				sendMsg(s, cli)
			}
		case cli := <-leaving:
			{
				delete(clients, cli)
				close(cli.ch)
			}
		}
	}
}

func sendMsg(msg string, cli client) {
	if cap(cli.ch) < 1 {
		log.Print("调用错误,请传入缓存通道")
		return
	}
	select {
	case cli.ch <- msg:
	default:
		log.Print("%s通道阻塞,消息%s丢失", cli.name, msg)
	}
}

func handleConn(conn net.Conn) {
	ch := make(chan string, 10)
	go clientWriter(conn, ch)

	input := bufio.NewScanner(conn)
	input.Scan()
	who := input.Text()
	ch <- "you are " + who
	massages <- who + " has arrived"
	entering <- client{who, ch}

	alive := make(chan struct{})
	go func() {
		for {
			select {
			case <-time.After(10 * time.Second):
				conn.Close()
				return
			case _, ok := <-alive:
				if !ok {
					return
				}
			}
		}
	}()
	for input.Scan() {
		massages <- input.Text()
		alive <- struct{}{}
	}

	leaving <- client{who, ch}
	massages <- who + " has left"
	conn.Close()
}

func clientWriter(conn net.Conn, ch <-chan string) {
	for msg := range ch {
		fmt.Fprintln(conn, msg)
	}
}

 类似资料: