最简单的多线程 tcp 服务器
func handleConn(conn net.Conn) {
// 处理连接
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
fmt.Println("read error:", err)
conn.Close()
return
}
// 输出客户端发来的消息
fmt.Printf("received message: %s\n", buf[:n])
// 关闭连接
conn.Close()
}
func main() {
// 创建监听器
listener, err := net.Listen("tcp", ":8080")
if err != nil {
panic(err)
}
defer listener.Close()
// 服务循环
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("accept error:", err)
continue
}
// 启动 goroutine 处理连接
go handleConn(conn)
}
}
在这个示例中,我们使用 net.Listen() 函数创建了一个 TCP 监听器,并在端口 8080 上监听客户端连接。然后,我们进入一个服务循环,不断接收客户端连接,并使用 go handleConn(conn) 启动一个 goroutine 来处理连接。
在 handleConn() 函数中,我们首先读取客户端发来的消息,并输出到终端上。然后,我们使用 conn.Close() 关闭连接。
由于每个客户端连接都会启动一个新的 goroutine,因此该例子可以同时服务多个客户端连接,而且每个连接之间是相互独立的。
总之,使用 goroutine 来服务客户端连接可以让我们编写高效的服务器程序,并且可以轻松地扩展处理连接的能力。
模拟时间 ntp 校准服务器
func handleConn(conn net.Conn) {
// 获取当前时间
t := time.Now()
// 将时间写入连接
fmt.Fprintln(conn, t.Format(time.RFC3339))
// 关闭连接
conn.Close()
}
func main() {
// 监听端口
listener, err := net.Listen("tcp", ":8080")
if err != nil {
panic(err)
}
defer listener.Close()
// 接受连接并处理
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("接收连接出错:", err)
continue
}
// 使用 goroutine 处理连接
go handleConn(conn)
}
}
在 handleConn() 函数中,我们使用 time.Now() 函数获取当前时间,然后使用 fmt.Fprintln() 将时间以 time.RFC3339 的格式写入连接中。最后,我们使用 conn.Close() 关闭连接。
在 main() 函数中,我们使用 net.Listen() 函数监听端口,然后使用 listener.Accept() 接受客户端连接,并使用 go handleConn(conn) 在一个新的 goroutine 中处理每个连接。
和客户端保持链接并实现心跳
func handleConn(conn net.Conn) {
defer conn.Close()
fmt.Println("New client connected")
// 创建定时器,每隔 5 秒向客户端发送数据
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
// 向客户端发送数据
_, err := conn.Write([]byte("heartbeat"))
if err != nil {
fmt.Println("Write error:", err)
return
}
default:
// 读取客户端发送的数据
data := make([]byte, 1024)
_, err := conn.Read(data)
if err != nil {
fmt.Println("Read error:", err)
return
}
fmt.Printf("Received data: %s\n", string(data))
}
}
}
func main() {
// 监听端口
listener, err := net.Listen("tcp", ":8080")
if err != nil {
panic(err)
}
defer listener.Close()
// 接受连接并处理
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("Accept error:", err)
continue
}
// 使用 goroutine 处理连接
go handleConn(conn)
}
}
在这个示例中,我们在 handleConn() 函数中创建了一个定时器 ticker,它每隔 5 秒会向客户端发送数据 "heartbeat"。同时,在函数 handleConn() 的主循环中,我们使用 default 分支读取客户端发送的数据。如果客户端没有发送数据,default 分支会被执行,如果客户端发送了数据,则 default 分支会被阻塞。
需要注意的是,我们在处理完一个连接后,使用 defer conn.Close() 关闭连接。这可以确保在退出该函数之前,连接会被正确关闭。
判断客户端主动关闭链接
func handleConn(conn net.Conn) {
defer conn.Close()
// 读取客户端发送的数据
data := make([]byte, 1024)
n, err := conn.Read(data)
if err != nil {
if err == io.EOF {
fmt.Println("client closed connection")
return
}
fmt.Println("read error:", err)
return
}
// 处理数据
fmt.Printf("received data: %s\n", string(data[:n]))
}
在这个示例中,我们使用 conn.Read() 从连接中读取数据。如果读取成功,我们会处理读取到的数据。如果读取过程中出现错误,我们检查错误是否为 io.EOF。如果是,那么我们就会显示提示信息并终止连接;否则,我们会输出错误信息并终止连接。
在处理连接时,需要使用 defer conn.Close() 来确保连接在函数退出时被正确关闭。
总之,当 conn.Read() 返回 io.EOF 错误时,可以将其视为客户端主动关闭连接,应该在程序中进行处理以确保程序正常运行,并防止程序出现错误。
设置读取客户端连接超时的时间
func handleConn(conn net.Conn) {
defer conn.Close()
// 设置连接的读取超时为 10 秒
conn.SetReadDeadline(time.Now().Add(10 * time.Second))
// 读取客户端发送的数据
data := make([]byte, 1024)
n, err := conn.Read(data)
if err != nil {
if err, ok := err.(net.Error); ok && err.Timeout() {
fmt.Println("read timeout")
} else {
fmt.Println("read error:", err)
}
return
}
// 处理数据
fmt.Printf("received data: %s\n", string(data[:n]))
}
在这个示例中,我们首先调用 conn.SetReadDeadline() 方法设置连接的读取超时为 10 秒。如果在读取数据时超过这个时间,conn.Read() 方法将返回一个 i/o timeout 错误。我们检查错误类型,如果它是一个超时错误,我们会输出 "read timeout" 提示信息。
如果客户端在超时时间内发送了数据,我们读取数据并处理。如果读取过程中发生错误,我们将输出错误信息并终止连接。
总之,使用 conn.SetReadDeadline() 方法可以确保连接在超时时间内读取数据,从而提高程序的可用性和稳定性。
利用 head(size)+body 结构处理粘包
func handleConn(conn net.Conn) {
defer conn.Close()
// 循环读取客户端发送的数据
reader := bufio.NewReader(conn)
for {
// 读取长度前缀
lengthBytes, err := reader.Peek(4)
if err != nil {
fmt.Println("read length error:", err)
return
}
length := binary.BigEndian.Uint32(lengthBytes)
// 读取数据
data := make([]byte, int(length)+4)
_, err = io.ReadFull(reader, data)
if err != nil {
fmt.Println("read data error:", err)
return
}
// 处理数据
content := string(data[4:])
fmt.Printf("received data: %s\n", content)
}
}
在这个示例中,我们使用 bufio.NewReader() 构建一个带缓存的读取器,然后循环从连接中读取数据。在每次读取之前,我们使用 reader.Peek() 方法读取长度前缀,并使用 binary.BigEndian.Uint32() 方法将长度前缀转换为一个 uint32 类型的整数。然后我们读取该长度的数据。这样,我们可以确保不会读取到下一个数据包的数据。
在读取数据时,我们将整个数据包(包括长度前缀在内)读入到 data 变量中,并将 data 变量传递给处理函数。在处理函数中,我们使用 string(data[4:]) 将数据转换为字符串,并输出到控制台上。
总之,通过添加长度前缀,我们可以正确地读取每个数据包,并避免读取数据粘包的问题。
创建无连接的 udp 服务器
func main() {
// 创建 UDP 监听器
addr, err := net.ResolveUDPAddr("udp", ":8080")
if err != nil {
panic(err)
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
panic(err)
}
defer conn.Close()
// 读取客户端发送的数据
for {
buf := make([]byte, 1024)
n, addr, err := conn.ReadFromUDP(buf)
if err != nil {
fmt.Println("read error:", err)
continue
}
// 处理数据
data := string(buf[:n])
fmt.Printf("received data from %s: %s\n", addr.String(), data)
}
}
在这个示例中,我们首先使用 net.ResolveUDPAddr() 函数创建了一个 UDPAddr 结构体,该结构体指定了服务器的 IP 地址和端口号。然后,我们调用 net.ListenUDP() 函数创建一个 UDP 监听器,并使用 defer 关键字将其关闭。在 UDP 监听器被关闭之前,它将持续监听客户端发送的数据。
在主循环中,我们使用 conn.ReadFromUDP() 方法从 UDP 监听器中读取数据。如果读取成功,我们将数据转换为字符串,并输出到控制台上。如果读取过程中发生错误,我们将输出错误信息并继续监听客户端发送的数据。
这个示例程序没有为每个UDP连接启动 goroutine。因为UDP是一个无连接的协议,因此每个收到的数据包都是独立的,而不需要为每个连接保持单独的状态。因此,我们只需要在主线程中循环读取数据,而不必为每个连接创建一个 goroutine。
总之,使用 Go 创建 UDP 服务器非常简单,只需要使用 net.ListenPacket() 函数来创建一个 UDP 监听套接字,并使用 p.ReadFrom() 方法从该套接字中读取数据。
使用缓冲区处理 udp 的大数据包
func handleConn(conn *net.UDPConn) {
// 创建缓冲区
buf := make([]byte, 1024)
// 读取客户端发送的数据
for {
n, addr, err := conn.ReadFromUDP(buf)
if err != nil {
fmt.Println("read error:", err)
continue
}
// 处理数据
data := string(buf[:n])
fmt.Printf("received data from %s: %s\n", addr.String(), data)
}
}
func main() {
// 创建 UDP 监听器
addr, err := net.ResolveUDPAddr("udp", ":8080")
if err != nil {
panic(err)
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
panic(err)
}
defer conn.Close()
// 处理连接
handleConn(conn)
}
在这个示例中,我们在 handleConn() 函数中创建一个 1024 字节的缓冲区。在主循环中,我们使用 conn.ReadFromUDP() 方法读取数据,并将其存储在缓冲区中。然后,我们将缓冲区中的数据转换为一个字符串,并将其输出到控制台上。
通过使用缓冲区,我们可以确保每个数据包都能够完整地处理。需要注意的是,如果您的数据包非常大,可能需要使用更大的缓冲区来存储数据。同时,应该注意,每个连接只有一个缓冲区,而不是为每个数据包创建一个缓冲区。
链接 tcp 服务器的客户端
func main() {
// 连接服务器
conn, err := net.Dial("tcp", "127.0.0.1:8080")
if err != nil {
panic(err)
}
defer conn.Close()
// 向服务器发送数据
message := "Hello, server!"
_, err = conn.Write([]byte(message))
if err != nil {
fmt.Println("write error:", err)
return
}
// 从服务器接收数据
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
fmt.Println("read error:", err)
return
}
// 输出服务器发送的消息
fmt.Printf("received message from server: %s\n", string(buf[:n]))
}
在这个示例中,我们使用 net.Dial() 函数连接到一个运行 TCP 服务器的地址 127.0.0.1:8080。然后,我们使用 conn.Write() 向服务器发送数据。如果写操作成功,我们将从服务器读取响应,并将响应作为字符串输出到终端上。
需要注意的是,在处理连接时,我们使用 defer conn.Close() 以确保在函数退出时将连接关闭。
链接 udp 服务器的客户端
func main() {
// 解析服务器地址
serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:8080")
if err != nil {
panic(err)
}
// 建立 UDP 连接
conn, err := net.DialUDP("udp", nil, serverAddr)
if err != nil {
panic(err)
}
defer conn.Close()
// 发送数据
message := "Hello, server!"
_, err = conn.Write([]byte(message))
if err != nil {
fmt.Println("write error:", err)
return
}
fmt.Println("message sent to server")
}
在这个示例中,我们首先使用 net.ResolveUDPAddr() 函数解析 UDP 服务器的地址,然后使用 net.DialUDP() 函数建立 UDP 连接。我们使用 conn.Write() 将数据发送给服务器。如果写操作成功,我们将输出 "message sent to server" 提示信息。
需要注意的是,在处理连接时,我们使用 defer conn.Close() 以确保在函数退出时将连接关闭。