第四次数据拷贝: 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) }splice
func 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) }