Go网络文件传输
程序员文章站
2022-06-22 10:50:45
流程分析 借助TCP完成文件的传输,基本思路如下: 1. 发送方(客户端)向服务端发送文件名,服务端保存该文件名。 2. 接收方(服务端)向客户端返回一个消息ok,确认文件名保存成功。 3. 发送方(客户端)收到消息后,开始向服务端发送文件数据。 4. 接收方(服务端)读取文件内容,写入到之前保存好 ......
流程分析
借助tcp完成文件的传输,基本思路如下:
- 发送方(客户端)向服务端发送文件名,服务端保存该文件名。
- 接收方(服务端)向客户端返回一个消息ok,确认文件名保存成功。
- 发送方(客户端)收到消息后,开始向服务端发送文件数据。
- 接收方(服务端)读取文件内容,写入到之前保存好的文件中。
由于文件传输需要稳定可靠的连接,所以采用tcp方式完成网络文件传输功能。
首先获取文件名。借助os包中的stat()函数来获取文件属性信息。在函数返回的文件属性中包含文件名和文件大小。stat参数name传入的是文件访问的绝对路径。fileinfo中的name()函数可以将文件名单独提取出来。
func stat(name string) (fi fileinfo, err error)
stat返回一个描述name指定的文件对象的fileinfo。如果指定的文件对象是一个符号链接,返回的fileinfo描述该符号链接指向的文件的信息,本函数会尝试跳转该链接。如果出错,返回的错误值为*patherror类型。
我们通过源码可以得知fileinfo是一个接口,要实现这个接口就必须实现这个接口的如下所有方法
实现网络文件传输实质上时借助了本地文件复制和tcp网络编程相关知识,可以先看看go语言复制文件和go网络编程了解相关内容。
所以关于使用tcp实现文件传输大致步骤可以归结为如下步骤
接收端:
- 创建监听 listener,程序结束时关闭。
- 阻塞等待客户端连接 conn,程序结束时关闭conn。
- 读取客户端发送文件名。保存 filename。
- 回发“ok”。
- 封装函数 recvfile 接收客户端发送的文件内容。传参 filename 和 conn
- 按文件名 create 文件,结束时 close
- 循环 read 发送端网络文件内容,当读到 0 说明文件读取完毕。
- 将读到的内容原封不动write到创建的文件中
接收端代码:
package main import ( "fmt" "io" "net" "os" ) func recvfile(conn net.conn, filename string) { //按照文件名创建新文件 file, err := os.create(filename) if err != nil { fmt.printf("os.create()函数执行错误,错误为:%v\n", err) return } defer file.close() //从网络中读数据,写入本地文件 for { buf := make([]byte, 4096) n, err := conn.read(buf) //写入本地文件,读多少,写多少 file.write(buf[:n]) if err != nil { if err == io.eof { fmt.printf("接收文件完成。\n") } else { fmt.printf("conn.read()方法执行出错,错误为:%v\n", err) } return } } } func main() { //1.创建监听socket listener, err := net.listen("tcp", "127.0.0.1:8000") if err != nil { fmt.printf("net.listen()函数执行错误,错误为:%v\n", err) return } defer listener.close() //阻塞监听 conn, err := listener.accept() if err != nil { fmt.printf("listener.accept()方法执行错误,错误为:%v\n", err) return } defer conn.close() //文件名的长度不能超过1024个字节 buf := make([]byte, 4096) n, err := conn.read(buf) if err != nil { fmt.printf("conn.read()方法执行错误,错误为:%v\n", err) return } filename := string(buf[:n]) //回写ok给发送端 conn.write([]byte("ok")) //获取文件内容 recvfile(conn, filename) }
发送端:
- 提示用户使用命令行参数输入文件名。接收文件名 filepath(含访问路径)
- 使用 os.stat()获取文件属性,得到纯文件名 filename(去除访问路径)
- 主动发起连接服务器请求,结束时关闭连接。
- 发送文件名到接收端 conn.write()
- 读取接收端回发的确认数据 conn.read()
- 判断是否为“ok”。如果是,封装函数 sendfile() 发送文件内容。传参 filepath 和 conn
- 只读 open 文件, 结束时close文件
- 循环读本地文件,读到 eof,读取完毕。
- 将读到的内容原封不动 conn.write 给接收端(服务器)
发送端代码:
package main import ( "fmt" "io" "net" "os" ) func sendfile(conn net.conn, filepath string) { //只读打开文件 file, err := os.open(filepath) if err != nil { fmt.printf("os.open()函数执行出错,错误为:%v\n", err) return } defer file.close() buf := make([]byte, 4096) for { //从本地文件中读数据,写给网络接收端。读多少,写多少 n, err := file.read(buf) if err != nil { if err == io.eof { fmt.printf("发送文件完毕\n") } else { fmt.printf("file.read()方法执行错误,错误为:%v\n", err) } return } //写到网络socket中 _, err = conn.write(buf[:n]) } } func main() { //获取命令行参数 list := os.args if len(list) != 2 { fmt.printf("格式为:go run xxx.go 文件名\n") return } //提取文件的绝对路径 path := list[1] //获取文件属性 fileinfo, err := os.stat(path) if err != nil { fmt.printf("os.stat()函数执行出错,错误为:%v\n", err) return } //主动发起连接请求 conn, err := net.dial("tcp", "127.0.0.1:8000") if err != nil { fmt.printf("net.dial()函数执行出错,错误为:%v\n", err) return } defer conn.close() //发送文件名给接收端 _, err = conn.write([]byte(fileinfo.name())) //读取服务器回发数据 buf := make([]byte, 4096) n, err := conn.read(buf) if err != nil { fmt.printf("conn.read(buf)方法执行出错,错误为:%v\n", err) return } if string(buf[:n]) == "ok" { //写文件内容给服务器 -- 借助conn sendfile(conn, path) } }
上一篇: 深拷贝与浅拷贝
下一篇: 第一部分day1-变量、运算