序言
UDP(User Datagram Protocol)协议和TCP一样是传输层的协议,但是UDP比较单纯,仅仅是将数据传送出去,基本上没有什么复杂的过程,不像TCP那样还要建立连接,数据确认等各种骚操作。一般应用于视频通话,UPD一般应用在于数据在传输过程中丢了就丢了,无伤大雅。
首部字段分析
刚才说了UPD的比较单纯,所以首部也是比较简单的。仅仅维护一些必要字段就行,首部只有8个字节
源端口号/目标端口
- 分别占用2个字节16位,范围0~65535,也就是程序中端口号最大65535
长度
-
占用2个字节16位
-
长度是首部长度和数据的长度
-
数据长度最大65535-8
校验和
-
占用2字节16位
-
伪首部+首部+数据
-
伪首部:仅在计算校验的时候起作用,而且不会传给网络层
代码跟踪流程
server.go
package main
import (
"fmt"
"log"
"net"
"strings"
)
func main() {
add, err := net.ResolveUDPAddr("udp", "0.0.0.0:8088")
if err != nil {
log.Fatal(err)
}
// 开启一个端口用来监听
conn, err := net.ListenUDP("udp", add)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
for {
data := make([]byte, 100)
// 这里是一直读取UDP的数据
len, rAddr, err := conn.ReadFromUDP(data)
fmt.Println("客户端", rAddr)
if err != nil {
fmt.Println("读取数据失败", err)
continue
}
strData := string(data[:len])
fmt.Println("收到数据", strData)
upper := strings.ToUpper(strData)
_, errs := conn.WriteToUDP([]byte(upper), rAddr)
if errs != nil {
fmt.Println(errs)
continue
}
fmt.Println("发送", upper)
}
}
client.go
package main
import (
"bufio"
"fmt"
"net"
"os"
"osi/config"
"strconv"
)
func checkError(err error) {
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func main() {
serverAddr := config.SERVER_IP + ":" + strconv.Itoa(config.SERVER_PORT)
// 其实Dial就是本地创建好一个资源,例如开启一个端口号
conn, err := net.Dial("udp", serverAddr)
checkError(err)
defer conn.Close()
input := bufio.NewScanner(os.Stdin)
for input.Scan() {
line := input.Text()
lineLen := len(line)
n := 0
for written := 0; written < lineLen; written += n {
var toWrite string
if lineLen-written > config.SERVER_RECV_LEN {
toWrite = line[written : written+config.SERVER_RECV_LEN]
} else {
toWrite = line[written:]
}
n, err = conn.Write([]byte(toWrite))
checkError(err)
fmt.Println("Write:", toWrite)
msg := make([]byte, config.SERVER_RECV_LEN)
n, err = conn.Read(msg)
checkError(err)
fmt.Println("Response:", string(msg))
}
}
}
config.go
package config
const (
SERVER_IP = "127.0.0.1"
SERVER_PORT = 8088
SERVER_RECV_LEN = 100
)
运行程序
抓包
-
发现客户端运行之后没有建立连接的过程,所以UDP是无连接的。
-
客户端发送数据
代码分析
服务端
-
服务端就是监听本地的一个端口号,创建UDP服务
-
因为没有连接的过程,所以直接读取UDP中的数据,然后会返回一个UDP客户端信息
-
所以UDP就是无脑的获取数据,然后给数据的过程
客户端
-
Dial()其实是创建本地资源,例如开启一个临时端口号。为发送upd数据做的一些准备
-
很多人认为,这样就是和对方UDP建立了一个通信。其实如果没有UDP服务器,仅仅是调用Dial函数也是不会报错的。
-
然后就是本地建立的资源,封装首部,数据部分,然后发送给目标端口。
问题验证
-
服务端,客户端通信完成之后,直接关闭服务端,发现客户端依然不断开,只是发送数据报错
-
如果客户端在服务端再次开启之前,不操作,还是可以用此次Dial的资源发送数据。