EchoServer设计到实现(二)

上篇抛出了几个问题,这次主要解决一下:支持读写异步,解决存在接收乱码情况。这里也顺便练习一下chan和select的使用。

问题1:支持读写异步

这个解决方式跟简单,只要把读写分离,分别用两个goroutine启动运行,这里需要注意一个坑—一定要先启动两个goroutine,不再for循环里面启动,这样会带来goroutine数量暴增,以后为你解偶代码就难了
    //客户端代码读写分离
    go func(read chan []byte) {}
    for{
            //发送数据
    }

问题2:解决乱序问题

解决这个有多种方法,毕竟这是一个经典问题!我的做法-设计一个应用层协议解决这个,虽然我们采用TCP连接方式保证了数据包不丢,但是我们要知道,TCP存在一个接受/发送缓冲区,我们每次读取数据都是从这里拿出,缓冲区把多少个数据包放到一起就不用定了。因此我做了一个简单文件头封装,为每个数据包的做了一个标识,在接收时,我们可以参考这个数据表识,来拆除数据包。设计灵感主要参考这里

全部设计协议代码:


import (
    "bytes"
    "encoding/binary"
)

const (
    ConstHeader = "Headers"
    ConstHeaderLength = 7
    ConstLength = 4
)

func Enpack(message []byte)[]byte{
    return append(append([]byte(ConstHeader),IntToBytes(len(message))...),message...)
}

func Depack(buffer []byte,readerChannel chan []byte) []byte{
    length := len(buffer)

    var i int
    for i = 0 ; i < length ; i++{
        if length < i + ConstHeaderLength + ConstLength{
            break
        }
        if string(buffer[i:i+ConstHeaderLength]) == ConstHeader{
            messageLength := BytesToInt(buffer[i+ConstHeaderLength:i+ConstHeaderLength+ConstLength])
            if length < i+ConstHeaderLength+messageLength{
                break
            }
            data := buffer[i+ConstHeaderLength+ConstLength : i+ConstHeaderLength+ConstLength+messageLength]
            readerChannel <- data
        }
    }
    if i == length{
        return make([]byte,0)
    }

    return buffer[i:]
}

func IntToBytes(n int)[]byte{
    x := int32(n)

    bytesBuffer := bytes.NewBuffer([]byte{})
    binary.Write(bytesBuffer,binary.BigEndian,x)
    return bytesBuffer.Bytes()
}

func BytesToInt(b []byte) int{
    bytesBuffer := bytes.NewBuffer(b)

    var x int32
    binary.Read(bytesBuffer,binary.BigEndian,&x)
    return int(x)
}

再原来基础上我又把客户端代码做了一下修改使之支持读写异步,通过时间控制发送和接收频率来模拟乱序问题:

import (
    "fmt"
    "net"
    "os"
    "time"
    "EchoTCP/protocol"
)
func main() {
    conn,err := net.Dial("tcp","127.0.0.1:12345")
    CheckError(err)
    defer conn.Close()
    readerChannal := make(chan []byte,1024)
    go func(read chan []byte){
        for{
            select{
            case data := <- read:
                fmt.Println("client recv : %d = %s", len(data), string(data))
            }
        }
    }(readerChannal)
    go func(read chan []byte){
        tmpBuf := make([]byte,0)
        buf := make([]byte, 1024)
        for{
            time.Sleep(time.Second*100)
            n, err := conn.Read(buf)
            fmt.Println("client recv:",string(buf))
            CheckError(err)
            tmpBuf = protocol.Depack(append(tmpBuf,buf[:n]...),read)
        }
    }(readerChannal)
    for {
        _, err := conn.Write(protocol.Enpack([]byte("Hello world")))
        CheckError(err)
        time.Sleep(1*time.Second)
    }
}
func CheckError(err error){
    if err != nil{
        fmt.Fprintf(os.Stderr, "Error: %s", err.Error())
        os.Exit(1)
    }
}

我在这里顺便把服务端也做了一下修改,使之支持一步读写。也方便大家测试

package main

import (
    "SimpleServer/protocol"
    "fmt"
    "net"
    "os"
    "time"
)

func main() {
    listen, err := net.Listen("tcp", "127.0.0.1:12345")
    //CheckError(err)
    if err != nil {
        fmt.Fprintln(os.Stderr, "Error:", err.Error())
        return
    }
    defer listen.Close()
    for {
        conn, err := listen.Accept()
        if err != nil {
            fmt.Fprintln(os.Stderr, "Error", err.Error())
            continue
        }
        go Handle(conn)
    }
}
func Handle(conn net.Conn) {
    buffer := make([]byte, 1024)
    tmpBuffer := make([]byte, 0)
    readerChannal := make(chan []byte, 1024)
    go func(read chan []byte) {
        for {
            select {
            case data := <-read:
                fmt.Println("recv : %d = %s", len(data), string(data))
            }
        }
    }(readerChannal)
    for {
        time.Sleep(1 * time.Second)
        n, err := conn.Read(buffer)
        if err != nil {
            fmt.Fprintln(os.Stderr, "Error:", err.Error())
        }
        tmpBuffer = protocol.Depack(append(tmpBuffer, buffer[:n]...), readerChannal)
        fmt.Println("server recv : %n = %s", n, string(buffer))
        n, err = conn.Write(buffer[:n])
        if err != nil {
            fmt.Fprintln(os.Stderr, "Error:", err.Error())
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值