闽公网安备 35020302035485号

第四次数据拷贝: write() 系统调用结束返回到用户进程,当前上下文从内核态切换至用户态,第四次数据拷贝为异步执行,从 Socket 缓冲区拷贝到网卡 (DMA 拷贝)
第三次数据拷贝: 数据从 Socket 缓冲区拷贝到网卡,当前上下文从内核态切换至用户态


2.输入文件描述符仅支持文件类型
func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err error)一个简单的使用示例:
package main
import (
"fmt"
"os"
"syscall"
)
func main() {
// 设置源文件
src, err := os.Open("/tmp/source.txt")
if err != nil {
panic(err)
}
defer src.Close()
// 设置目标文件
target, err := os.Create("/tmp/target.txt")
if err != nil {
panic(err)
}
defer target.Close()
// 获取源文件的文件描述符
srcFd := int(src.Fd())
// 获取目标文件的文件描述符
targetFd := int(target.Fd())
// 使用 Sendfile 实现零拷贝 (拷贝 10 个字节)
// 如果因为字符编码导致的字符截断问题 (如中文乱码问题), 结果自动保留到截断前的最后完整字节
// 例如文件内容为 “星期三四五六七”,count 参数为 4, 那么只会拷贝第一个字 (一个汉字 3 个字节)
// 但是需要注意的是,方法的返回值 written 不受影响 (和 count 参数保持一致)
// 所以实际开发中,第三个参数 offset 必须设置正确,否则就可能引起乱码或数据丢失问题
n, err := syscall.Sendfile(targetFd, srcFd, nil, 4)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("写入字节数: %d", n)
}
splicefunc Splice(rfd int, roff *int64, wfd int, woff *int64, len int, flags int) (n int64, err error)一个简单的使用示例:
package main
import (
"fmt"
"os"
"syscall"
)
func main() {
// 堆代码 duidaima.com
// 设置源文件
src,
err: = os.Open("/tmp/source.txt")
if err != nil {
panic(err)
}
defer src.Close()
// 设置目标文件
target,
err: = os.Create("/tmp/target.txt")
if err != nil {
panic(err)
}
defer target.Close()
// 创建管道文件
// 作为两个文件传输数据的中介
pipeReader,
pipeWriter,
err: = os.Pipe()
if err != nil {
panic(err)
}
defer pipeReader.Close()
defer pipeWriter.Close()
// 设置文件读写模式
// 笔者在标准库中没有找到对应的常量说明
// 读者可以参考这个文档:
// https://pkg.go.dev/golang.org/x/sys/unix#pkg-constants
// SPLICE_F_NONBLOCK = 0x2
spliceNonBlock: = 0x02
// 使用 Splice 将数据从源文件描述符移动到管道 writer
_,
err = syscall.Splice(int(src.Fd()), nil, int(pipeWriter.Fd()), nil, 1024, spliceNonBlock)
if err != nil {
panic(err)
}
// 使用 Splice 将数据从管道 reader 移动到目标文件描述符
n,
err: = syscall.Splice(int(pipeReader.Fd()), nil, int(target.Fd()), nil, 1024, spliceNonBlock)
if err != nil {
panic(err)
}
fmt.Printf("写入字节数: %d", n)
}