Go网络编程小tips

请添加图片描述

1读写操作

1.1 write和read注意事项

在这里插入图片描述

1.2 并发读写

并发读写只两方面:
1.读操作和写操作是并发执行的
2.可能出现多个goroutine同时读或写
3.因此在go中,要使用goroutine完成,同一个连接的并发读或写操作是goroutine并发安全的。指的是同时存在多个goroutine并发的读写,之间是不会相互影响的,这个在实操中,主要针对write操作,conn.write()增加了锁操作

  1. 读操作和写操作并发执行
  2. 可能出现多个goroutine同时读或写

服务端

//并发的读和写操作 全双工
func TcpServerRWConcurrency() {
	address := ":5678" //any ip or version
	//A.基于某个地址进行监听
	listen, err := net.Listen(tcp, address) //端口省略 随机端口 address := "127.0.0.1:"
	if err != nil {
		log.Fatalln(err)
	}
	//关闭监听
	defer listen.Close()
	log.Printf("%s server is listening on %s\n", tcp, listen.Addr())
	//B.接受连接请求
	//循环接受
	for {
		//阻塞接受
		conn, err := listen.Accept()
		if err != nil {
			log.Println(err)
		}
		go HandleConnConcurrency(conn)
	}
}

func HandleConnConcurrency(conn net.Conn) {
	//处理连接,读写
	//日志连接的远程地址(client addr)
	log.Printf("aceept from %s\n", conn.RemoteAddr())
	//A.关闭连接
	defer conn.Close()
	wg := sync.WaitGroup{}
	//并发写
	wg.Add(1)
	go SerWrite(conn, &wg, "123")
	//多并发写
	wg.Add(1)
	go SerWrite(conn, &wg, "456")
	wg.Add(1)
	go SerWrite(conn, &wg, "789")
	//并发读
	wg.Add(1)
	go SerRead(conn, &wg)
	wg.Wait()
}
func SerWrite(conn net.Conn, wg *sync.WaitGroup, data string) {
	defer wg.Done()
	for {
		//向客户端发送数据 write
		wn, err := conn.Write([]byte(data + "send some data from server " + "\n"))
		if err != nil {
			log.Println(err)
		}
		log.Println("向客户端发送的长度为:", wn)
		time.Sleep(200 * time.Millisecond)
	}

}
func SerRead(conn net.Conn, wg *sync.WaitGroup) {
	defer wg.Done()
	for {
		//从客户端读取数据 read
		buf := make([]byte, 1024)
		rn, err := conn.Read(buf)
		if err != nil {
			log.Println(err)
		}
		log.Println("receive from client data are: ", string(buf[:rn]))
	}

}

客户端

func TcpClientRWConcurrency() {
	address := "127.0.0.1:5678"
	//A.建立连接
	conn, err := net.DialTimeout(tcp, address, time.Second)
	if err != nil {
		log.Println(err)
		return
	}
	//保证关闭
	defer conn.Close()
	log.Printf("connection is establish,client addr is %s\n", conn.LocalAddr())
	wg := sync.WaitGroup{}
	//并发写
	wg.Add(1)
	go CliWrite(conn, &wg)
	//并发读
	wg.Add(1)
	go CliRead(conn, &wg)
	wg.Wait()
}

func CliWrite(conn net.Conn, wg *sync.WaitGroup) {
	defer wg.Done()
	for {
		//向客户端发送数据 write
		wn, err := conn.Write([]byte("send some data from client " + "\n"))
		if err != nil {
			log.Println(err)
		}
		log.Println("向客户端发送的长度为:", wn)
		time.Sleep(500 * time.Millisecond)
	}
}
func CliRead(conn net.Conn, wg *sync.WaitGroup) {
	defer wg.Done()
	for {
		//从客户端读取数据 read
		buf := make([]byte, 1024)
		rn, err := conn.Read(buf)
		if err != nil {
			log.Println(err)
		}
		log.Println("receive from server data are: ", string(buf[:rn]))
	}
}

  • 并发读写安全,指的是数据完整,顺序并不保证
    在这里插入图片描述

2 格式化消息

2:06 sc
在这里插入图片描述

7 20 sc
在这里插入图片描述
服务端

//格式化消息
func TcpServerFormat() {
	address := ":5678" //any ip or version
	//A.基于某个地址进行监听
	listen, err := net.Listen(tcp, address) //端口省略 随机端口 address := "127.0.0.1:"
	if err != nil {
		log.Fatalln(err)
	}
	//关闭监听
	defer listen.Close()
	log.Printf("%s server is listening on %s\n", tcp, listen.Addr())
	//B.接受连接请求
	//循环接受
	for {
		//阻塞接受
		conn, err := listen.Accept()
		if err != nil {
			log.Println(err)
		}
		go HandleConnFormat(conn)
	}
}
func HandleConnFormat(conn net.Conn) {
	//处理连接,读写
	//日志连接的远程地址(client addr)
	log.Printf("aceept from %s\n", conn.RemoteAddr())
	//A.关闭连接
	defer conn.Close()
	wg := sync.WaitGroup{}
	//并发写
	wg.Add(1)
	go SerWriteFormat(conn, &wg)
	wg.Wait()
}
func SerWriteFormat(conn net.Conn, wg *sync.WaitGroup) {
	defer wg.Done()
	for {
		//发送端
		//发送数据前进行编码
		//创建需要传递的数据
		type Message struct {
			Id      uint   `json:"id,omitempty"`
			Code    string `json:"code,omitempty"`
			Content string `json:"content,omitempty"`
		}
		message := Message{
			Id:      uint(rand.Int()),
			Code:    "SERVER-STARDEND ",
			Content: "message form server",
		}
		//编码后数据展示
		var buf bytes.Buffer
		encoder := json.NewEncoder(&buf)
		if err := encoder.Encode(message); err != nil {
			log.Println(err)
			continue
		}
		log.Println(buf.String())
		/*
			//1 JSON 文本编码
			//创建编码器,向编码器写信息
			encoder := json.NewEncoder(conn)
			//利用编码器进行编码
			//encode成功后,会写入至conn,已经完成了conn.Write()
			if err := encoder.Encode(message); err != nil {
				log.Println(err)
				continue
			}
		*/
		log.Println("MESSAGE WAS SEND!!")
		//2 GOB 二进制编码
		//encoder := gob.NewEncoder() 其他不变
		time.Sleep(time.Second)
	}

}

客户端

//格式化消息
func TcpClientFormat() {
	address := "127.0.0.1:5678"
	//A.建立连接
	conn, err := net.DialTimeout(tcp, address, time.Second)
	if err != nil {
		log.Println(err)
		return
	}
	//保证关闭
	defer conn.Close()
	log.Printf("connection is establish,client addr is %s\n", conn.LocalAddr())
	wg := sync.WaitGroup{}
	//并发读
	wg.Add(1)
	go CliReadFormat(conn, &wg)
	wg.Wait()
}
func CliReadFormat(conn net.Conn, wg *sync.WaitGroup) {
	defer wg.Done()
	for {
		//从服务端读取数据 read
		type Message struct {
			Id      uint   `json:"id,omitempty"`
			Code    string `json:"code,omitempty"`
			Content string `json:"content,omitempty"`
		}
		message := Message{}
		//接受数据后解码
		//1 JSON 解码
		//创建解码器
		decoder := json.NewDecoder(conn)
		//利用解码器进行解码
		//解码操作,从conn中读取内容,成功后会将解码后结果,赋值到 message 变量中
		if err := decoder.Decode(&message); err != nil {
			log.Println(err)
			continue
		}
		log.Println(message)
		//2 GOB 解码
		//decoder := gob.NewDecoder(conn). 其他不变
	}
}

3 短连接 长连接

在这里插入图片描述

  • 短链接示意图
    在这里插入图片描述
  • 长连接示意图
    在这里插入图片描述

3.1 短链接

在这里插入图片描述

  • 服务端

//短连接
func TcpServerShort() {
	address := ":5678" //any ip or version
	//A.基于某个地址进行监听
	listen, err := net.Listen(tcp, address) //端口省略 随机端口 address := "127.0.0.1:"
	if err != nil {
		log.Fatalln(err)
	}
	//关闭监听
	defer listen.Close()
	log.Printf("%s server is listening on %s\n", tcp, listen.Addr())
	//B.接受连接请求
	//循环接受
	for {
		//阻塞接受
		conn, err := listen.Accept()
		if err != nil {
			log.Println(err)
		}
		go HandleConnShort(conn)
	}
}
func HandleConnShort(conn net.Conn) {
	//处理连接,读写
	//日志连接的远程地址(client addr)
	log.Printf("aceept from %s\n", conn.RemoteAddr())
	//A.关闭连接
	defer conn.Close()
	wg := sync.WaitGroup{}
	//并发写
	wg.Add(1)
	go SerWriteShort(conn, &wg)
	wg.Wait()
}
func SerWriteShort(conn net.Conn, wg *sync.WaitGroup) {
	defer wg.Done()
	//发送端
	type Message struct {
		Id      uint   `json:"id,omitempty"`
		Code    string `json:"code,omitempty"`
		Content string `json:"content,omitempty"`
	}
	message := Message{
		Id:      uint(rand.Int()),
		Code:    "SERVER-STARDEND ",
		Content: "message form server",
	}
	encoder := gob.NewEncoder(conn)
	//Encode成功后,会写入到conn,已经完成了conn.Write()
	if err := encoder.Encode(message); err != nil {
		log.Println(err)
	}
	log.Println("message was send")
	log.Println("link will be close")
	return
}
  • 客户端
//短连接
func TcpClientShort() {
	address := "127.0.0.1:5678"
	//A.建立连接
	conn, err := net.DialTimeout(tcp, address, time.Second)
	if err != nil {
		log.Println(err)
		return
	}
	//保证关闭
	defer conn.Close()
	log.Printf("connection is establish,client addr is %s\n", conn.LocalAddr())
	wg := sync.WaitGroup{}
	//并发读
	wg.Add(1)
	go CliReadShort(conn, &wg)
	wg.Wait()
}
func CliReadShort(conn net.Conn, wg *sync.WaitGroup) {
	defer wg.Done()
	type Message struct {
		Id      uint   `json:"id,omitempty"`
		Code    string `json:"code,omitempty"`
		Content string `json:"content,omitempty"`
	}
	message := Message{}
	for {
		//从服务端读取数据 read
		//2 GOB 解码
		decoder := gob.NewDecoder(conn)
		//利用解码器进行解码
		//解码操作,从conn中读取内容,成功后会将解码后结果,赋值到 message 变量中
		err := decoder.Decode(&message)
		//错误为 io.EOF时,表示连接被给关闭
		if err != nil && errors.Is(err, io.EOF) {
			log.Println(err)
			log.Println("link was closed!")
			break
		}
		log.Println(message)
	}
}

3.2 长连接的心跳检测请添加图片描述

请添加图片描述
请添加图片描述
开启服务 多开几个客户端,关闭其中某些客户端
服务器端检测时,会主动断开连接。

  • 服务端
func TcpServerHB() {
	address := ":5678" //any ip or version
	//A.基于某个地址进行监听
	listen, err := net.Listen(tcp, address) //端口省略 随机端口 address := "127.0.0.1:"
	if err != nil {
		log.Fatalln(err)
	}
	//关闭监听
	defer listen.Close()
	log.Printf("%s server is listening on %s\n", tcp, listen.Addr())
	//B.接受连接请求
	//循环接受
	for {
		//阻塞接受
		conn, err := listen.Accept()
		if err != nil {
			log.Println(err)
		}
		go HandleConnHB(conn)
	}
}
func HandleConnHB(conn net.Conn) {
	//处理连接,读写
	//日志连接的远程地址(client addr)
	log.Printf("aceept from %s\n", conn.RemoteAddr())
	//A.关闭连接
	defer func() {
		log.Println("current connection be closed!")
		conn.Close()
	}()
	wg := sync.WaitGroup{}
	//独立goroutine,在连接建立后,周期发送ping
	//发送ping
	wg.Add(1)
	go SerPing(conn, &wg)
	wg.Wait()
}
func SerPing(conn net.Conn, wg *sync.WaitGroup) {
	defer wg.Done()
	//启动接收 pong  为什么放在这里面?是因为ping停止的时候 pong也应该停止
	ctx, cancel := context.WithCancel(context.Background())
	go SerReadPong(conn, ctx)
	//ping失败次数
	const maxPingNum = 3
	pingErrCounter := 0
	//周期性的发送
	//利用 time.Ticker
	ticker := time.NewTicker(2 * time.Second)
	for t := range ticker.C {
		pingMsg := MessageHB{
			Id:   uint(rand.Int()),
			Code: "PING-SERVER",
			Time: t,
		}
		encoder := gob.NewEncoder(conn)
		//Encode成功后,会写入到conn,已经完成了conn.Write()
		if err := encoder.Encode(pingMsg); err != nil {
			log.Println(err)
			//连接有问题的情况
			//累加错误计数器
			pingErrCounter++
			//判断是否达到上限
			if pingErrCounter == maxPingNum {
				//心跳失败
				//终止接收 pong 的处理
				cancel()
				return
			}
		}
		log.Printf("ping send to %s,ping id is %d ", conn.RemoteAddr(), pingMsg.Id)
	}
}
func SerReadPong(conn net.Conn, ctx context.Context) {
	for {
		//处理ping结束
		select {
		case <-ctx.Done():
			return
		default:
			message := MessageHB{}
			decoder := gob.NewDecoder(conn)
			err := decoder.Decode(&message)
			//错误为 io.EOF时,表示连接被给关闭
			if err != nil && errors.Is(err, io.EOF) {
				log.Println(err)
				break
			}
			//判断是否为pong类型
			if message.Code == "PONG-CLIENT" {
				log.Printf("receive ping from %s,ping id is %s", conn.RemoteAddr(), message.Content)
			}
		}
	}
}
  • 客户端
type MessageHB struct {
	Id      uint      `json:"id,omitempty"`
	Code    string    `json:"code,omitempty"`
	Content string    `json:"content,omitempty"`
	Time    time.Time `json:"time,omitempty"`
}
func TcpClientHB() {
	address := "127.0.0.1:5678"
	//A.建立连接
	conn, err := net.DialTimeout(tcp, address, time.Second)
	if err != nil {
		log.Println(err)
		return
	}
	//保证关闭
	defer conn.Close()
	log.Printf("connection is establish,client addr is %s\n", conn.LocalAddr())

	wg := sync.WaitGroup{}
	//并发读
	wg.Add(1)
	go CliReadPing(conn, &wg)
	wg.Wait()
}
func CliReadPing(conn net.Conn, wg *sync.WaitGroup) {
	defer wg.Done()
	message := MessageHB{}
	for {
		decoder := gob.NewDecoder(conn)
		err := decoder.Decode(&message)
		//错误为 io.EOF时,表示连接被给关闭
		if err != nil && errors.Is(err, io.EOF) {
			log.Println(err)
			break
		}
		//判断是否为ping类型
		if message.Code == "PING-SERVER" {
			log.Println("receive ping from", conn.RemoteAddr())
			CliWritePong(conn, message)
		}
	}
}
func CliWritePong(conn net.Conn, pingMsg MessageHB) {
	pongMsg := MessageHB{
		Id:      uint(rand.Int()),
		Code:    "PONG-CLIENT",
		Content: fmt.Sprintf("pingID :%d", pingMsg.Id),
		Time:    time.Now(),
	}
	encoder := gob.NewEncoder(conn)
	//Encode成功后,会写入到conn,已经完成了conn.Write()
	if err := encoder.Encode(pongMsg); err != nil {
		log.Println(err)
		return
	}
	log.Println("pong was send to ", conn.RemoteAddr())
	return
}

4 连接池

请添加图片描述

请添加图片描述

4.1 核心结构

//连接池接口
type Pool interface {
	//获取连接
	Get() (net.Conn, error) //interface{}==any
	//放回连接
	Put(net.Conn) error
	//释放池,关闭连接
	Release() error
	//有效连接长度
	Len() int
}

//连接工厂接口
type ConnFactory interface {
	//生产连接
	Factory() (net.Conn, error)
	//关闭连接
	Close(net.Conn) error
	//Ping
	Ping(net.Conn) error
}

//连接池配置
type PoolConfig struct {
	//最小连接数,至少保持多少个有效连接
	MinConnNum int
	//最大连接数,池中最多支持多少连接
	MaxConnNum int
	//最大空闲连接数,池中最多有多少可用的连接
	MaxIdleNum int
	//空闲连接超时时间,多久后空闲连接会被释放
	IdleTimeOut time.Duration

	//连接工厂
	Factory ConnFactory
}

//空闲连接类型(管理的连接)
type IdleConn struct {
	//连接本身
	conn net.Conn
	//放入池子的时间,用于判断是否空闲超时
	putTime time.Time
}

//连接池结构
type TcpPool struct {
	//配置信息
	config PoolConfig

	//运行时信息
	//使用连接数量
	openingConnNum int
	//空闲连接链表
	idleList chan *IdleConn

	//并发安全锁
	mu sync.RWMutex
}

4.2 生产工厂的实现

//TCP连接工厂类型
type TcpConnFactory struct{}
//产生连接方法
func (*TcpConnFactory) Factory(addr string) (net.Conn, error) {
	//校验参数合理性
	if addr == "" {
		return nil, errors.New("addr is empty!")
	}
	//建立连接
	conn, err := net.DialTimeout("tcp", addr, time.Second*5)
	if err != nil {
		return nil, err
	}
	//return
	return conn, nil
}
//关闭连接
func (*TcpConnFactory) Close(conn net.Conn) error {
	return conn.Close()
}
func (*TcpConnFactory) Ping(conn net.Conn) error {
	return nil
}

4.3 完善连接池基本结构

//连接池结构实现连接池(Pool)接口
func (*TcpPool) Get() (net.Conn, error) {
	return nil, nil
}
func (*TcpPool) Put(conn net.Conn) error {
	return nil
}
func (*TcpPool) Release() error {
	return nil
}
func (*TcpPool) Len() int {
	return 0
}

4.4 创建连接池函数

请添加图片描述

//创建TcpPool对象
func NewTcpPool(addr string, poolConfig PoolConfig) (*TcpPool, error) {
	//1.校验参数
	if addr == "" {
		return nil, errors.New("The addr is empty!")
	}
	//校验工厂存在
	if poolConfig.Factory == nil {
		return nil, errors.New("factory is not exists!")
	}
	//最大连接数
	if poolConfig.MaxConnNum == 0 {
		//a return错误
		//return nil, errors.New("max conn num is zeor!")
		//b 人为修改一个合理的值
		poolConfig.MaxConnNum = defaultMaxConnNum
	}
	//初始化连接数
	if poolConfig.InitConnNum == 0 {
		poolConfig.InitConnNum = defaultInitConnNum
	} else if poolConfig.InitConnNum > poolConfig.MaxConnNum {
		poolConfig.InitConnNum = poolConfig.MaxConnNum
	}
	//合理化最大空闲连接数
	if poolConfig.MaxIdleNum == 0 {
		poolConfig.MaxIdleNum = poolConfig.InitConnNum
	} else if poolConfig.MaxIdleNum > poolConfig.MaxConnNum {
		poolConfig.MaxIdleNum = poolConfig.MaxConnNum
	}
	//2.初始化TcpPool对象
	pool := TcpPool{
		config:         poolConfig,
		openingConnNum: 0,
		idleList:       make(chan *IdleConn, poolConfig.MaxIdleNum),
		mu:             sync.RWMutex{},
	}
	//3.初始化连接
	//根据initConnNum配置来创建
	for i := 0; i < poolConfig.InitConnNum; i++ {
		conn, err := pool.config.Factory.Factory(addr)
		if err != nil {
			//通常意味着连接初始化失败
			//释放可能已经存在的连接
			pool.Release()
			return nil, err
		}
		//连接创建成功
		//加入到空闲连接队列中
		pool.idleList <- &IdleConn{
			conn:    conn,
			putTime: time.Now(),
		}
	}
	return &pool, nil
}

4.5 从连接池中获取连接

请添加图片描述

func (pool *TcpPool) Get() (net.Conn, error) {
	//1.锁定
	pool.mu.Lock()
	defer pool.mu.Unlock()
	//2.获取空闲连接,若没有则创建链接
	for {
		select {
		//2.1获取空闲连接
		case idleConn, ok := <-pool.idleList:
			//判断channel是否被关闭
			if !ok {
				return nil, errors.New("idle list is closed!")
			}
			//判断连接是否超时
			if pool.config.IdleTimeOut > 0 { //设置了超时时间
				//putime + IdleTimeOut 是否在 now 之前
				if idleConn.putTime.Add(pool.config.IdleTimeOut).Before(time.Now()) {
					//关闭连接,继续查找下一个连接
					_ = pool.config.Factory.Close(idleConn.conn)
					log.Println("空闲连接超时!")
					continue
				}
			}
			//判断连接是否可用
			if err := pool.config.Factory.Ping(idleConn.conn); err != nil {
				//ping失败,连接不可用
				//关闭连接,继续查找
				_ = pool.config.Factory.Close(idleConn.conn)
				continue
			}
			//找到可用空闲连接
			log.Println("get conn from idle")
			//使用的连接计数
			pool.openingConnNum++
			//返回连接
			return idleConn.conn, nil
		//2.2创建连接
		default:
			//a.判断是否还可以继续创建
			//基于开放的连接是否已经达到了连接池最大的连接数
			if pool.openingConnNum >= pool.config.MaxConnNum {
				return nil, errors.New("max opening connection")
				//另一种方案 阻塞 意思:只要有连接被放回就可以执行第一个case,再此之前 一直default中continue循环
				//continue
			}
			//b.创建连接
			conn, err := pool.config.Factory.Factory(pool.addr)
			if err != nil {
				return nil, err
			}
			//c正确创建了连接
			log.Println("get conn from factory")
			//使用连接计数
			pool.openingConnNum++
			//返回连接
			return conn, nil
		}
	}
}

4.6 将连接放回连接池

请添加图片描述

func (pool *TcpPool) Put(conn net.Conn) error {
	//1.锁
	pool.mu.Lock()
	defer pool.mu.Unlock()
	//2.校验
	if conn == nil {
		return errors.New("connection is not exist!")
	}
	//判断空闲连接列表是否存在
	if pool.idleList == nil {
		//关闭连接
		_ = pool.config.Factory.Close(conn)
		return errors.New("idlelist is not exist!")
	}
	//3.放回连接
	select {
	//放回连接
	case pool.idleList <- &IdleConn{
		conn:    conn,
		putTime: time.Now(),
	}:
		//只要可以发送成功,任务完成
		//更新开放的连接数量
		pool.openingConnNum--
		//fmt.Println("运行时连接数量", pool.openingConnNum, "len(pool.idleList)", len(pool.idleList))
		return nil
	//关闭连接
	default:
		_ = pool.config.Factory.Close(conn)
		return nil
	}
}

4.7 释放连接池

请添加图片描述

//释放连接池
func (pool *TcpPool) Release() error {
	//1.并发锁
	pool.mu.Lock()
	defer pool.mu.Unlock()
	//2.确定连接池是否被释放
	if pool.idleList == nil {
		return nil
	}
	//3.关闭Idlelist
	close(pool.idleList)
	//4.释放全部空闲连接
	//继续接收已关闭channel中的元素
	for idleConn := range pool.idleList {
		//关闭连接
		_ = pool.config.Factory.Close(idleConn.conn)
	}
	log.Println("已经释放所有连接!")
	return nil
}

4.8 总结

请添加图片描述

5 TCP黏包

请添加图片描述
请添加图片描述
请添加图片描述

5.1 Header方案的粘包解决实现

请添加图片描述

  • 定义编码器解码器
//定义编码器(发送端)
type Encoder struct {
	//编码结束后,写入目标
	w io.Writer
}

//创建编码器函数
func NewEncoder(w io.Writer) *Encoder {
	return &Encoder{
		w: w,
	}
}

//编码,将编码的结果 写入 w io.write
//binary(int32(13))[]byte("package data.")
//定义编码器
func (enc Encoder) Encode(message string) error {
	//1.获取message的长度
	l := int32(len(message))
	//构建一个数据包缓存
	buf := new(bytes.Buffer)
	//2.在数据包中写入长度
	//需要二进制的写入操作,需要将数据以bit的形式写入
	if err := binary.Write(buf, binary.LittleEndian, l); err != nil {
		return err
	}
	//3.将数据主体body写入
	//if err := binary.Write(buf, binary.LittleEndian, []byte(message)); err != nil {
	//	return err
	//}
	if _, err := buf.Write([]byte(message)); err != nil {
		return err
	}
	//4.利用io.Write发送数据 从 buf.Bytes()写入底层的数据流
	if _, err := enc.w.Write(buf.Bytes()); err != nil {
		return err
	}
	return nil
}

//定义解码器(接收端)
type Decoder struct {
	r io.Reader
}

//创建decoder
func NewDecoder(r io.Reader) *Decoder {
	return &Decoder{
		r: r,
	}
}

//从Reader中读取内容,解码
//binary(int32(13))[]byte("package data.")
func (dec *Decoder) Decode(message *string) error {
	//1.读取前四个字节,读取heder
	header := make([]byte, 4)
	hn, err := dec.r.Read(header)
	if err != nil {
		return err
	}
	if hn != 4 {
		return errors.New("header is not enough!")
	}
	//2.将前四个字节转化为int32类型,确定了body长度
	var l int32
	//headerBuf := bytes.NewBuffer(header) //为什么要转化为buffer?因为binary.Read()第一个参数需要 io.Reader类型
	newReader := bytes.NewReader(header)
	if err := binary.Read(newReader, binary.LittleEndian, &l); err != nil {
		return err
	}
	//3.读取body
	body := make([]byte, l)
	bn, err := dec.r.Read(body)
	if err != nil {
		return err
	}
	if bn != int(l) {
		return errors.New("body is not enough")
	}
	//4.设置message
	*message = string(body)
	return nil
}

  • 客户端
func TcpClientCoder() {
	address := "127.0.0.1:5678"
	//A.建立连接
	conn, err := net.DialTimeout(tcp, address, time.Second)
	if err != nil {
		log.Println(err)
		return
	}
	//保证关闭
	defer conn.Close()
	log.Printf("connection is establish,client addr is %s\n", conn.LocalAddr())
	//B.从服务端接受数据
	for {
		decoder := NewDecoder(conn)
		var msg string
		err := decoder.Decode(&msg)
		if err != nil {
			log.Println(err)
			break
		}
		log.Println("receive data: ", msg)
	}
}
  • 服务端
func TcpServerCoder() {
	address := ":5678" //any ip or version
	//A.基于某个地址进行监听
	listen, err := net.Listen(tcp, address) //端口省略 随机端口 address := "127.0.0.1:"
	if err != nil {
		log.Fatalln(err)
	}
	//关闭监听
	defer listen.Close()
	log.Printf("%s server is listening on %s\n", tcp, listen.Addr())
	//B.接受连接请求
	//循环接受
	for {
		//阻塞接受
		conn, err := listen.Accept()
		if err != nil {
			log.Println(err)
		}
		go HandleConnCoder(conn)
	}
}
func HandleConnCoder(conn net.Conn) {
	//处理连接,读写
	//日志连接的远程地址(client addr)
	log.Printf("aceept from %s\n", conn.RemoteAddr())
	//A.关闭连接
	defer func() {
		log.Println("current connection be closed!")
		conn.Close()
	}()
	// 连续发送数据
	//data := "package data."
	data := []string{
		"package data.",
		"package.",
		"package data data",
		"pack",
	}
	for i := 0; i < 50; i++ {
		//创建编码器
		encoder := NewEncoder(conn)
		if err := encoder.Encode(data[rand.Intn(len(data))]); err != nil {
			log.Println(err)
		}
	}
}

6 Tcp专用方法

请添加图片描述

  • 服务端
//TCP特定方法
func TcpServerSpecial() {
	//1. 建立监听
	// 获取本地地址(监听地址)
	laddr, err := net.ResolveTCPAddr("tcp", ":5678")
	if err != nil {
		log.Fatalln(err)
	}
	tcpListener, err := net.ListenTCP("tcp", laddr)
	if err != nil {
		log.Fatalln(err)
	}
	defer tcpListener.Close()
	//2. 接收连接
	for {
		tcpConn, err := tcpListener.AcceptTCP()
		if err != nil {
			log.Println(err)
			continue
		}
		//3. 处理连接
		go handleConnSpecial(tcpConn)
	}
}
func handleConnSpecial(conn *net.TCPConn) {
	//defer conn.Close()
	log.Printf("accept from %s\n", conn.RemoteAddr())
	//设置连接属性
	conn.SetKeepAlive(true)
	//写数据
	data := "message data."
	wn, err := conn.Write([]byte(data))
	if err != nil {
		log.Println(err)
		return
	}
	log.Println("data len is ", wn)
}

  • 客户端
//TCP特定方法
func TcpClientSpecial() {
	//1.建立连接
	raddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:5678")
	if err != nil {
		log.Fatalln(err)
	}
	tcpCoon, err := net.DialTCP("tcp", nil, raddr) //第二个参数 localaddr一般为nil
	if err != nil {
		log.Fatalln(err)
	}
	//关闭连接
	defer tcpCoon.Close()
	log.Println("connection is  establish,client addr is ", tcpCoon.LocalAddr())
	//2.读数据
	buf := make([]byte, 1024)
	for {
		readn, err := tcpCoon.Read(buf)
		if err != nil {
			log.Println(err)
			return
		}
		fmt.Println("读取内容为:", string(buf[:readn]), "长度为:", readn)
	}
}

6.1 tcp连接属性设置

请添加图片描述
请添加图片描述

请添加图片描述
请添加图片描述

7 UDP程序设计

7.1 编写udp客户端服务端

请添加图片描述
请添加图片描述

  • 服务端
func UDPServerBasic() {
	//1.解析地址
	udpAddr, err := net.ResolveUDPAddr("udp", ":9876")
	if err != nil {
		log.Fatalln(err)
	}
	//2.监听地址
	udpConn, err := net.ListenUDP("udp", udpAddr)
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Printf("%s server is listening on %s\n", "udp", udpConn.LocalAddr().String())
	defer udpConn.Close()
	//3.读
	buf := make([]byte, 1024)
	rn, raddr, err := udpConn.ReadFromUDP(buf)
	if err != nil {
		log.Fatalln(err)
	}
	log.Printf("receive %s from %s", string(buf[:rn]), raddr.String())
	//4.写
	data := []byte("received:" + string(buf[:rn]))
	wn, err := udpConn.WriteToUDP(data, raddr) //没有建立连接所以用 WriteToUDP,使用这个是因为无目标地址
	if err != nil {
		log.Fatalln(err)
	}
	log.Printf("send %s(%d) to %s ", string(data), wn, raddr.String())
}

  • 客户端
func UDPClientBasic() {
	//1.建立连接
	raddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:9876")
	if err != nil {
		log.Fatalln(raddr)
	}
	udpConn, err := net.DialUDP("udp", nil, raddr)
	if err != nil {
		log.Fatalln(err)
	}
	//2.写
	data := []byte("Go UDP program")
	wn, err := udpConn.Write(data) //14行已经建立好连接 所以用write(方法)
	if err != nil {
		log.Fatalln(err)
	}
	log.Printf("send %s(%d) to %s ", string(data), wn, raddr.String())
	//3.读
	buf := make([]byte, 1024)
	rn, raddr, err := udpConn.ReadFromUDP(buf)
	if err != nil {
		log.Fatalln(err)
	}
	log.Printf("receive %s from %s", string(buf[:rn]), raddr.String())
}

7.2 已连接和未连接的udp连接

请添加图片描述

  • 读操作有兼容性,但还是推荐按规则使用
udpConn, err := net.ListenUDP("udp", udpAddr)
wn, err := udpConn.WriteToUDP(data, raddr) //没有建立连接所以用 WriteToUDP,使用这个是因为无目标地址
以上不会报错
但使用udpConn.Write()会报错

udpConn, err := net.DialUDP("udp", nil, raddr)
wn, err := udpConn.Write(data) //14行已经建立好连接 所以用write(方法)
以上不会报错
但是用udpConn.WriteToUDP()会报错

请添加图片描述

请添加图片描述

7.3 对等的服务端和客户端

请添加图片描述

  • 服务端
//udp服务端Peer
func UDPServerPeer() {
	//1.解析地址
	udpAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:9876") //需要拿到确切地址
	if err != nil {
		log.Fatalln(err)
	}
	//2.监听地址
	udpConn, err := net.ListenUDP("udp", udpAddr)
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Printf("%s server is listening on %s\n", "udp", udpConn.LocalAddr().String())
	defer udpConn.Close()
	//远程地址
	raddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:6789")
	if err != nil {
		log.Fatalln(err)
	}
	//3.读
	buf := make([]byte, 1024)
	rn, raddr, err := udpConn.ReadFromUDP(buf)
	if err != nil {
		log.Fatalln(err)
	}
	log.Printf("receive %s from %s", string(buf[:rn]), raddr.String())
	//4.写
	data := []byte("received:" + string(buf[:rn]))
	wn, err := udpConn.WriteToUDP(data, raddr) //没有建立连接所以用 WriteToUDP,使用这个是因为无目标地址
	if err != nil {
		log.Fatalln(err)
	}
	log.Printf("send %s(%d) to %s ", string(data), wn, raddr.String())
}
  • 客户端
//udp客户端Peer
func UDPClientPeer() {
	//1.解析地址
	udpAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:6789") //需要拿到确切地址,不能和服务端地址一致
	if err != nil {
		log.Fatalln(err)
	}
	//2.监听地址
	udpConn, err := net.ListenUDP("udp", udpAddr)
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Printf("%s server is listening on %s\n", "udp", udpConn.LocalAddr().String())
	defer udpConn.Close()
	//远程地址
	raddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:9876")
	if err != nil {
		log.Fatalln(err)
	}
	//2.写
	data := []byte("Go UDP program")
	wn, err := udpConn.WriteToUDP(data, raddr)
	if err != nil {
		log.Fatalln(err)
	}
	log.Printf("send %s(%d) to %s ", string(data), wn, raddr.String())
	//3.读
	buf := make([]byte, 1024)
	rn, raddr, err := udpConn.ReadFromUDP(buf)
	if err != nil {
		log.Fatalln(err)
	}
	log.Printf("receive %s from %s", string(buf[:rn]), raddr.String())
}

7.4 多播编程

请添加图片描述
-接收端

//多播接收端
func UDPReceiverMulticast() {
	//1.多播监听地址
	address := "224.1.1.2:6789"
	gAddr, err := net.ResolveUDPAddr("udp", address)
	if err != nil {
		log.Fatalln(err)
	}
	//2.多播监听
	udpConn, err := net.ListenMulticastUDP("udp", nil, gAddr)
	if err != nil {
		log.Fatalln(err)
	}
	//3. 接收数据
	//4. 循环接收
	buf := make([]byte, 1024)
	for {
		rn, addr, err := udpConn.ReadFromUDP(buf)
		if err != nil {
			log.Println(err)
		}
		log.Printf("received \"%s\" from %s\n", string(buf[:rn]), addr.String())
	}
}
  • 发送端
//多播发送端
func UDPSenderMulticast() {
	//1.建立UDP多播组链接
	address := "224.1.1.2:6789"
	rAddr, err := net.ResolveUDPAddr("udp", address)
	if err != nil {
		log.Fatalln(err)
	}
	udpConn, err := net.DialUDP("udp", nil, rAddr)
	if err != nil {
		log.Fatalln(err)
	}
	//2.发送内容
	//循环发送
	for {
		data := fmt.Sprintf("[%s]:%s", time.Now().Format("03:04:05.000"), "hello")
		wn, err := udpConn.Write([]byte(data))
		if err != nil {
			fmt.Println(err)
		}
		log.Printf("send \"%s\"(%d) to %s\n", string(data), wn, rAddr.String())
		time.Sleep(time.Second)
	}
}

7.5 广播编程

请添加图片描述

  • 服务端
//广播接收端
func UDPReceiverBroadcast() {
	//1.广播监听地址
	laddr, err := net.ResolveUDPAddr("udp", ":6789")
	if err != nil {
		log.Fatalln(err)
	}
	//2.广播监听
	udpConn, err := net.ListenUDP("udp", laddr)
	if err != nil {
		log.Fatalln(err)
	}
	//3.接收数据
	//4.处理数据
	//4. 循环接收
	buf := make([]byte, 1024)
	for {
		rn, addr, err := udpConn.ReadFromUDP(buf)
		if err != nil {
			log.Println(err)
		}
		log.Printf("received \"%s\" from %s\n", string(buf[:rn]), addr.String())
	}
}

  • 客户端

//广播发送端
func UDPSenderBroadcast() {
	//1.监听地址
	//2.建立连接
	laddr, err := net.ResolveUDPAddr("udp", ":9876")
	if err != nil {
		log.Fatalln(err)
	}
	udpConn, err := net.ListenUDP("udp", laddr)
	if err != nil {
		log.Fatalln(err)
	}
	//3.发送数据
	rAddress := "127.0.1.255:6789" //192.168.50.255
	raddr, err := net.ResolveUDPAddr("udp", rAddress)
	if err != nil {
		log.Fatalln(err)
	}
	//循环发送
	for {
		data := fmt.Sprintf("[%s]:%s", time.Now().Format("03:04:05.000"), "hello")
		wn, err := udpConn.WriteToUDP([]byte(data), raddr)
		if err != nil {
			fmt.Println(err)
		}
		log.Printf("send \"%s\"(%d) to %s\n", string(data), wn, raddr.String())
		time.Sleep(time.Second)
	}
}

7.6文件传输

请添加图片描述

请添加图片描述

  • 接收端
//UDP文件传输
func UDPFileServer() {
	//1.建立udp连接
	lAddress := ":5678"
	laddr, err := net.ResolveUDPAddr("udp", lAddress)
	if err != nil {
		log.Fatalln(err)
	}
	udpConn, err := net.ListenUDP("udp", laddr)
	if err != nil {
		log.Fatalln(udpConn)
	}
	defer udpConn.Close()
	fmt.Printf("%s server is listening on %s\n", "udp", udpConn.LocalAddr().String())
	//2.接收文件名并确认
	buf := make([]byte, 4*1024)
	rn, raddr, err := udpConn.ReadFromUDP(buf)
	if err != nil {
		log.Fatalln(err)
	}
	fileName := string(buf[:rn])
	if _, err := udpConn.WriteToUDP([]byte("filename ok"), raddr); err != nil {
		log.Fatalln(err)
	}
	//3.接收文件内容,并写入文件
	//打开(创建)文件
	file, err := os.Create(fileName)
	if err != nil {
		log.Fatalln(err)
	}
	defer file.Close()
	//网络读取
	i := 0
	for {
		//一次读取
		rn, _, err := udpConn.ReadFromUDP(buf)
		if err != nil {
			log.Fatalln(err)
		}
		if _, err = file.Write(buf[:rn]); err != nil {
			log.Fatalln(err)
		}
		i++
		log.Println("file write some content!:", i)
	}
	fmt.Println("接收完毕")
}
  • 发送端
//文件传输(上传)
func UDPFileClient() {
	//1.获取文件信息
	filename := "./data/无畏.mp3"
	//打开文件
	file, err := os.Open(filename)
	if err != nil {
		log.Fatalln(err)
	}
	//关闭文件
	defer file.Close()
	//获取文件信息
	fileInfo, err := file.Stat()
	if err != nil {
		log.Fatalln(err)
	}
	log.Println("send file size is ", fileInfo.Size())
	//2.连接服务器
	raddress := "127.0.0.1:5678" //192.168.50.131
	raddr, err := net.ResolveUDPAddr("udp", raddress)
	if err != nil {
		log.Fatalln(err)
	}
	udpConn, err := net.DialUDP("udp", nil, raddr)
	if err != nil {
		log.Fatalln(err)
	}
	//defer udpConn.Close()
	//3.发送文件名
	if _, err := udpConn.Write([]byte(fileInfo.Name())); err != nil {
		log.Fatalln(err)
	}
	//4.服务端确认
	buf := make([]byte, 1024*4)
	rn, err := udpConn.Read(buf)
	if err != nil {
		log.Fatalln(err)
	}
	//判断是否为文件名正确接收响应
	if "filename ok" != string(buf[:rn]) {
		log.Fatalln("接收文件名不对!")
	}
	//5.发送文件内容
	//读取文件内容,利用连接发送到服务端
	i := 0
	for {
		//读取文件内容
		rn, err := file.Read(buf)
		if err != nil {
			if err == io.EOF { // io.EOF错误表示文件读取完毕
				break
			}
			log.Fatalln(err)
		}
		//发送到服务端
		if _, err := udpConn.Write(buf[:rn]); err != nil {
			log.Fatalln(err)
		}
		i++
	}
	log.Println(i)
	//文件发送完成
	log.Println("file send complete.")
	//等待的测试
}

8 网络轮询器

请添加图片描述
请添加图片描述

8.1阻塞io模型

请添加图片描述

请添加图片描述

  • 网络io
//网络IO(系统调用syscall的IO)阻塞
func BIONet() {
	addr := "127.0.0.1:5678"
	wg := sync.WaitGroup{}
	//1.模拟读,体会读的阻塞状态
	wg.Add(1)
	go func(wg *sync.WaitGroup) {
		defer wg.Done()
		conn, _ := net.Dial("tcp", addr)
		defer conn.Close()
		buf := make([]byte, 1024)
		//注意:两次时间的间隔
		log.Println("start read.: ", time.Now().Format("03:04:05.000"))
		n, _ := conn.Read(buf)
		log.Println("content", string(buf[:n]), time.Now().Format("03:04:05.000"))
	}(&wg)
	//2.模拟写
	wg.Add(1)
	go func(wg *sync.WaitGroup) {
		defer wg.Done()
		listen, _ := net.Listen("tcp", addr)
		defer listen.Close()
		for {
			conn, _ := listen.Accept()
			go func(conn net.Conn) {
				defer conn.Close()
				log.Println("connceted!")
				//阻塞时常
				time.Sleep(3 * time.Second)
				conn.Write([]byte("BLOCKING I/O"))
			}(conn)
		}
	}(&wg)
	wg.Wait()
}
  • channel阻塞
//Channel(Go自管理的IO)的阻塞
func BIOChannel() {
	// 0初始化数据
	wg := sync.WaitGroup{}
	// IO channel
	ch := make(chan struct{})
	//1.模拟读,体会读的阻塞状态
	wg.Add(1)
	go func(wg *sync.WaitGroup) {
		defer wg.Done()
		//注意:两次时间的间隔
		log.Println("start read.: ", time.Now().Format("03:04:05.000"))
		content := <-ch //IO read receive
		log.Println("content", content, time.Now().Format("03:04:05.000"))
	}(&wg)
	//2.模拟写
	wg.Add(1)
	go func(wg *sync.WaitGroup) {
		defer wg.Done()
		//阻塞时长
		time.Sleep(time.Second)
		ch <- struct{}{} //Write send
	}(&wg)
	wg.Wait()
}

8.2 非阻塞

请添加图片描述
请添加图片描述
请添加图片描述

  • conn
//网络IO(系统调用syscall的IO)非阻塞
func NIONet() {
	addr := "127.0.0.1:5678"
	wg := sync.WaitGroup{}
	//1.模拟读,体会读的阻塞状态
	wg.Add(1)
	go func(wg *sync.WaitGroup) {
		defer wg.Done()
		conn, _ := net.Dial("tcp", addr)
		defer conn.Close()
		buf := make([]byte, 1024)
		//注意:两次时间的间隔
		log.Println("start read.: ", time.Now().Format("03:04:05.000"))
		//设置截至时间
		conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
		n, _ := conn.Read(buf)
		log.Println("content", string(buf[:n]), time.Now().Format("03:04:05.000"))
	}(&wg)
	//2.模拟写
	wg.Add(1)
	go func(wg *sync.WaitGroup) {
		defer wg.Done()
		listen, _ := net.Listen("tcp", addr)
		defer listen.Close()
		for {
			conn, _ := listen.Accept()
			go func(conn net.Conn) {
				defer conn.Close()
				log.Println("connceted!")
				//阻塞时常
				time.Sleep(3 * time.Second)
				conn.Write([]byte("BLOCKING I/O"))
			}(conn)
		}
	}(&wg)
	wg.Wait()
}
  • channel
//Channel(Go自管理的IO)的非阻塞
func NIOChannel() {
	// 0初始化数据
	wg := sync.WaitGroup{}
	// IO channel
	ch := make(chan struct{ id int })
	//1.模拟读,体会读的阻塞状态
	wg.Add(1)
	go func(wg *sync.WaitGroup) {
		defer wg.Done()
		//注意:两次时间的间隔
		log.Println("start read.: ", time.Now().Format("03:04:05.000"))
		content := struct {
			id int
		}{}
		for {
			time.Sleep(time.Millisecond * 500)
			select {
			case s := <-ch:
				log.Println("content", s.id, time.Now().Format("03:04:05.000"))
			default:
				log.Println("no content", content, time.Now().Format("03:04:05.000"))
			}
		}

	}(&wg)
	//2.模拟写
	wg.Add(1)
	go func(wg *sync.WaitGroup) {
		defer wg.Done()
		//阻塞时长
		time.Sleep(time.Second)
		ch <- struct{ id int }{id: 42} //Write send
	}(&wg)
	wg.Wait()
}
  • channel conn
//网络IO(系统调用syscall的IO)非阻塞
func NIONetChannel() {
	addr := "127.0.0.1:5678"
	wg := sync.WaitGroup{}
	//1.模拟读,体会读的阻塞状态
	wg.Add(1)
	go func(wg *sync.WaitGroup) {
		defer wg.Done()
		conn, _ := net.Dial("tcp", addr)
		defer conn.Close()
		//注意:两次时间的间隔
		log.Println("start read.: ", time.Now().Format("03:04:05.000"))
		//** 独立goroutine完成Read操作,将结果send到channel中
		wgwg := sync.WaitGroup{}
		chRead := make(chan []byte)
		buf := make([]byte, 1024)
		wgwg.Add(1)
		go func() {
			defer wgwg.Done()
			n, _ := conn.Read(buf)
			chRead <- buf[:n]
		}()
		time.Sleep(time.Millisecond * 100) //是为了让上面的协程执行 将conn读到的数据放入至chRead管道
		//** select+default实现非阻塞操作
		data := []byte{}
		select {
		case data = <-chRead:
		default:
		}
		log.Println("content:", string(data), time.Now().Format("03:04:05.000"))
		wgwg.Wait()
	}(&wg)
	//2.模拟写
	wg.Add(1)
	go func(wg *sync.WaitGroup) {
		defer wg.Done()
		listen, _ := net.Listen("tcp", addr)
		defer listen.Close()
		conn, _ := listen.Accept()
		go func(conn net.Conn) {
			defer conn.Close()
			log.Println("connceted!")
			//阻塞时常
			//time.Sleep(3 * time.Second)
			conn.Write([]byte("BLOCKING I/O"))
		}(conn)
	}(&wg)
	wg.Wait()
}

8.3 信号驱动和异步io模型

请添加图片描述

请添加图片描述

8.4 多路复用io模型

请添加图片描述
请添加图片描述

8.5 网络轮询器

请添加图片描述

请添加图片描述

8.6 网络轮询器 初始化操作

9

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值