网络编程
-
TCP通信
服务器:
package main import ( "fmt" "net" ) // tcp server端 func processConn(conn net.Conn) { // 3.与客户端通信,通过循环一直接受连接 for { var tmp [128]byte // 参数为切片 n, err := conn.Read(tmp[:]) if err != nil { fmt.Println("read from conn failed,err:", err) return } // 切片转换为字符串,不然读出来的是一串ASCLL码 fmt.Println(string(tmp[:n])) } } func main() { // 1.本地端口启动服务 listener, err := net.Listen("tcp", "127.0.0.1:20000") if err != nil { fmt.Println("start tcp on 127.0.0.1:20000 failed,err:", err) return } // 2.等待客户端建立连接 for { conn, err := listener.Accept() if err != nil { fmt.Println("accept failed,err", err) return } go processConn(conn) } }客户端:
package main import ( "bufio" "fmt" "net" "os" "strings" ) // tcp client func main() { // 1.与server端建立连接 conn, err := net.Dial("tcp", "127.0.0.1:20000") if err != nil { fmt.Println("dial 127.0.0.1:20000 failed,err:", err) return } // 2.发送数据 reader := bufio.NewReader(os.Stdin) var msg string for { fmt.Print("请输入内容:") msg, _ = reader.ReadString('\n') //读到换行 msg = strings.TrimSpace(msg) if msg == "exit" { break } conn.Write([]byte(msg)) } // 通过命令行输入参数 // if len(os.Args) < 2 { // msg = "hello world" // } else { // msg = os.Args[1] // } // conn.Write([]byte(msg)) conn.Close() } -
TCP黏包
TCP数据传输模式是流模式,保持长时间的连接和多次的收发数据,但这也导致会出现“黏包” 的情况,在客户端和服务器都有可能发生。
发送端的黏包:由于Nagle算法为了改善网络传输的效率,TCP收到收到数据后不会立即发送,而是会等待一段时间看是否还有需要发送的数据,再一起发送。
接收端的黏包:TCP接受到数据不会立即读取,而是会把数据放在缓冲区中,再通知应用层取数据。而如果应用取出数据不及时就会导致缓冲区放了好几段数据。
package main // TCP黏包服务器端 import ( "bufio" "fmt" "io" "net" ) func process(conn net.Conn) { defer conn.Close() reader := bufio.NewReader(conn) var buf [1024]byte for { n, err := reader.Read(buf[:]) if err == io.EOF { break } if err != nil { fmt.Println("read from client failed, err:", err) break } recvStr := string(buf[:n]) fmt.Println("收到client发来的数据:", recvStr) } } func main() { listen, err := net.Listen("tcp", "127.0.0.1:30000") if err != nil { fmt.Println("listen failed, err:", err) return } defer listen.Close() for { conn, err := listen.Accept() if err != nil { fmt.Println("accept failed, err:", err) continue } go process(conn) } }package main import ( "fmt" "net" "time" ) // TCP黏包客户端 // socket_stick/client/main.go func main() { conn, err := net.Dial("tcp", "127.0.0.1:30000") if err != nil { fmt.Println("dial failed, err", err) return } defer conn.Close() for i := 0; i < 20; i++ { msg := `Hello, Hello. How are you?` conn.Write([]byte(msg)) time.Sleep(time.Second) } }解决方法就是自定义协议,为数据加上包头,确认每一次发送的数据的长度。接受方收到数据先查看长度,然后接受对应长度的数据。
package protocol import ( "bufio" "bytes" "encoding/binary" ) // Encode 将消息编码 func Encode(message string) ([]byte, error) { // 读取消息的长度,转换成int32类型(占4个字节) var length = int32(len(message)) var pkg = new(bytes.Buffer) // 写入消息头 err := binary.Write(pkg, binary.LittleEndian, length) if err != nil { return nil, err } // 写入消息实体 err = binary.Write(pkg, binary.LittleEndian, []byte(message)) if err != nil { return nil, err } return pkg.Bytes(), nil } // Decode 解码消息 func Decode(reader *bufio.Reader) (string, error) { // 读取消息的长度 lengthByte, _ := reader.Peek(4) // 读取前4个字节的数据 lengthBuff := bytes.NewBuffer(lengthByte) var length int32 err := binary.Read(lengthBuff, binary.LittleEndian, &length) if err != nil { return "", err } // Buffered返回缓冲中现有的可读取的字节数。 if int32(reader.Buffered()) < length+4 { return "", err } // 读取真正的消息数据 pack := make([]byte, int(4+length)) _, err = reader.Read(pack) if err != nil { return "", err } return string(pack[4:]), nil }package main import ( "fmt" "net" "github.com/studygo/tcpsticky_solve/protocol" ) // TCP解决黏包的客户端 // socket_stick/client/main.go func main() { conn, err := net.Dial("tcp", "127.0.0.1:30000") if err != nil { fmt.Println("dial failed, err", err) return } defer conn.Close() for i := 0; i < 20; i++ { msg := `Hello, Hello. How are you?` b, err := protocol.Encode(msg) if err != nil { fmt.Println("Encode failed,err:", err) return } conn.Write(b) } }package main // TCP解决黏包服务器端 import ( "bufio" "fmt" "io" "net" "github.com/studygo/tcpsticky_solve/protocol" ) func process(conn net.Conn) { defer conn.Close() reader := bufio.NewReader(conn) for { // 按照协议中定义的长度去取数据 recvStr, err := protocol.Decode(reader) if err == io.EOF { return } if err != nil { fmt.Println("Decode failed,err:", err) return } fmt.Println("收到client发来的数据:", recvStr) } } func main() { listen, err := net.Listen("tcp", "127.0.0.1:30000") if err != nil { fmt.Println("listen failed, err:", err) return } defer listen.Close() for { conn, err := listen.Accept() if err != nil { fmt.Println("accept failed, err:", err) continue } go process(conn) } } -
UDO服务端
使用UDP通信不需要建立连接,直接向目标服务器和端口发送数据和监听接受数据就行。
package main import ( "bufio" "fmt" "net" "os" ) // UDP客户端 func main() { socket, err := net.DialUDP("udp", nil, &net.UDPAddr{ IP: net.IPv4(127, 0, 0, 1), Port: 30000, }) if err != nil { fmt.Println("连接服务端失败,err:", err) return } defer socket.Close() // 从标准输入中获取输入 var reply [1024]byte reader := bufio.NewReader(os.Stdin) for { fmt.Print("请输入内容:") msg, _ := reader.ReadString('\n') socket.Write([]byte(msg)) // 收回复的数据,忽略接受到的地址信息 n, _, err := socket.ReadFromUDP(reply[:]) if err != nil { fmt.Println("read reply msg failed,err:", err) return } fmt.Println("收到回复信息:", string(reply[:n])) } }package main import ( "fmt" "net" "strings" ) // UDP server func main() { // 第二个参数为监听的ip地址和端口号 conn, err := net.ListenUDP("udp", &net.UDPAddr{ IP: net.IPv4(127, 0, 0, 1), Port: 30000, }) if err != nil { fmt.Println("listen UDP failed,err:", err) return } defer conn.Close() // 不需要建立连接,直接收发数据 var data [1024]byte for { n, addr, err := conn.ReadFromUDP(data[:]) if err != nil { fmt.Println("read from UDP failed,err:", err) return } fmt.Println(string(data[:n])) reply := strings.ToUpper(string(data[:n])) // 发送数据 conn.WriteToUDP([]byte(reply), addr) } }
本文介绍了Go语言中的网络编程,包括TCP通信中的黏包问题及其原因:发送端的Nagle算法和接收端的缓冲区机制。解决黏包的方法是自定义协议,通过包头指示数据长度。此外,还提及了UDP服务端的简单使用,它无需建立连接即可进行数据交换。
317

被折叠的 条评论
为什么被折叠?



