上篇抛出了几个问题,这次主要解决一下:支持读写异步,解决存在接收乱码情况。这里也顺便练习一下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())
}
}
}