本文首发于微信公众号北国故事
同步阻塞、同步非阻塞、异步非阻塞IO使我们在工作中性能优化过程中需要理解的重要知识点。
今天通过Golang代码示例理解这三种网络IO模型。
一、同步阻塞IO模型TPC和HTTP示例
同步阻塞IO符合我们的直觉认知,应用程序从TPC连接接收数据缓冲区接受数据,如果没有数据就等待——此处就是阻塞,如果有数据需要把数据从内核空间读取到用户空间——此处就是同步。
在Go语言中进行同步阻塞IO编程TCP交互,可以使用标准库中的net包来实现。以下是一个示例:
package main
import (
"fmt"
"net"
)
func main() {
// 监听TCP端口
listener, err := net.Listen("tcp", ":8080")
if err != nil {
fmt.Println("Error starting TCP server:", err)
return
}
defer listener.Close()
fmt.Println("TCP server started and listening on port 8080")
for {
// 接受客户端连接
conn, err := listener.Accept()
if err != nil {
fmt.Println("Error accepting client connection:", err)
continue
}
// 处理客户端连接
go handleConnection(conn)
}
}
func handleConnection(conn net.Conn) {
defer conn.Close()
// 读取客户端发送的数据
data := make([]byte, 1024)
_, err := conn.Read(data)
if err != nil {
fmt.Println("Error reading data from client:", err)
return
}
// 处理客户端发送的数据
response := processData(data)
// 向客户端发送响应数据
_, err = conn.Write([]byte(response))
if err != nil {
fmt.Println("Error sending response to client:", err)
return
}
}
func processData(data []byte) string {
// 处理客户端发送的数据
return "Processed data: " + string(data)
}
在这个示例中,我们使用了标准库中的net包来实现TCP通信。首先,我们使用"net.Listen"函数监听TCP端口。然后,我们使用"listener.Accept"函数接受客户端连接,并使用"handleConnection"函数来处理客户端连接。
在"handleConnection"函数中,我们首先从客户端读取数据。然后,我们使用"processData"函数来处理客户端发送的数据,并将处理结果发送回客户端。
由于我们使用了同步阻塞IO函数来进行IO操作,所以程序会在执行IO操作时被阻塞。这意味着,在等待IO操作完成的同时,程序无法执行其他任务。因此,在处理多个客户端连接时,我们使用了goroutine来进行并发处理。
以下是一个使用Go语言进行同步阻塞IO编程的HTTP示例:
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// 读取请求的Body
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error reading request body", http.StatusInternalServerError)
return
}
// 处理请求
response := processRequest(body)
// 将响应写入ResponseWriter
_, err = w.Write(response)
if err != nil {
http.Error(w, "Error writing response body", http.StatusInternalServerError)
return
}
})
// 启动HTTP服务器
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println("Error starting HTTP server:", err)
}
}
func processRequest(request []byte) []byte {
// 处理请求的逻辑
return []byte("Processed request: " + string(request))
}
在这个示例中,我们使用了Go标准库中的"net/http"包来创建一个HTTP服务器。服务器会监听来自客户端的HTTP请求,并将请求的Body读取出来,然后通过调用processRequest函数来处理请求。
在processRequest函数中,我们可以执行任何我们需要完成的逻辑,并返回响应。在这个示例中,我们只是简单地将请求的内容添加到响应消息中。
最后,我们使用ResponseWriter将响应消息发送回客户端。如果出现任何错误,我们会返回一个HTTP错误代码来通知客户端发生了错误。
这个示例中的IO操作都是同步阻塞的,因为我们使用了标准库中的函数来读取请求的Body和写入响应消息。这意味着当我们执行IO操作时,程序会一直等待直到操作完成,然后才会继续执行下一步操作。
二、同步非阻塞IO模型TPC和HTTP示例
在Go语言中进行同步非阻塞IO编程TCP,可以使用标准库中的net包和goroutine来实现。
package main
import (
"fmt"
"net"
"time"
)
func main() {
// 监听TCP端口
listener, err := net.Listen("tcp", ":8080")
if err != nil {
fmt.Println("Error starting TCP server:", err)
return
}
defer listener.Close()
fmt.Println("TCP server started and listening on port 8080")
for {
// 接受客户端连接
conn, err := listener.Accept()
if err != nil {
fmt.Println("Error accepting client connection:", err)
continue
}
// 处理客户端连接
go handleConnection(conn)
}
}
func handleConnection(conn net.Conn) {
defer conn.Close()
// 设置读取超时时间为5秒
conn.SetReadDeadline(time.Now().Add(5 * time.Second))
// 读取客户端发送的数据
data := make([]byte, 1024)
n, err := conn.Read(data)
if err != nil {
fmt.Println("Error reading data from client:", err)
return
}
// 处理客户端发送的数据
response := processData(data[:n])
// 设置写入超时时间为5秒
conn.SetWriteDeadline(time.Now().Add(5 * time.Second))
// 向客户端发送响应数据
_, err = conn.Write([]byte(response))
if err != nil {
fmt.Println("Error sending response to client:", err)
return
}
}
func processData(data []byte) string {
// 处理客户端发送的数据
return "Processed data: " + string(data)
}
在这个示例中,我们使用了标准库中的net包来实现TCP通信。首先,我们使用"net.Listen"函数监听TCP端口。然后,我们使用"listener.Accept"函数接受客户端连接,并使用"handleConnection"函数来处理客户端连接。
在"handleConnection"函数中,我们首先设置了读取和写入超时时间,以防止IO操作阻塞程序。然后,我们使用"net.Conn.Read"函数从客户端读取数据,并使用"processData"函数来处理客户端发送的数据。最后,我们使用"net.Conn.Write"函数向客户端发送响应数据。
由于我们使用了同步非阻塞IO函数来进行IO操作,所以程序不会在执行IO操作时被阻塞。这意味着,在等待IO操作完成的同时,程序可以执行其他任务。因此,在处理多个客户端连接时,我们使用goroutine来进行并发处理。
HTTP实现可以使用goroutine和channel来实现
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// 创建一个channel来接收请求的Body
bodyChan := make(chan []byte)
// 将请求的Body读取操作放到goroutine中执行
go func() {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
// 发送错误信息到channel
bodyChan <- []byte(fmt.Sprintf("Error reading request body: %v", err))
} else {
// 发送读取到的Body到channel
bodyChan <- body
}
}()
// 处理请求
response := processRequest(bodyChan)
// 将响应写入ResponseWriter
_, err := w.Write(response)
if err != nil {
http.Error(w, "Error writing response body", http.StatusInternalServerError)
return
}
})
// 启动HTTP服务器
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println("Error starting HTTP server:", err)
}
}
func processRequest(bodyChan chan []byte) []byte {
// 非阻塞地从channel中获取请求的Body
select {
case body := <-bodyChan:
// 处理请求的逻辑
return []byte("Processed request: " + string(body))
default:
// 如果没有收到请求的Body,则返回错误信息
return []byte("No request body received")
}
}
在这个示例中,我们使用了goroutine和channel来实现非阻塞IO操作。当一个HTTP请求到达时,我们创建了一个channel来接收请求的Body。然后,我们启动一个goroutine来执行读取请求Body的操作,并将读取到的Body发送到channel中。
在processRequest函数中,我们使用了select语句来从channel中获取请求的Body。如果没有收到请求的Body,我们会返回一个错误信息。如果收到了请求的Body,则执行处理请求的逻辑,并返回响应消息。
由于我们使用了goroutine和channel来进行IO操作,所以程序不会在执行IO操作时被阻塞。这意味着,在等待IO操作完成的同时,程序可以继续执行其他任务。
三、异步非阻塞IO模型TPC和HTTP示例
在Go语言中进行异步非阻塞IO编程TCP,可以使用标准库中的net包和goroutine来实现
package main
import (
"fmt"
"net"
)
func main() {
// 监听TCP端口
listener, err := net.Listen("tcp", ":8080")
if err != nil {
fmt.Println("Error starting TCP server:", err)
return
}
defer listener.Close()
fmt.Println("TCP server started and listening on port 8080")
for {
// 接受客户端连接
conn, err := listener.Accept()
if err != nil {
fmt.Println("Error accepting client connection:", err)
continue
}
// 处理客户端连接
go handleConnection(conn)
}
}
func handleConnection(conn net.Conn) {
defer conn.Close()
// 读取客户端发送的数据
data := make([]byte, 1024)
_, err := conn.Read(data)
if err != nil {
fmt.Println("Error reading data from client:", err)
return
}
// 处理客户端发送的数据
response := processData(data)
// 向客户端发送响应数据
_, err = conn.Write([]byte(response))
if err != nil {
fmt.Println("Error sending response to client:", err)
return
}
}
func processData(data []byte) string {
// 处理客户端发送的数据
return "Processed data: " + string(data)
}
在这个示例中,我们使用了标准库中的net包来实现TCP通信。首先,我们使用"net.Listen"函数监听TCP端口。然后,我们使用"listener.Accept"函数接受客户端连接,并使用"handleConnection"函数来处理客户端连接。
在"handleConnection"函数中,我们使用"net.Conn.Read"函数从客户端读取数据,并使用"processData"函数来处理客户端发送的数据。然后,我们使用"net.Conn.Write"函数向客户端发送响应数据。
由于我们使用了异步非阻塞IO函数来进行IO操作,所以程序不会在执行IO操作时被阻塞。这意味着,在等待IO操作完成的同时,程序可以执行其他任务。因此,在处理多个客户端连接时,我们使用goroutine来进行并发处理。
HTTP的模式使用Go语言进行同步阻塞IO编程示例
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// 读取请求的Body
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error reading request body", http.StatusInternalServerError)
return
}
// 处理请求
response := processRequest(body)
// 将响应写入ResponseWriter
_, err = w.Write(response)
if err != nil {
http.Error(w, "Error writing response body", http.StatusInternalServerError)
return
}
})
// 启动HTTP服务器
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println("Error starting HTTP server:", err)
}
}
func processRequest(request []byte) []byte {
// 处理请求的逻辑
return []byte("Processed request: " + string(request))
}
在这个示例中,我们使用了Go标准库中的"net/http"包来创建一个HTTP服务器。服务器会监听来自客户端的HTTP请求,并将请求的Body读取出来,然后通过调用processRequest函数来处理请求。
在processRequest函数中,我们可以执行任何我们需要完成的逻辑,并返回响应。在这个示例中,我们只是简单地将请求的内容添加到响应消息中。
最后,我们使用ResponseWriter将响应消息发送回客户端。如果出现任何错误,我们会返回一个HTTP错误代码来通知客户端发生了错误。
这个示例中的IO操作都是同步阻塞的,因为我们使用了标准库中的函数来读取请求的Body和写入响应消息。这意味着当我们执行IO操作时,程序会一直等待直到操作完成,然后才会继续执行下一步操作。
总结:以上有两个概念,是否同步、是否阻塞,阻塞与非阻塞的区别是读取TCP接收数据缓冲区如果没有数据是否等待,如果等待即是阻塞,不等待即是不阻塞;至于同步和异步则是如果有数据应用程序是否自己复制数据从内核空间到用户空间,如果需要自己复制数据则是同步,否则即是异步,异步模型下请求指定发送完后就即刻返回,所以天然的没有异步阻塞这个IO模型。