Go入门(七)之并发编程、Socket编程、HTTP编程

上一篇文章> Go入门(六)之文本文件处理

一、并发编程


1、goroutine

(1)创建goroutine
package main

import (
	"fmt"
	"time"
)

func newTask() {
	for {
		fmt.Println("this is a newTask")
		time.Sleep(time.Second) // 延迟一秒
	}
}

func main() {
	go newTask() // 新建一个协程
	for {
		fmt.Println("this is a main goroutine")
		time.Sleep(time.Second) // 延迟一秒
	}
}

结果:

this is a main goroutine
this is a newTask
this is a newTask
this is a main goroutine
this is a main goroutine
this is a newTask
this is a newTask
     .
     .
     .
(2)主goroutine先退出
package main

import (
	"fmt"
	"time"
)

// 主协程退出了,其他子协程也要跟着退出
func main() {
	go func() {
		i := 0
		for {
			i++
			fmt.Println("子协程 i = ", i)
			time.Sleep(time.Second)
		}
	}()

	i := 0
	for {
		i++
		fmt.Println("main i = ", i)
		time.Sleep(time.Second)
		if i == 2 {
			break
		}
	}
}

结果:

main i =  1
子协程 i =  1
子协程 i =  2
main i =  2
子协程 i =  3
(3)runtime之Gosched的使用

让其他协程先执行

package main

import (
	"fmt"
	"runtime"
)

func main() {
	go func() {
		for i := 0; i < 5; i++ {
			fmt.Println("hello go")
		}
	}()

	for i := 0; i < 2; i++ {
		// 让别的先执行
		runtime.Gosched()
		fmt.Println("hello wielun")
	}
}

结果:

hello go
hello go
hello go
hello go
hello go
hello wielun
hello wielun
(4)runtime之Goexit的使用
package main

import (
	"fmt"
	"runtime"
)

func test() {
	defer fmt.Println("3333")
	// return  // 终止函数

	runtime.Goexit() // 终止所在的协程
	fmt.Println("4444")
}

func main() {
	go func() {
		fmt.Println("1111")
		test()

		fmt.Println("2222")
	}()

	for {

	}
}

结果:

1111
3333
(5)runtime之GOMAXPROCS的使用

设置并行计算的CPU核数的最大值,并返回之前的值

package main

import (
	"fmt"
	"runtime"
)

func main() {
	n := runtime.GOMAXPROCS(2) // 指定以2核运算
	fmt.Println("n = ", n)
	for {
		go fmt.Print(1)
		fmt.Print(0)
	}
}

结果:

00000000000000000000011111111111111111111111111111111111111111111111111111111111111111111111000000000000000000000000111111111111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000111111111111111. . .
(6)多任务资源竞争问题
package main

import (
	"fmt"
	"time"
)

func Printer(str string) {
	for _, data := range str {
		fmt.Printf("%c", data)
		time.Sleep(time.Second)
	}
	fmt.Printf("\n")
}

func person1() {
	Printer("hello")
}

func person2() {
	Printer("person")
}
func main() {
	go person1()
	go person2()
	for {

	}
}

结果:

hpeelrsloon...

2、channel

(1)通过channel实现同步
package main

import (
	"fmt"
	"time"
)

// 创建一个channel
var ch = make(chan int)

func Printer(str string) {
	for _, data := range str {
		fmt.Printf("%c", data)
		time.Sleep(time.Second)
	}
	fmt.Printf("\n")
}

func person1() {
	Printer("hello")
	ch <- 666
}

func person2() {
	<-ch
	Printer("person")
}
func main() {
	go person1()
	go person2()
	for {

	}
}

结果:

hello
person
(2)通过channel实现同步和数据交互
package main

import (
	"fmt"
	"time"
)

func main() {
	// 创建channel
	ch := make(chan string)
	defer fmt.Println("主协程结束")
	go func() {
		defer fmt.Println("子协程调用完毕")

		for i := 0; i < 2; i++ {
			fmt.Println("子协程i = ", i)
			time.Sleep(time.Second)
		}
		ch <- "我是子协程,工作完毕"
	}()

	str := <-ch //没有数据阻塞
	fmt.Println("str = ", str)
}

结果:

子协程i =  0
子协程i =  1
子协程调用完毕
str =  我是子协程,工作完毕
主协程结束
(3)无缓冲的channel

创建格式:make(chan Type) //等价于make(chan Type, 0)

package main

import (
	"fmt"
	"time"
)

func main() {
	// 创建一个无缓冲的channel
	ch := make(chan int, 0)

	// 缓冲区剩余数据个数
	fmt.Printf("len(ch) = %d, cap(ch) = %d\n", len(ch), cap(ch))
	go func() {
		for i := 0; i < 3; i++ {
			fmt.Printf("子协程i = %d ", i)
			ch <- i //往chan写内容
		}
	}()
	time.Sleep(2 * time.Second)
	for i := 0; i < 3; i++ {
		num := <-ch // 读管道中内容。没有内容前,阻塞
		fmt.Println("num = ", num)
	}
}

结果:

len(ch) = 0, cap(ch) = 0
子协程i = 0 子协程i = 1 num =  0
num =  1
子协程i = 2 num =  2
(3)有缓冲的channel

创建格式:make(chan Type, capacity)

package main

import (
	"fmt"
	"time"
)

func main() {
	// 创建一个有缓冲的channel
	ch := make(chan int, 3)

	// 缓冲区剩余数据个数
	fmt.Printf("len(ch) = %d, cap(ch) = %d\n", len(ch), cap(ch))
	go func() {
		for i := 0; i < 10; i++ {
			ch <- i //往chan写内容
			fmt.Printf("子协程[%d]: len(ch) = %d, cap(ch) = %d\n", i, len(ch), cap(ch))
		}
	}()
	time.Sleep(2 * time.Second)
	for i := 0; i < 10; i++ {
		num := <-ch // 读管道中内容。没有内容前,阻塞
		fmt.Println("num = ", num)
	}
}

结果:

len(ch) = 0, cap(ch) = 3
子协程[0]: len(ch) = 1, cap(ch) = 3
子协程[1]: len(ch) = 2, cap(ch) = 3
子协程[2]: len(ch) = 3, cap(ch) = 3
num =  0
num =  1
num =  2
num =  3
子协程[3]: len(ch) = 3, cap(ch) = 3
子协程[4]: len(ch) = 0, cap(ch) = 3
子协程[5]: len(ch) = 1, cap(ch) = 3
子协程[6]: len(ch) = 2, cap(ch) = 3
子协程[7]: len(ch) = 3, cap(ch) = 3
num =  4
num =  5
num =  6
num =  7
num =  8
子协程[8]: len(ch) = 3, cap(ch) = 3
子协程[9]: len(ch) = 0, cap(ch) = 3
num =  9
(4)关闭channel

关闭channel后,无法向channel再发送数据(引发panic错误后导致接收立即返回零值)
关闭cahnnel后,可以继续向channel接收数据
对于nil channel,无论收发都会被阻塞

package main

import (
	"fmt"
)

func main() {
	ch := make(chan int)

	go func() {
		for i := 0; i < 5; i++ {
			ch <- i
		}
		// 不需要再写数据时,关闭channel
		close(ch)
	}()

	// 1、通过for实现
	// for {
	// 	// 如果ok为true, 说明管道没有关闭
	// 	if num, ok := <-ch; ok == true {
	// 		fmt.Println("num = ", num)
	// 	} else {
	// 		break
	// 	}
	// }

	// 2、通过range实现
	for num := range ch {
		fmt.Println("num = ", num)
	}
}

结果:

num =  0
num =  1
num =  2
num =  3
num =  4
(5)单向channel特性

声明:

chan<-: 表示数据进入管道,要把数据写进管道,对于调用者就是输出
<-chan: 表示数据从管道出来,对于调用者就是得到管道的数据,当然就是输入

var ch1 chan int        // ch1是一个正常的channel,不是单向的
var ch chan<- float64   // ch2是单向channel,只用于写float64数据
var ch3 <-chan int      // ch3是单向channel,只用于读取int数据
package main

func main() {
	// 创建一个channel,双向的
	ch := make(chan int)

	// 双向channel能隐式转换为单向channel
	var writeCh chan<- int = ch //只能写,不能读
	var readCh <-chan int = ch  //只能读,不能写
	writeCh <- 666              //写
	<-readCh                    //读

	// 单向无法转换为双向
	// var ch2 chan int = writeCh
}
(6)单向channel的应用
package main

import (
	"fmt"
)

// 此通道只能写,不能读
func producer(out chan<- int) {
	for i := 0; i < 10; i++ {
		out <- i * i
	}
	close(out)
}

// 此通道只能读,不能写
func consumer(in <-chan int) {
	for num := range in {
		fmt.Println("num = ", num)
	}
}

func main() {
	ch := make(chan int)

	// 生产者,生产数字,写入channel
	go producer(ch)

	// 消费者,从channel读取数字,打印
	consumer(ch)
}

结果:

num =  0
num =  1
num =  4
num =  9
num =  16
num =  25
num =  36
num =  49
num =  64
num =  81
(7)定时器之Timer的使用

时间到了,只会响应一次,不会多次写入

package main

import (
	"fmt"
	"time"
)

func main() {
	// 创建一个定时器,设置时间为2s,2s后往time通道写内容(当前时间)
	timer := time.NewTimer(2 * time.Second)
	fmt.Println("当前时间:", time.Now())

	// 2s后就会往timer.C写数据,有数据就可以读取
	t := <-timer.C //channel没有数据前会阻塞
	fmt.Println("t = ", t)
}

结果:

当前时间: 2021-07-25 18:46:49.7496266 +0800 CST m=+0.003512801
t =  2021-07-25 18:46:51.7506114 +0800 CST m=+2.004497601
(8)定时器之Timer实现延时功能
package main

import (
	"fmt"
	"time"
)

func main() {
	// 延时2s
	// 方式一
	// timer := time.NewTimer(2 * time.Second)
	// <-timer.C
	// fmt.Println("时间到")

	// 方式二
	// time.Sleep(2*time.Second)
	// fmt.Println("时间到")

	// 方式三
	<-time.After(2 * time.Second) // 定时2s,阻塞2s,2s后产生一个事件,往channel写内容
	fmt.Println("时间到")
}
(9)定时器之停止和重置定时器
package main

import (
	"fmt"
	"time"
)

func main() {
	timer := time.NewTimer(3 * time.Second)
	timer.Reset(1 * time.Second) // 重新设置为1s
	<-timer.C
	fmt.Println("时间到")
}

// 停止
// func main01() {
// 	timer := time.NewTimer(3 * time.Second)

// 	go func() {
// 		<-timer.C
// 		fmt.Println("子协程可以打印了,定时器时间到")
// 	}()

// 	timer.Stop() //停止定时器
// 	for {
// 	}
// }
(10)定时器之Ticker的使用

Ticker是一个定时触发的计时器,它会以一个间隔(interval)往channel发送一个事件(当前时间),而channel的接收者可以以固定的时间间隔从channel中读取事件

package main

import (
	"fmt"
	"time"
)

func main() {
	ticker := time.NewTicker(1 * time.Second)
	i := 0
	for {
		<-ticker.C
		i++
		fmt.Println("i = ", i)
		if i == 5 {
			ticker.Stop()
			break
		}
	}
}

结果:

i =  1
i =  2
i =  3
i =  4
i =  5

3、select

通过select可以监听channel上的数据流动
如果多个条件同时满足,会随机选一个优先执行

(1)fibonacci数列
package main

import (
	"fmt"
)

// ch写,quit读
func fibonacci(ch chan<- int, quit <-chan bool) {
	x, y := 1, 1
	for {
		// 监听channel数据的流动
		select {
		case ch <- x:
			x, y = y, x+y
		case flag := <-quit:
			fmt.Println("flag = ", flag)
			return
		}
	}
}

func main() {
	ch := make(chan int)    //数字通信
	quit := make(chan bool) //程序是否结束

	// 消费者,从channel读取内容
	go func() {
		for i := 0; i < 8; i++ {
			num := <-ch
			fmt.Println(num)
		}
		// 可以停止
		quit <- true
	}()
	// 生产者,产生数字,写入channel
	fibonacci(ch, quit)
}

结果:

1
1
2
3
5
8
13
21
flag =  true
(2)通过select实现超时
package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan int)
	quit := make(chan bool)
	// 协程
	go func() {
		for {
			select {
			case num := <-ch:
				fmt.Println("num = ", num)
			case <-time.After(3 * time.Second):
				fmt.Println("超时")
				quit <- true
			}
		}
	}()

	for i := 0; i < 5; i++ {
		ch <- i
		time.Sleep(time.Second)
	}

	<-quit
	fmt.Println("程序结束")
}

结果:

num =  0
num =  1
num =  2
num =  3
num =  4
超时
程序结束

二、Socket编程


1、CS模型

(1)TCP初体验

<1> TCP服务器

package main

import (
	"fmt"
	"net"
)

func main() {
	// 监听
	listener, err := net.Listen("tcp", "127.0.0.1:8080")
	if err != nil {
		fmt.Println("err = ", err)
		return
	}

	defer listener.Close()
	// 阻塞等待用户链接

	conn, err := listener.Accept()
	if err != nil {
		fmt.Println("err = ", err)
		return
	}

	// 接收用户得请求
	buf := make([]byte, 1024)
	n, err1 := conn.Read(buf)
	if err1 != nil {
		fmt.Println("err1 = ", err1)
		return
	}

	fmt.Println("buf = ", string(buf[:n]))
	defer conn.Close() //关闭当前用户链接
}

<2> TCP客户端

package main

import (
	"fmt"
	"net"
)

func main() {
	// 主动连接服务器
	conn, err := net.Dial("tcp", "127.0.0.1:8080")
	if err != nil {
		fmt.Println("err = ", err)
		return
	}
	defer conn.Close()
	// 发送数据
	conn.Write([]byte("hello wielun"))
}
(2)简单版并发服务器

<1> TCP服务器

package main

import (
	"fmt"
	"net"
	"strings"
)

func HandleConn(conn net.Conn) {
	// 自动关闭
	defer conn.Close()

	// 获取客户端的网络地址信息
	addr := conn.RemoteAddr().String()
	fmt.Println(addr, "connect sucessful")

	buf := make([]byte, 2048)
	for {
		// 读取用户数据
		n, err := conn.Read(buf)
		if err != nil {
			fmt.Println("err = ", err)
			return
		}

		fmt.Printf("[%s]: %s ", addr, string(buf[:n]))

		if "exit" == string(buf[:n-2]) { // 发送时多了换行字符
			fmt.Println(addr, "exit")
		}

		// 转换为大写,在发送给用户
		conn.Write([]byte(strings.ToUpper(string(buf[:n]))))
	}

}

func main() {
	// 监听
	listener, err := net.Listen("tcp", "127.0.0.1:8080")
	if err != nil {
		fmt.Println("err = ", err)
		return
	}

	defer listener.Close()

	// 接收多个用户
	for {
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("err = ", err)
			return
		}

		// 处理用户请求,新建一个协程
		go HandleConn(conn)
	}
}

<2> Client

package main

import (
	"fmt"
	"net"
	"os"
)

func main() {
	// 主动连接服务器
	conn, err := net.Dial("tcp", "127.0.0.1:8080")
	if err != nil {
		fmt.Println("net.Dial err = ", err)
		return
	}

	// 关闭连接
	defer conn.Close()

	go func() {
		str := make([]byte, 1024)
		// 从键盘输入内容,给服务器发送内容
		for {
			n, err := os.Stdin.Read(str) // 从键盘读取内容,放在str
			if err != nil {
				fmt.Println("os.Stdin.Read err = ", err)
				return
			}
			// 内容发送给服务器
			conn.Write(str[:n])
		}
	}()

	// 接受服务器回复的数据
	// 切片缓冲
	buf := make([]byte, 1024)

	for {
		n, err := conn.Read(buf)
		if err != nil {
			fmt.Println("conn.Read err = ", err)
			return
		}
		fmt.Println(string(buf[:n])) //打印接收到的内容
	}
}

2、文件传输

(1)获取文件属性
package main

import (
	"fmt"
	"os"
)

func main() {
	list := os.Args
	if len(list) != 2 {
		fmt.Println("useage: xxx file")
		return
	}
	fileName := list[1]
	info, err := os.Stat(fileName)
	if err != nil {
		fmt.Println("err = ", err)
		return
	}
	fmt.Println("name = ", info.Name())
	fmt.Println("size = ", info.Size())
}

结果:

PS D:\MySoftWare\Go Projects\src\wielun666@gmail.com\02> go run .\04_获取文件属性.go D:\MySoftWare\TIM\Bin\QQScLauncher.exe
name =  QQScLauncher.exe
size =  58960
(2)send
package main

import (
	"fmt"
	"io"
	"net"
	"os"
)

func SendFile(path string, conn net.Conn) {
	// 以只读方式打开文件
	f, err := os.Open(path)
	if err != nil {
		fmt.Println("os.Open err = ", err)
		return
	}
	defer f.Close()

	buf := make([]byte, 1024*4)

	// 读文件内容发送
	for {
		n, err := f.Read(buf)
		if err != nil {
			if err == io.EOF {
				fmt.Println("文件发送完毕")
			} else {
				fmt.Println("f.Read err = ", err)
			}
			return
		}

		// 发送内容
		conn.Write(buf[:n])
	}
}

func main() {
	// 提示输入文件
	fmt.Println("请输入需要传输得文件:")
	var path string
	fmt.Scan(&path)

	// 获取文件名
	info, err := os.Stat(path)
	if err != nil {
		fmt.Println("err = ", err)
		return
	}

	// 主动连接服务器
	conn, err1 := net.Dial("tcp", "127.0.0.1:8080")
	if err1 != nil {
		fmt.Println("err1 = ", err1)
		return
	}

	defer conn.Close()

	// 给接收方,先发送文件名
	_, err = conn.Write([]byte(info.Name()))
	if err != nil {
		fmt.Println("err = ", err)
		return
	}

	// 接收对方得回复
	var n int
	buf := make([]byte, 1024)
	n, err = conn.Read(buf)
	if err != nil {
		fmt.Println("conn.Read err = ", err)
		return
	}
	if "ok" == string(buf[:n]) {
		// 发送文件内容
		SendFile(path, conn)
	}
}
(3)recv
package main

import (
	"fmt"
	"io"
	"net"
	"os"
)

func RecvFile(fileName string, conn net.Conn) {
	// 新建文件
	f, err := os.Create(fileName)
	if err != nil {
		fmt.Println("os.Create err = ", err)
		return
	}
	buf := make([]byte, 1024*4)
	for {
		n, err := conn.Read(buf)
		if err != nil {
			if err == io.EOF {
				fmt.Println("文件接收完毕")
			} else {
				fmt.Println("conn.Read err = ", err)
			}
			return
		}
		f.Write(buf[:n])
	}

}

func main() {
	// 监听
	listenner, err := net.Listen("tcp", "127.0.0.1:8080")
	if err != nil {
		fmt.Println("net.Liste err = ", err)
		return
	}

	defer listenner.Close()

	// 阻塞等待用户连接
	conn, err1 := listenner.Accept()
	if err1 != nil {
		fmt.Println("listenner.Accept err1 = ", err1)
		return
	}

	defer conn.Close()

	buf := make([]byte, 1024)
	var n int
	n, err = conn.Read(buf)
	if err != nil {
		fmt.Println("conn.Read err = ", err)
		return
	}

	fileName := string(buf[:n])

	// 回复ok
	conn.Write([]byte("ok"))

	// 接收文件内容
	RecvFile(fileName, conn)
}

三、HTTP编程


1、http服务器

package main

import (
	"fmt"
	"net/http"
)

// w,给客户端回复数据
// r,读取客户端发送的数据
func HandConn(w http.ResponseWriter, r *http.Request) {
	fmt.Println("method:", r.Method)
	fmt.Println("url:", r.URL.Path)
	fmt.Println("header:", r.Header)
	fmt.Println("body:", r.Body)

	w.Write([]byte("<h1>hello wielun</h1>"))
}

func main() {
	http.HandleFunc("/go", HandConn)

	// 监听绑定
	http.ListenAndServe(":8080", nil)
}

结果:

method: GET
url: /go
header: map[Accept:[text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3] Accept-Encoding:[gzip, deflate, br] Accept-Language:[zh-CN,zh;q=0.9] Cache-Control:[max-age=0] Connection:[keep-alive] Sec-Fetch-Mode:[navigate] Sec-Fetch-Site:[none] Sec-Fetch-User:[?1] Upgrade-Insecure-Requests:[1] User-Agent:[Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36]]
body: {}

2、http客户端

package main

import (
	"fmt"
	"net/http"
)

func main() {
	resp, err := http.Get("http://www.baidu.com")
	if err != nil {
		fmt.Println("http.Get err = ", err)
		return
	}

	defer resp.Body.Close()

	fmt.Println("Status = ", resp.Status)
	fmt.Println("StatusCode = ", resp.StatusCode)
	fmt.Println("Header = ", resp.Header)
	// fmt.Println("Body = ", resp.Body)

	buf := make([]byte, 4*1024)
	var tmp string

	for {
		n, err := resp.Body.Read(buf)
		if n == 0 {
			fmt.Println("read err = ", err)
			break
		}
		tmp += string(buf[:n])
	}
	fmt.Println("tmp = ", tmp)
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wielun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值