文章目录
前言
本文介绍的不是http.net包,仅仅介绍传统的网络通信(后期会单独进行http.net包的更新)
一、互联网的层次结构
大致分为四层,细分的话可以分为7层
1.应用层
应用层、表示层、会话层
2.传输层
传输层
3.网络层
网络层
4.网络接口层
数据链路层、 物理层
5.图解
其中socket层是为我们准备的,我们可以在这一层进行一些功能的实现达到用户的需求
二、tcp协议概述
1.三次握手
第一次握手
客户端发送一个TCP的SYN标志位置1的包指明客户打算连接的服务器的端口, 以及初始序号X,保存在包头的序列号(Sequence Number)字段里。
第二次握手
服务器发回确认包(ACK)应答。即SYN标志位和ACK标志位均为1同 时,将确认序号(Acknowledgement Number)设置为客户的I S N加1以.即X+1。
第三次握手
客户端再次发送确认包(ACK) SYN标志位为0,ACK标志位为1.并且把服 务器发来ACK的序号字段+1,放在确定字段中发送给对方.并且在数据段放写ISN的+1
2.tcp通常用来做什么?
- 由于tcp通信数据包传输精确可以用来传输文件图片,等重要的数据
- tcp传输准确率高但效率低,不易传数据量大重要程度不高的数据
3.代码实现tcp通信
客户端代码如下:
package main import ( "bufio" "fmt" "net" "os" "strings" "time" ) // tcp/client/main.go // 接收数据 func getMsg(conn net.Conn) { for { buf := [512]byte{} // n为获取到的数据长度 n, err := conn.Read(buf[:]) if err != nil { fmt.Println("recv failed, err:", err) return } fmt.Println(string(buf[:n])) } } // 发送数据 func sendMsg(conn net.Conn) { for { // 创建一个缓冲区阅读器 inputReader := bufio.NewReader(os.Stdin) // 接收用户输入,以\n为终结符 input, _ := inputReader.ReadString('\n') // 读取用户输入 // 去掉获取到的\n inputInfo := strings.Trim(input, "\r\n") // 判断是否满足退出条件 if strings.ToUpper(inputInfo) == "Q" { // 对服务器发送退出请求 _, _ = conn.Write([]byte("q")) return } // 给服务端写入数据(数据在两端之间以字节的形式传输) _, err := conn.Write([]byte(inputInfo)) if err != nil { return } } } // 客户端 func main() { // 使用tcp的方式去连接127.0.0.1:20000 conn, err := net.Dial("tcp", "127.0.0.1:20000") if err != nil { fmt.Println("err :", err) return } // 在函数执行到末端的时候进行关闭连接 defer conn.Close() go getMsg(conn) go sendMsg(conn) time.Sleep(time.Hour) }
服务端代码如下:
package main import ( "bufio" "fmt" "net" "os" "strings" ) // TCP server端 func sendMsg(conn net.Conn) { for { inputer := bufio.NewReader(os.Stdin) input, _ := inputer.ReadString('\n') input = strings.Trim(input, "\r\n") conn.Write([]byte(input)) } } // 接收处理函数 func process(conn net.Conn) { fmt.Println("线路:", conn, "连接成功!") defer conn.Close() // 关闭连接 for { reader := bufio.NewReader(conn) var buf [128]byte n, err := reader.Read(buf[:]) // 读取数据 if err != nil { fmt.Println("read from client failed, err:", err) break } recvStr := string(buf[:n]) if recvStr == "q" { fmt.Println(conn, "断开了连接!") return } fmt.Println(conn, ":", recvStr) fmt.Printf("请输入:") } } func main() { // 绑定网关的127.0.0.1:20000,以tcp的形式进行数据传输 listen, err := net.Listen("tcp", "127.0.0.1:20000") 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 process(conn) go sendMsg(conn) } }
三、udp协议概述
1.udp通常用来做什么?
- udp传输效率高,但容易丢失数据
- 可用来进行网络直播,视频通话
- 保留重要的信息,对可有可无的信息尽力保存
2.代码实现
客户端代码如下:
package main import ( "bufio" "fmt" "net" "os" ) // UDP client func main() { socket, err := net.DialUDP("udp", nil, &net.UDPAddr{ IP: net.IPv4(127, 0, 0, 1), Port: 40000, }) 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("redv reply msg failed,err:", err) return } fmt.Println("收到回复信息:", string(reply[:n])) } }
服务端代码如下:
package main import ( "fmt" "net" "strings" ) // UDP server func main() { // 建立UDP服务端,不需要使用accept方法 conn, err := net.ListenUDP("udp", &net.UDPAddr{ IP: net.IPv4(127, 0, 0, 1), Port: 40000, }) 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(data[:n]) reply := strings.ToUpper(string(data[:n])) // 发送数据 conn.WriteToUDP([]byte(reply), addr) } }
四、粘包问题【及解决方法】
1.为什么会粘包?
由于频繁的进行数据的收发,底层为了提高收发效率,在发送消息前会检查一下是否还有需要发送的其他数据
如果发送过于频繁,上一次数据包将与夏下一次的数据报粘在一起,导致数据非常乱
由Nagle算法造成的发送端的粘包:Nagle算法是一种改善网络传输效率的算法。简单来说就是当我们提交一段数据给TCP发送时,TCP不立刻发送此段数据,而是等待一小段时间看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去
/* 在服务端接收客户端发送来的20次Tomhello并打印 在粘包下的打印效果(各个信息有可能会直接相连) hello Tomhello Tomhello Tomhello Tomhello Tomhello Tomhello Tomhello Tomhello Tomhello Tomhello Tomhello Tomhello Tomhello Tomhello Tomhello Tomhello Tom -------------- hello Tomhello Tomhello Tom -------------- 正常的打印结果为: -------------- hello Tomhello -------------- hello Tomhello -------------- hello Tomhello -------------- hello Tomhello -------------- hello Tomhello -------------- hello Tomhello -------------- hello Tomhello -------------- hello Tomhello -------------- ...... */
2.解决方案
- 将每一个数据包大小进行保存,数据包写一表头用于存储数据包的大小,每次进行数据的读取时先
- 进行数据包大小的读取,在进行数据信息实体的读取
- 降低发送数据的频率
3.编码解码函数
编码解码函数代码如下:
package edcode import ( "bufio" "bytes" "encoding/binary" ) /* 小端低低:低地址存低位 大端高低:高地址存低位 对需要发送的信息进行编码解码(打包处理) 其中包内的前四个字节用于存储包的大小,后面用于存储信息的主要内容 */ // 解码 // 取出bytes前四个字节,转化为int32类型,然后再将剩余的消息体取出 func Mydecode(reader *bufio.Reader) (string, error) { Size, _ := reader.Peek(4) lenthbuff := bytes.NewBuffer(Size) var lenth int32 // 读取lenthbuff内的内容,读取完将数据放在lenth内,读取的方式是小端方式 err := binary.Read(lenthbuff, binary.LittleEndian, &lenth) // 判断一下是否出错,长度不够包头指定的长度就抛异常 if err != nil || int32(reader.Buffered()) < lenth+4 { return "", err } // pack := make([]byte, int(lenth+4)) // read读取之后缓冲区就少一部分数据 _, err = reader.Read(pack) if err != nil { return "", err } return string(pack[4:]), nil } // 编码 // 先计算出字符串的长度,再进行打包 func MyEncode(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 }
客户端代码如下:
package main import ( "fmt" "net" ed "aCorePackage/edcode" ) func main() { conn, err := net.Dial("tcp", "127.0.0.1:20000") if err != nil { fmt.Println(err) } // 快速发送信息还是会黏在一起,只不过通过一定的编码解码方式可以使信息很好的分离 for i := 0; i < 20; i++ { str := "hello Tom" mybtys, err := ed.MyEncode(str) if err != nil { fmt.Println(err) } conn.Write(mybtys) fmt.Println("-------------------") } }
服务端代码如下:
package main import ( ed "aCorePackage/edcode" "bufio" "fmt" "io" "net" ) func readMsd(conn net.Conn) { defer conn.Close() reader := bufio.NewReader(conn) for { str, err := ed.Mydecode(reader) if err == io.EOF { return } if err != nil { fmt.Println(err) return } fmt.Println(str) fmt.Println("--------------") } } func main() { listener, err := net.Listen("tcp", "127.0.0.1:20000") if err != nil { fmt.Println(err) } for { conn, err := listener.Accept() if err != nil { fmt.Println(err) return } go readMsd(conn) } }
总结
TCP 需要连接,UDP 是无连接的,发送数据之前不需要建立连接, TCP 提供可靠的服务,通过 TCP 连接传送的数据,无差错,不丢失 TCP 逻辑通信信道是全双工的可靠信道,UDP 则是不可靠信道,两者各有所长,对他们进行各取所需。