Go_TCP服务端实现读写分离

实现简单的TCP通信,客户端定时推送消息,服务端收到客户端消息后进行响应

客户端代码,连接服务端,定时发送消息

func main() {
	conn, err := net.Dial("tcp", "127.0.0.1:8000")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer conn.Close()
	for {
		conn.Write([]byte("client message"))

		buf := make([]byte, 512)
		n, err := conn.Read(buf)
		if err != nil {
			fmt.Println("Read from client failed, err:", err)
			break
		}
		data := string(buf[:n])
		fmt.Println("Receive server: ", data)

		time.Sleep(time.Second)
	}
}

服务端代码,接收客户端连接,响应消息

func main() {
	listen, err := net.Listen("tcp", ":8000")
	if err != nil {
		fmt.Println("Listen failed, err:", err)
		return
	}
	for {
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("Accept failed, err:", err)
			continue
		}
		go Start(conn)
	}
}

func Start(conn net.Conn) {
	defer conn.Close()
	for {
		// read
		buf := make([]byte, 512)
		n, err := conn.Read(buf)
		if err != nil {
			fmt.Println("Read from client failed, err:", err)
			break
		}
		data := string(buf[:n])
		fmt.Println("Receive client: ", data)

		// write
		conn.Write([]byte("ok"))
	}
}

服务端这里处理客户端连接时,读和写是在一个协程中。为了便于在读和写逻辑中添加更多的控制,实现读写分离

  1. 单独启动Reader和Writer协程
  2. 保证回应收到的信息,增加channel维护读和写次序
  3. 出错时读和写可以退出循环,增加连接是否关闭属性
  4. 退出时可以清理资源,增加channel阻塞等待断开连接
type Connection struct {
	conn     net.Conn
	msgChan  chan []byte
	exitChan chan bool
	isClose  bool
}

func Start(conn net.Conn) {
	defer fmt.Println("Client exit")
	defer conn.Close()

	c := &Connection{conn, make(chan []byte), make(chan bool, 1), false}
	go c.StartReader()
	go c.StartWriter()

	select {
	case <-c.exitChan:
	}
}

func (c *Connection) StartReader() {
	fmt.Println("Reader start")
	defer fmt.Println("Reader exit")
	for !c.isClose {
		buf := make([]byte, 512)
		n, err := c.conn.Read(buf)
		if err != nil {
			c.exitChan <- true
			c.isClose = true
			fmt.Println("Read Data error: ", err)
			break
		}

		c.msgChan <- buf[:n]
		data := string(buf[:n])
		fmt.Println("Receive client: ", data)
	}
}

func (c *Connection) StartWriter() {
	fmt.Println("Writer start")
	defer fmt.Println("Writer exit")
	for !c.isClose {
		select {
		case <-c.msgChan:
			if _, err := c.conn.Write([]byte("ok")); err != nil {
				c.exitChan <- true
				c.isClose = true
				fmt.Println("Send Data error: ", err)
				return
			}
		case <-time.After(3 * time.Second):
		}
	}
}

这里需要注意的点有:

  1. 定义Connection结构体
  2. isClose属性控制是否退出循环
  3. Writer增加超时等待
  4. exitChan的长度设置为1,出错时写入数据不会阻塞

参考: zinx-Golang轻量级TCP服务器框架

posted @ 2022-03-30 12:56  柠檬水请加冰  阅读(252)  评论(0编辑  收藏  举报