GO_网络编程---文件传输实战

一、传输整体流程

发送端 (客户端) ↔ 接收端 (服务端) 自定义简易传输协议:

  1. 客户端先发送文件名
  2. 服务端收到文件名,回复 ok
  3. 客户端收到 ok 后,分片读取本地文件,发送二进制文件内容
  4. 服务端循环接收二进制数据,写入新文件,传输完成

二、核心前置 API 说明

  1. os.Stat(path):获取文件元信息,可拿到文件名、文件大小,校验文件是否存在
  2. os.Open():只读打开本地文件(发送端使用)
  3. os.Create():创建新文件(接收端使用)
  4. file.Read(buf):读取文件到缓冲区,返回实际读取字节数,读到文件末尾返回io.EOF
  5. conn.Read/conn.Write:TCP 连接读写,网络数据传输
  6. 缓冲区分片传输:使用 4KB 缓冲,避免一次性加载大文件造成内存溢出

三、客户端发送代码 send.go

go

Go 复制代码
package main

import (
	"fmt"
	"io"
	"net"
	"os"
)

func main() {
	// 输入待传输文件路径
	var path string
	fmt.Println("请输入需要传输的文件路径:")
	fmt.Scan(&path)

	// 获取文件信息,校验文件是否存在
	info, err := os.Stat(path)
	if err != nil {
		fmt.Println("os.Stat err =", err)
		return
	}

	// 主动连接文件服务端
	conn, err1 := net.Dial("tcp", "127.0.0.1:8000")
	if err1 != nil {
		fmt.Println("net.Dial err1 = ", err1)
		return
	}
	defer conn.Close()

	// 第一步:发送文件名给服务端
	_, err := conn.Write([]byte(info.Name()))
	if err != nil {
		fmt.Println("发送文件名 err = ", err)
		return
	}

	// 等待服务端ok回复
	buf := make([]byte, 1024)
	n, err1 := conn.Read(buf)
	if err1 != nil {
		fmt.Println("读取服务端响应 err1 = ", err1)
		return
	}
	if string(buf[:n]) != "ok" {
		fmt.Println("服务端未就绪,终止传输")
		return
	}

	// 打开本地待发送文件
	f, err := os.Open(path)
	if err != nil {
		fmt.Println("打开文件失败 err =", err)
		return
	}
	defer f.Close()

	// 4KB缓冲区循环分片发送文件内容
	sendBuf := make([]byte, 1024*4)
	for {
		readNum, err := f.Read(sendBuf)
		if err != nil {
			if err == io.EOF {
				fmt.Println("文件发送完毕")
			} else {
				fmt.Println("读取文件 err = ", err)
			}
			break
		}
		// 发送本次读到的有效数据
		_, err = conn.Write(sendBuf[:readNum])
		if err != nil {
			fmt.Println("发送文件数据失败 err =", err)
			return
		}
	}
}

四、服务端接收代码 recv.go

go

Go 复制代码
package main

import (
	"fmt"
	"io"
	"net"
	"os"
)

func main() {
	// 监听tcp端口
	listener, err := net.Listen("tcp", "127.0.0.1:8000")
	if err != nil {
		fmt.Println("net.Listen err =", err)
		return
	}
	defer listener.Close()
	fmt.Println("文件接收服务启动,监听8000端口")

	// 阻塞等待客户端连接
	conn, err1 := listener.Accept()
	if err1 != nil {
		fmt.Println("listener.Accept err =", err1)
		return
	}
	defer conn.Close()
	fmt.Println("客户端已连接")

	// 先读取客户端发送的文件名
	buf := make([]byte, 1024)
	n, err := conn.Read(buf)
	if err != nil {
		fmt.Println("读取文件名 err =", err)
		return
	}
	fileName := string(buf[:n])
	fmt.Println("待接收文件名称:", fileName)

	// 向客户端回复ok,表示准备接收文件内容
	_, err = conn.Write([]byte("ok"))
	if err != nil {
		fmt.Println("发送ok响应失败 err =", err)
		return
	}

	// 调用函数接收完整文件
	RecvFile(fileName, conn)
}

// RecvFile 根据文件名创建文件,循环接收网络数据写入本地
func RecvFile(fileName string, conn net.Conn) {
	f, err := os.Create(fileName)
	if err != nil {
		fmt.Println("os.Create 创建文件 err = ", err)
		return
	}
	defer f.Close()

	recvBuf := make([]byte, 1024*4)
	for {
		readNum, err := conn.Read(recvBuf)
		if err != nil {
			if err == io.EOF {
				fmt.Println("文件接收完毕")
			} else {
				fmt.Println("conn.Read 读取文件数据 err =", err)
			}
			break
		}
		if readNum == 0 {
			fmt.Println("文件传输结束")
			break
		}
		// 将读到的有效数据写入本地文件
		_, err = f.Write(recvBuf[:readNum])
		if err != nil {
			fmt.Println("写入文件失败 err =", err)
			return
		}
	}
}

五、运行测试步骤

  1. 先运行接收端 recv.go,启动监听服务
  2. 新开终端运行发送端 send.go
  3. 输入本地文件路径(图片、txt、压缩包均可)
  4. 传输完成后,服务端同级目录生成同名文件

六、重点复习 & 踩坑总结

  1. 必须分片读写文件,大文件一次性读取会造成内存溢出,统一使用 4KB 缓冲
  2. 读写缓冲区时,必须截取有效长度 buf[:n],否则缓冲区残留脏数据,文件损坏
  3. 传输流程不能颠倒:先发文件名→回 ok→传文件内容,自定义协议避免粘包问题
  4. io.EOF 是文件正常读取结束标识,其他 error 属于异常
  5. 文件、TCP 连接必须搭配defer Close(),避免文件句柄、网络资源泄漏
  6. 仅支持单客户端单次文件传输,拓展可使用 goroutine 实现并发多客户端上传
相关推荐
damoluomu4 天前
挑 PHP CMS 之前,先学会看它的协议
php
虎头金猫4 天前
4K 视频总卡在公网带宽?用 N1 + OpenList 把网盘播放链路重新理顺
运维·服务器·网络·python·容器·beautifulsoup·pandas
ttwuai4 天前
Go开源后台管理系统推荐:怎么按技术栈和边界比较4个官方仓库?
golang·gin
codeejun4 天前
每日一Go·MySQL-5、锁机制全解析
云原生·golang
wuyk5554 天前
《WiFi 嵌入式物联网开发全套实战》| 第 16 章 ESP32 AP+STA 双模共存原理与工程坑点
网络·stm32·物联网
Achou.Wang4 天前
k8s中nginx worker process自动设置
后端·golang
QYRdata4 天前
年均增速24.2%!机器人数据湖未来六年增长动能强劲
网络·机器人·服务发现
CHENKONG_CK4 天前
破解制鞋打磨痛点:RFID赋能去毛刺工序自动化升级
网络·单片机·嵌入式硬件·网络协议·tcp/ip
chshang19924 天前
工业路由器是什么?浅谈5G工业网络中的IR602
网络·物联网·5g·智能路由器
萧瑟余晖4 天前
Netty 核心组件与 Reactor 模型详解
网络·架构