客户端发送消息询问服务端,你上次接收到的文件内容位置
服务端告诉客户端上次接收到的文件内容位置
客户端就从上次断点的位置继续发送文件内容
客户端发送文件内容完毕后通知服务端,然后断开连接
下面我们看看代码的实现
服务端
// file name: server.go
package main
import (
"os"
"io"
"net"
"log"
"strconv"
// "time"
)
// 把接收到的内容append到文件
func writeFile(content []byte) {
if len(content) != 0 {
fp, err := os.OpenFile("test_1.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0755)
defer fp.Close()
if err != nil {
log.Fatalf("open file faild: %s\n", err)
}
_, err = fp.Write(content)
if err != nil {
log.Fatalf("append content to file faild: %s\n", err)
}
log.Printf("append content: 【%s】 success\n", string(content))
}
}
// 获取已接收内容的大小
// (断点续传需要把已接收内容大下通知客户端从哪里开始发送文件内容)
func getFileStat() int64 {
fileinfo, err := os.Stat("test_1.txt")
if err != nil {
// 如果首次没有创建test_1.txt文件,则直接返回0
// 告诉客户端从头开始发送文件内容
if os.IsNotExist(err) {
log.Printf("file size: %d\n", 0)
return int64(0)
}
log.Fatalf("get file stat faild: %s\n", err)
}
log.Printf("file size: %d\n", fileinfo.Size())
return fileinfo.Size()
}
func serverConn(conn net.Conn) {
defer conn.Close()
for {
var buf = make([]byte, 10)
n, err := conn.Read(buf)
if err != nil {
if err == io.EOF {
log.Println("server io EOF\n")
return
}
log.Fatalf("server read faild: %s\n", err)
}
log.Printf("recevice %d bytes, content is 【%s】\n", n, string(buf[:n]))
// 判断客户端发送过来的消息
// 如果是’start-->‘则表示需要告诉客户端从哪里开始读取文件数据发送
switch string(buf[:n]) {
case "start-->":
off := getFileStat()
// int conver string
stringoff := strconv.FormatInt(off, 10)
_, err = conn.Write([]byte(stringoff))
if err != nil {
log.Fatalf("server write faild: %s\n", err)
}
continue
case "<--end":
// 如果接收到客户端通知所有文件内容发送完毕消息则退出
log.Fatalf("receive over\n")
return
// default:
// time.Sleep(time.Second * 1)
}
// 把客户端发送的内容保存到文件
writeFile(buf[:n])
}
}
func main() {
// 建立监听
l, err := net.Listen("tcp", ":8888")
if err != nil {
log.Fatalf("error listen: %s\n", err)
}
defer l.Close()
log.Println("waiting accept.")
// 允许客户端连接,在没有客户端连接时,会一直阻塞
conn, err := l.Accept()
if err != nil {
log.Fatalf("accept faild: %s\n", err)
}
serverConn(conn)
}
客户端
// file name: client.go
package main
import (
"os"
"io"
"net"
"log"
"time"
"strconv"
)
// 获取服务端发送的消息
func clientRead(conn net.Conn) int {
buf := make([]byte, 5)
n, err := conn.Read(buf)
if err != nil {
log.Fatalf("receive server info faild: %s\n", err)
}
// string conver int
off, err := strconv.Atoi(string(buf[:n]))
if err != nil {
log.Fatalf("string conver int faild: %s\n", err)
}
return off
}
// 发送消息到服务端
func clientWrite(conn net.Conn, data []byte) {
_, err := conn.Write(data)
if err != nil {
log.Fatalf("send 【%s】 content faild: %s\n", string(data), err)
}
log.Printf("send 【%s】 content success\n", string(data))
}
// client conn
func clientConn(conn net.Conn) {
defer conn.Close()
// 发送"start-->"消息通知服务端,我要开始发送文件内容了
// 你赶紧告诉我你那边已经接收了多少内容,我从你已经接收的内容处开始继续发送
clientWrite(conn, []byte("start-->"))
off := clientRead(conn)
// send file content
fp, err := os.OpenFile("test.txt", os.O_RDONLY, 0755)
if err != nil {
log.Fatalf("open file faild: %s\n", err)
}
defer fp.Close()
// set file seek
// 设置从哪里开始读取文件内容
_, err = fp.Seek(int64(off), 0)
if err != nil {
log.Fatalf("set file seek faild: %s\n", err)
}
log.Printf("read file at seek: %d\n", off)
for {
// 每次发送10个字节大小的内容
data := make([]byte, 10)
n, err := fp.Read(data)
if err != nil {
if err == io.EOF {
// 如果已经读取完文件内容
// 就发送'<--end'消息通知服务端,文件内容发送完了
time.Sleep(time.Second * 1)
clientWrite(conn, []byte("<--end"))
log.Println("send all content, now quit")
break
}
log.Fatalf("read file err: %s\n", err)
}
// 发送文件内容到服务端
clientWrite(conn, data[:n])
}
}
func main() {
// connect timeout 10s
conn, err := net.DialTimeout("tcp", ":8888", time.Second * 10)
if err != nil {
log.Fatalf("client dial faild: %s\n", err)
}
clientConn(conn)
}
实际应用的注意事项:
可限时记录未上传完的文件,过时删除,也可通过某个操作限制未上传完的文件存储时间。
上传完成的文件要将文件名更改避免文件名重复
为了避免不同用户上传相同文件名也可将文件名记录到数据库中与用户绑定。
---------------------
作者:Tab609
来源:CSDN
原文:https://blog.csdn.net/luckytanggu/article/details/79830493
版权声明:本文为博主原创文章,转载请附上博文链接!