Go的net库提供TCP/UDP网络编程核心功能,通过net.Listen、net.Dial、net.Conn和net.PacketConn实现;其优势在于goroutine并发模型、简洁API、强制错误处理和高性能;实践中需注意资源管理、超时设置、错误处理、并发安全及TLS加密,避免常见陷阱。

Go语言的
net
net
使用Go的
net
net.Listen
net.Dial
net.Conn
net.PacketConn
TCP 服务器端
构建一个TCP服务器通常涉及监听端口、接受连接和处理数据流。
立即学习“go语言免费学习笔记(深入)”;
package main
import (
"fmt"
"net"
"io"
"time"
)
func handleConnection(conn net.Conn) {
defer conn.Close() // 确保连接关闭
fmt.Printf("新连接来自: %s\n", conn.RemoteAddr().String())
// 设置读取超时,防止长时间阻塞
conn.SetReadDeadline(time.Now().Add(5 * time.Second))
buffer := make([]byte, 1024)
for {
n, err := conn.Read(buffer)
if err != nil {
if err == io.EOF {
fmt.Println("客户端关闭连接")
} else if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
fmt.Println("读取超时,关闭连接")
} else {
fmt.Printf("读取错误: %v\n", err)
}
break
}
message := string(buffer[:n])
fmt.Printf("收到消息: %s\n", message)
// 回复客户端
_, err = conn.Write([]byte("服务器已收到: " + message + "\n"))
if err != nil {
fmt.Printf("写入错误: %v\n", err)
break
}
}
}
func main() {
listener, err := net.Listen("tcp", ":8080")
if err != nil {
fmt.Printf("监听失败: %v\n", err)
return
}
defer listener.Close() // 确保监听器关闭
fmt.Println("TCP服务器正在监听 :8080")
for {
conn, err := listener.Accept()
if err != nil {
fmt.Printf("接受连接失败: %v\n", err)
continue
}
go handleConnection(conn) // 为每个连接启动一个goroutine
}
}TCP 客户端
TCP客户端则需要拨号连接到服务器,然后进行读写操作。
package main
import (
"fmt"
"net"
"time"
"io"
)
func main() {
conn, err := net.Dial("tcp", "localhost:8080")
if err != nil {
fmt.Printf("连接服务器失败: %v\n", err)
return
}
defer conn.Close()
fmt.Println("已连接到服务器")
messages := []string{"Hello Server!", "How are you?", "Goodbye!"}
for _, msg := range messages {
// 发送消息
_, err := conn.Write([]byte(msg + "\n"))
if err != nil {
fmt.Printf("发送消息失败: %v\n", err)
return
}
fmt.Printf("发送: %s\n", msg)
// 接收服务器回复
buffer := make([]byte, 1024)
conn.SetReadDeadline(time.Now().Add(2 * time.Second)) // 设置读取超时
n, err := conn.Read(buffer)
if err != nil {
if err == io.EOF {
fmt.Println("服务器关闭连接")
} else if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
fmt.Println("读取服务器回复超时")
} else {
fmt.Printf("读取回复失败: %v\n", err)
}
return
}
fmt.Printf("收到回复: %s", string(buffer[:n]))
time.Sleep(1 * time.Second) // 模拟间隔
}
}UDP 服务器端
UDP服务器使用
net.ListenPacket
ReadFrom
WriteTo
package main
import (
"fmt"
"net"
"time"
)
func main() {
conn, err := net.ListenPacket("udp", ":8081")
if err != nil {
fmt.Printf("监听UDP失败: %v\n", err)
return
}
defer conn.Close()
fmt.Println("UDP服务器正在监听 :8081")
buffer := make([]byte, 1024)
for {
conn.SetReadDeadline(time.Now().Add(10 * time.Second)) // 设置读取超时
n, addr, err := conn.ReadFrom(buffer)
if err != nil {
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
fmt.Println("UDP读取超时,继续等待...")
continue
}
fmt.Printf("读取UDP数据失败: %v\n", err)
break
}
message := string(buffer[:n])
fmt.Printf("收到来自 %s 的消息: %s\n", addr.String(), message)
// 回复客户端
response := []byte("服务器已收到UDP: " + message)
_, err = conn.WriteTo(response, addr)
if err != nil {
fmt.Printf("回复UDP数据失败: %v\n", err)
}
}
}UDP 客户端
UDP客户端可以直接向目标地址发送数据报,并从服务器接收回复。
package main
import (
"fmt"
"net"
"time"
)
func main() {
serverAddr, err := net.ResolveUDPAddr("udp", "localhost:8081")
if err != nil {
fmt.Printf("解析服务器地址失败: %v\n", err)
return
}
// UDP客户端通常不需要Dial,直接用ListenPacket来收发,或者用DialUDP来建立一个“连接”
// 这里我们用DialUDP来简化收发,它会绑定一个本地端口,并设置远程地址
conn, err := net.DialUDP("udp", nil, serverAddr)
if err != nil {
fmt.Printf("连接UDP服务器失败: %v\n", err)
return
}
defer conn.Close()
fmt.Println("UDP客户端已启动")
message := "Hello UDP Server!"
_, err = conn.Write([]byte(message))
if err != nil {
fmt.Printf("发送UDP消息失败: %v\n", err)
return
}
fmt.Printf("发送: %s\n", message)
// 接收服务器回复
buffer := make([]byte, 1024)
conn.SetReadDeadline(time.Now().Add(2 * time.Second)) // 设置读取超时
n, _, err := conn.ReadFromUDP(buffer) // ReadFromUDP会返回发送者的地址,这里我们不关心
if err != nil {
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
fmt.Println("读取UDP回复超时")
} else {
fmt.Printf("读取UDP回复失败: %v\n", err)
}
return
}
fmt.Printf("收到回复: %s\n", string(buffer[:n]))
}在我看来,Go在网络编程方面简直是如鱼得水。它最显著的优势,也是我个人最欣赏的一点,就是其原生的并发模型——goroutine和channel。你可以非常轻松地为每一个新进来的网络连接启动一个轻量级的goroutine去处理,而不用担心传统线程模型带来的巨大开销和复杂性。这种“写同步代码,跑并发逻辑”的体验,让网络服务的开发效率提升了一大截。
另外,
net
选择TCP还是UDP,这真的是一个老生常谈的问题,但在实际的Go应用中,它关乎到你服务的核心特性。简单来说,TCP(
net.Conn
TCP的典型应用场景:
在Go中实现TCP,你拿到的是一个
net.Conn
而UDP(
net.PacketConn
UDP的典型应用场景:
在Go中实现UDP,你通常会使用
net.ListenPacket
net.DialUDP
我的经验是,很多初学者在不确定的时候会倾向于选择TCP,因为它“可靠”。但如果你在构建一个对延迟极度敏感、且能容忍少量数据丢失的系统(比如一个游戏服务器),盲目使用TCP可能会引入不必要的开销,导致性能瓶颈。反之,如果你的应用对数据完整性有严格要求,比如金融交易系统,那么TCP的可靠性是不可替代的。选择的关键在于你对“可靠性”和“实时性”的需求权衡。
在用Go的
net
1. 资源管理与defer
net.Listener
net.Conn
defer listener.Close()
defer conn.Close()
defer conn.Close()
2. 超时(Timeouts)是你的救星: 网络环境复杂多变,连接可能会断开、对端可能无响应。如果你的读写操作没有设置超时,一旦网络出现问题,goroutine就可能无限期地阻塞在那里,消耗内存和CPU。
conn.SetReadDeadline()
conn.SetWriteDeadline()
3. 错误处理的严谨性: Go语言的错误处理模式鼓励你显式地检查每一个错误。在网络编程中,这尤为重要。不要仅仅打印错误就完事,要根据错误类型采取不同的恢复策略。例如,
io.EOF
net.Error
4. 并发安全: 虽然goroutine让并发变得简单,但共享状态下的并发安全问题依然存在。如果你在多个goroutine中访问或修改同一个
net.Conn
sync.Mutex
sync.RWMutex
5. 缓冲区管理: 当你使用
conn.Read(buffer)
n
buffer
bufio.Reader
6. 安全性考量:TLS/SSL: 如果你的应用需要在公共网络上安全传输数据,仅仅使用TCP是不够的。你需要引入TLS/SSL加密。Go的
crypto/tls
net
tls.Listen()
tls.Dial()
遵循这些实践,虽然可能增加一些代码量,但能让你的Go网络应用更加稳定、高效和安全。
以上就是Golang net库TCP/UDP网络编程基础的详细内容,更多请关注php中文网其它相关文章!
编程怎么学习?编程怎么入门?编程在哪学?编程怎么学才快?不用担心,这里为大家提供了编程速学教程(入门课程),有需要的小伙伴保存下载就能学习啦!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号