网络文件传输
网络文件传输
流程简析
借助TCP完成文件的传输,基本思路如下:
1:发送方(客户端)向服务端发送文件名,服务端保存该文件名。
2:接收方(服务端)向客户端返回一个消息ok,确认文件名保存成功。
3:发送方(客户端)收到消息后,开始向服务端发送文件数据。
4:接收方(服务端)读取文件内容,写入到之前保存好的文件中。
获取文件属性和命令行参数
首先获取文件名。借助os包中的stat()函数来获取文件属性信息。在函数返回的文件属性中包含文件名和文件大小。Stat参数name传入的是文件访问的绝对路径。FileInfo中的Name()函数可以将不含路径的文件名单独提取出来。.
func Stat(name string) (FileInfo, error)
type FileInfo interface {
Name() string
Size() int64
Mode() FileMode
ModTime() time.Time
IsDir() bool
Sys() interface{}
}
获取文件属性示例:
package main
import (
"os"
"fmt"
)
func main() {
list := os.Args // 获取命令行参数,存入list中
if len(list) != 2 { // 确保用户输入了一个命令行参数
fmt.Println("格式为:xxx.go 文件名")
return
}
fileName := list[1] // 从命令行保存文件名(含路径)
fileInfo, err := os.Stat(fileName) //根据文件名获取文件属性信息 fileInfo
if err != nil {
fmt.Println("os.Stat err:", err)
return
}
fmt.Println("文件name为:", fileInfo.Name()) // 得到文件名(不含路径)
fmt.Println("文件size为:", fileInfo.Size()) // 得到文件大小。单位字节
}
发送端实现
实现流程大致如下:
-
提示用户输入文件名。接收文件名path(含访问路径)
-
使用os.Stat()获取文件属性,得到纯文件名(去除访问路径)
-
主动连接服务器,结束时关闭连接
-
给接收端(服务器)发送文件名conn.Write()
-
读取接收端回发的确认数据conn.Read()
-
判断是否为“ok”。如果是,封装函数SendFile() 发送文件内容。传参path和conn
-
只读Open文件, 结束时Close文件
-
循环读文件,读到EOF终止文件读取
-
将读到的内容原封不动Write给接收端(服务器)
代码实现:
package main
import (
"fmt"
"os"
"net"
"io"
)
func SendFile(path string, conn net.Conn) {
// 以只读方式打开文件
f, err := os.Open(path)
if err != nil {
fmt.Println("os.Open err:", err)
return
}
defer f.Close() // 发送结束关闭文件。
// 循环读取文件,原封不动的写给服务器
buf := make([]byte, 4096)
for {
n, err := f.Read(buf) // 读取文件内容到切片缓冲中
if err != nil {
if err == io.EOF {
fmt.Println("文件发送完毕")
} else {
fmt.Println("f.Read err:", err)
}
return
}
conn.Write(buf[:n]) // 原封不动写给服务器
}
}
func main() {
// 提示输入文件名
fmt.Println("请输入需要传输的文件:")
var path string
fmt.Scan(&path)
// 获取文件名 fileInfo.Name()
fileInfo, err := os.Stat(path)
if err != nil {
fmt.Println("os.Stat err:", err)
return
}
// 主动连接服务器
conn, err := net.Dial("tcp", "127.0.0.1:8005")
if err != nil {
fmt.Println("net.Dial err:", err)
return
}
defer conn.Close()
// 给接收端,先发送文件名
_, err = conn.Write([]byte(fileInfo.Name()))
if err != nil {
fmt.Println("conn.Write err:", err)
return
}
// 读取接收端回发确认数据 —— ok
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
fmt.Println("conn.Read err:", err)
return
}
// 判断如果是ok,则发送文件内容
if "ok" == string(buf[:n]) {
SendFile(path, conn) // 封装函数读文件,发送给服务器,需要path、conn
}
}
接收端实现
实现流程大致如下:
- 创建监听listener,程序结束时关闭。
- 阻塞等待客户端连接,程序结束时关闭conn。
- 读取客户端发送文件名。保存fileName。
- 回发“ok”给客户端做应答
- 封装函数 RecvFile接收客户端发送的文件内容。传参fileName 和conn
- 按文件名Create文件,结束时Close
- 循环Read客户端发送的文件内容,当读到EOF说明文件读取完毕。
- 将读到的内容原封不动Write到创建的文件中
代码实现:
package main
import (
"net"
"fmt"
"os"
"io"
)
func RecvFile(fileName string, conn net.Conn) {
// 创建新文件
f, err := os.Create(fileName)
if err != nil {
fmt.Println("Create err:", err)
return
}
defer f.Close()
// 接收客户端发送文件内容,原封不动写入文件
buf := make([]byte, 4096)
for {
n, err := conn.Read(buf)
if err != nil {
if err == io.EOF {
fmt.Println("文件接收完毕")
} else {
fmt.Println("Read err:", err)
}
return
}
f.Write(buf[:n]) // 写入文件,读多少写多少
}
}
func main() {
// 创建监听
listener, err := net.Listen("tcp", "127.0.0.1:8005")
if err != nil {
fmt.Println("Listen err:", err)
return
}
defer listener.Close()
// 阻塞等待客户端连接
conn, err := listener.Accept()
if err != nil {
fmt.Println("Accept err:", err)
return
}
defer conn.Close()
// 读取客户端发送的文件名
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
fmt.Println("Read err:", err)
return
}
fileName := string(buf[:n]) // 保存文件名
// 回复 0k 给发送端
conn.Write([]byte("ok"))
// 接收文件内容
RecvFile(fileName, conn) // 封装函数接收文件内容, 传fileName 和 conn
}