udp 和 tcp 的简单比较和用 go 实现最简单的 udp 客户端和服务端 ……
用 go 实现简单的 udp
用户数据包协议(英语:User Datagram Protocol,缩写为UDP),又称用户数据报文协议,是一个简单的面向数据报的传输层协议,正式规范为RFC 768。
在TCP/IP模型中,UDP为网络层以上和应用层以下提供了一个简单的接口。UDP只提供数据的不可靠传递,它一旦把应用程序发给网络层的数据发送出去,就不保留数据备份(所以UDP有时候也被认为是不可靠的数据报协议)。UDP在IP数据报的头部仅仅加入了复用和数据校验(字段)。
UDP 与 TCP 的比较
UDP – 用户数据协议包,是一个简单的面向数据报的运输层协议。UDP 不提供可靠性,它只是把应用程序给 IP 层的数据报发送出去,但是并不能保证他们能达到目的地。由于 UDP 在传输数据报之前不用在客户端和服务端之间建立连接,且没有超时机制,故而传输速度很快。
TCP – 传输控制协议,提供的是面向连接,可靠的字节流服务。当客户端和服务端彼此交换数据前,必须先在双方之间建立 TCP 连接,之后才能传输数据。TCP 提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一段传到另一端。
- | TCP | UDP |
---|
是否连接 | 面向连接 | 面向非连接 |
传输可靠性 | 可靠 | 会丢包,不可靠 |
应用场景 | 传输数据量大 | 传输数据量小 |
速度 | 慢 | 快 |
TCP 与 UDP 的选择
当数据传输的性能必须让位于数据传输的完整性、可控制性和可靠性时,TCP协议是当然的选择。当强调传输性能而不是传输的完整性时,如:音频和多媒体应用,UDP是最好的选择。在数据传输时间很短,以至于此前的连接过程成为整个流量主体的情况下,UDP也是一个好的选择,如:DNS交换。把SNMP建立在UDP上的部分原因是设计者认为当发生网络阻塞时,UDP较低的开销使其有更好的机会去传送管理数据。TCP丰富的功能有时会导致不可预料的性能低下,但是我们相信在不远的将来,TCP可靠的点对点连接将会用于绝大多数的网络应用。
UDP 使用场景
在选择使用协议的时候,选择UDP必须要谨慎。在网络质量令人十分不满意的环境下,UDP协议数据包丢失会比较严重。但是由于UDP的特性:它不属于连接型协议,因而具有资源消耗小,处理速度快的优点,所以通常音频、视频和普通数据在传送时使用UDP较多,因为它们即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。而且如果在内网的情况下,丢包率也很低,所以内网的数据传输也可以用 UDP 协议。我们常用的 QQ,一部分数据传输功能也是用 UDP协议来实现的。
实现
下面分别是服务端和客户端实现代码:
服务端代码 server.go
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| package main
import (
"fmt"
"net"
)
func main() {
// 解析地址
addr, err := net.ResolveUDPAddr("udp", ":3017")
if err != nil {
fmt.Println("Can't resolve addr:", err.Error())
panic(err)
}
// 监听端口
conn, err := net.ListenUDP("udp", addr)
if err != nil {
fmt.Println("listen error:", err.Error())
panic(err)
}
defer conn.Close()
for {
handlerClient(conn)
}
}
func handlerClient(conn *net.UDPConn) {
data := make([]byte, 1024)
// 从 UDP 中读取内容并写到 data
_, remoteAddr, err := conn.ReadFromUDP(data)
if err != nil {
fmt.Println("read udp msg failed with:", err.Error())
return
}
// 给收到消息的 client 写回信息
conn.WriteToUDP([]byte("a"), remoteAddr)
}
|
客户端代码client.go
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
| package client
import (
"fmt"
"net"
)
var (
// Connection *net.UDPConn
Connection []*net.UDPConn
)
// Client 创建一个 UDP 连接
func Client() {
addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:3017")
if err != nil {
fmt.Println("Can't resolve address: ", err)
panic(err)
}
conn, err := net.DialUDP("udp", nil, addr)
if err != nil {
fmt.Println("Can't dial: ", err)
panic(err)
}
Connection = append(Connection, conn)
}
// WriteTo 像传入参数 conn 写数据
func WriteTo(conn *net.UDPConn) {
_, err := conn.Write([]byte("hello from the other site"))
if err != nil {
fmt.Println("failed:", err)
}
data := make([]byte, 1024)
_, err = conn.Read(data)
if err != nil {
fmt.Println("failed to read UDP msg because of ", err)
}
}
|
总结
以上是一个最简单的 UDP 客户端服务器的代码,只有启动服务和收发消息的功能,但实际应用 UDP 协议到具体需求的时候,需要考虑的问题很多,比如包的设计,包头的设计,错误处理,丢包处理,包顺序调换处理等。所以需要用到传输数据协议的时候,请考虑好需求和可能遇到的问题,以及对问题的处理方案。