Golang多线程文件传输
------------------------------
前段时间抽空用Golang简单实现了一下多线程文件传输。
采用的方式为将待传输的文件拆分成多部分由多个协程同时传输。服务端在全部数据接收完成后将文件拼接还原为原文件。
服务端编译后直接执行,客户端至少需要带一个参数:待发送文件名 。另外更多参数中,第二参数为指定服务端最终生成的文件名。第三个参数为单次发送数据包的大小(单位:Byte),最大不超过18K。第四个参数为待发送文件拆分的个数,其对应发送的协程数量。
好了,下面是全部代码
//多协程文件传输服务端
//作者:LvanNeo
//邮箱:lvan_software@foxmail.com
//版本:1.0
//日期:2013-09-26
//对每个请求由一个单独的协程进行处理,文件接收完成后由一个协负责将所有接收的数据合并成一个有效文件
package main
import (
"bytes"
"fmt"
"net"
"os"
"runtime"
"strconv"
)
func init() {
runtime.GOMAXPROCS(runtime.NumCPU())
}
func main() {
var (
// host   = "192.168.1.5"    //如果写locahost或127.0.0.1则只能本地访问。
port = "9090"
// remote = host + ":" + port
remote = ":" + port //此方式本地与非本地都可访问
)
fmt.Println("服务器初始化... (Ctrl-C 停止)")
lis, err := net.Listen("tcp", remote)
defer lis.Close()
if err != nil {
fmt.Println("监听端口发生错误: ", remote)
os.Exit(-1)
}
for {
conn, err := lis.Accept()
if err != nil {
fmt.Println("客户端连接错误: ", err.Error())
// os.Exit(0)
continue
}
//调用文件接收方法
go receiveFile(conn)
}
}
/*
*   文件接收方法
*   2013-09-26
*   LvanNeo
*
*   con 连接成功的客户端连接
*/
func receiveFile(con net.Conn) {
var (
res          string
tempFileName string                    //保存临时文件名称
data         = make([]byte, 1024*1024) //用于保存接收的数据的切片
by           []byte
databuf      = bytes.NewBuffer(by) //数据缓冲变量
fileNum      int                   //当前协程接收的数据在原文件中的位置
)
defer con.Close()
fmt.Println("新建立连接: ", con.RemoteAddr())
j := 0 //标记接收数据的次数
for {
length, err := con.Read(data)
if err != nil {
// writeend(tempFileName, databuf.Bytes())
da := databuf.Bytes()
// fmt.Println("over", fileNum, len(da))
fmt.Printf("客户端 %v 已断开. %2d %d \n", con.RemoteAddr(), fileNum, len(da))
return
}
if 0 == j {
res = string(data[0:8])
if "fileover" == res { //判断是否为发送结束指令,且结束指令会在第一次接收的数据中
xienum := int(data[8])
mergeFileName := string(data[9:length])
go mainMergeFile(xienum, mergeFileName) //合并临时文件,生成有效文件
res = "文件接收完成: " + mergeFileName
con.Write([]byte(res))
fmt.Println(mergeFileName, "文件接收完成")
return
} else { //创建临时文件
fileNum = int(data[0])
tempFileName = string(data[1:length]) + strconv.Itoa(fileNum)
fmt.Println("创建临时文件:", tempFileName)
fout, err := os.Create(tempFileName)
if err != nil {
fmt.Println("创建临时文件错误", tempFileName)
return
}
fout.Close()
}
} else {
// databuf.Write(data[0:length])
writeTempFileEnd(tempFileName, data[0:length])
}
res = strconv.Itoa(fileNum) + " 接收完成"
con.Write([]byte(res))
j++
}
}
/*
*   把数据写入指定的临时文件中
*   2013-09-26
*   LvanNeo
*
*   fileName    临时文件名
*   data        接收的数据
*/
func writeTempFileEnd(fileName string, data []byte) {
// fmt.Println("追加:", name)
tempFile, err := os.OpenFile(fileName, os.O_APPEND|os.O_RDWR, 0666)
if err != nil {
// panic(err)
fmt.Println("打开临时文件错误", err)
return
}
defer tempFile.Close()
tempFile.Write(data)
}
/*
*   根据临时文件数量及有效文件名称生成文件合并规则进行文件合并
*   2013-09-26
*   LvanNeo
*
*   connumber   临时文件数量
*   filename    有效文件名称
*/
func mainMergeFile(connumber int, filename string) {
file, err := os.Create(filename)
if err != nil {
fmt.Println("创建有效文件错误", err)
return
}
defer file.Close()
//依次对临时文件进行合并
for i := 0; i < connumber; i++ {
mergeFile(filename+strconv.Itoa(i), file)
}
//删除生成的临时文件
for i := 0; i < connumber; i++ {
os.Remove(filename + strconv.Itoa(i))
}
}
/*
*   将指定临时文件合并到有效文件中
*   2013-09-26
*   LvanNeo
*
*   rfilename   临时文件名称
*   wfile       有效文件
*/
func mergeFile(rfilename string, wfile *os.File) {
// fmt.Println(rfilename, wfilename)
rfile, err := os.OpenFile(rfilename, os.O_RDWR, 0666)
defer rfile.Close()
if err != nil {
fmt.Println("合并时打开临时文件错误:", rfilename)
return
}
stat, err := rfile.Stat()
if err != nil {
panic(err)
}
num := stat.Size()
buf := make([]byte, 1024*1024)
for i := 0; int64(i) < num; {
length, err := rfile.Read(buf)
if err != nil {
fmt.Println("读取文件错误")
}
i += length
wfile.Write(buf[:length])
}
}
//多协程文件传输客户端
//作者:LvanNeo
//邮箱:lvan_software@foxmail.com
//版本:1.0
//日期:2013-09-26
//对待发送文件进行拆分,由多个协程异步进行发送
package main
import (
"bufio"
"bytes"
"fmt"
"net"
"os"
"runtime"
"strconv"
"time"
)
func init() {
runtime.GOMAXPROCS(runtime.NumCPU())
}
func main() {
var (
host   = "192.168.1.8"     //服务端IP
port   = "9090"            //服务端端口
remote = host + ":" + port //构造连接串
fileName      = "node.exe" //待发送文件名称
mergeFileName = "mm.exe"   //待合并文件名称
coroutine     = 10         //协程数量或拆分文件的数量
bufsize       = 1024       //单次发送数据的大小
)
//获取参数信息。
//参数顺序:
// 1:待发送文件名
// 2:待合并文件名
// 3:单次发送数据大小
// 4:协程数量或拆分文件数量
for index, sargs := range os.Args {
switch index {
case 1:
fileName = sargs
mergeFileName = sargs
case 2:
mergeFileName = sargs
case 3:
bufsize, _ = strconv.Atoi(sargs)
case 4:
coroutine, _ = strconv.Atoi(sargs)
}
}
fmt.Printf("请输入服务端IP: ")
reader := bufio.NewReader(os.Stdin)
ipdata, _, _ := reader.ReadLine()
host = string(ipdata)
remote = host + ":" + port
fl, err := os.OpenFile(fileName, os.O_RDWR, 0666)
if err != nil {
fmt.Println("userFile", err)
return
}
stat, err := fl.Stat() //获取文件状态
if err != nil {
panic(err)
}
var size int64
size = stat.Size()
fl.Close()
littleSize := size / int64(coroutine)
fmt.Printf("Size: %d  %d \n", size, littleSize)
begintime := time.Now().Unix()
//对待发送文件进行拆分计算并调用发送方法
c := make(chan string)
var begin int64 = 0
for i := 0; i < coroutine; i++ {
if i == coroutine-1 {
go splitFile(remote, c, i, bufsize, fileName, mergeFileName, begin, size)
fmt.Println(begin, size, bufsize)
} else {
go splitFile(remote, c, i, bufsize, fileName, mergeFileName, begin, begin+littleSize)
fmt.Println(begin, begin+littleSize)
}
begin += littleSize
}
//同步等待发送文件的协程
for j := 0; j < coroutine; j++ {
fmt.Println(<-c)
}
midtime := time.Now().Unix()
sendtime := midtime - begintime
fmt.Printf("发送耗时:%d 分 %d 秒 \n", sendtime/60, sendtime%60)
sendMergeCommand(remote, mergeFileName, coroutine) //发送文件合并指令及文件名
endtime := time.Now().Unix()
mergetime := endtime - midtime
fmt.Printf("合并耗时:%d 分 %d 秒 \n", mergetime/60, mergetime%60)
tot := endtime - begintime
fmt.Printf("总计耗时:%d 分 %d 秒 \n", tot/60, tot%60)
}
/*
*   文件拆分发送方法
*   2013-09-26
*   LvanNeo
*
*   remote 服务端IP及端口号(如:192.168.1.8:9090)
*   c               channel,用于同步协程
*   coroutineNum    协程顺序或拆分文件的顺序
*   size            发送数据的大小
*   fileName        待发送的文件名
*   mergeFileName   待合并的文件名
*   begin           当前协程拆分待发送文件中的开始位置
*   end             当前协程拆分待发送文件中的结束位置
*/
func splitFile(remote string, c chan string, coroutineNum int, size int, fileName, mergeFileName string, begin int64, end int64) {
con, err := net.Dial("tcp", remote)
defer con.Close()
if err != nil {
fmt.Println("服务器连接失败.")
os.Exit(-1)
return
}
fmt.Println(coroutineNum, "连接已建立.文件发送中...")
var by [1]byte
by[0] = byte(coroutineNum)
var bys []byte
databuf := bytes.NewBuffer(bys) //数据缓冲变量
databuf.Write(by[:])
databuf.WriteString(mergeFileName)
bb := databuf.Bytes()
// bb := by[:]
// fmt.Println(bb)
in, err := con.Write(bb) //向服务器发送当前协程的顺序,代表拆分文件的顺序
if err != nil {
fmt.Printf("向服务器发送数据错误: %d\n", in)
os.Exit(0)
}
var msg = make([]byte, 1024)  //创建读取服务端信息的切片
lengthh, err := con.Read(msg) //确认服务器已收到顺序数据
if err != nil {
fmt.Printf("读取服务器数据错误.\n", lengthh)
os.Exit(0)
}
// str := string(msg[0:lengthh])
// fmt.Println("服务端信息:",str)
//打开待发送文件,准备发送文件数据
file, err := os.OpenFile(fileName, os.O_RDWR, 0666)
defer file.Close()
if err != nil {
fmt.Println(fileName, "-文件打开错误.")
os.Exit(0)
}
file.Seek(begin, 0) //设定读取文件的位置
buf := make([]byte, size) //创建用于保存读取文件数据的切片
var sendDtaTolNum int = 0 //记录发送成功的数据量(Byte)
//读取并发送数据
for i := begin; int64(i) < end; i += int64(size) {
length, err := file.Read(buf) //读取数据到切片中
if err != nil {
fmt.Println("读文件错误", i, coroutineNum, end)
}
//判断读取的数据长度与切片的长度是否相等,如果不相等,表明文件读取已到末尾
if length == size {
//判断此次读取的数据是否在当前协程读取的数据范围内,如果超出,则去除多余数据,否则全部发送
if int64(i)+int64(size) >= end {
sendDataNum, err := con.Write(buf[:size-int((int64(i)+int64(size)-end))])
if err != nil {
fmt.Printf("向服务器发送数据错误: %d\n", sendDataNum)
os.Exit(0)
}
sendDtaTolNum += sendDataNum
} else {
sendDataNum, err := con.Write(buf)
if err != nil {
fmt.Printf("向服务器发送数据错误: %d\n", sendDataNum)
os.Exit(0)
}
sendDtaTolNum += sendDataNum
}
} else {
sendDataNum, err := con.Write(buf[:length])
if err != nil {
fmt.Printf("向服务器发送数据错误: %d\n", sendDataNum)
os.Exit(0)
}
sendDtaTolNum += sendDataNum
}
//读取服务器端信息,确认服务端已接收数据
lengths, err := con.Read(msg)
if err != nil {
fmt.Printf("读取服务器数据错误.\n", lengths)
os.Exit(0)
}
// str := string(msg[0:lengths])
// fmt.Println("服务端信息:",str)
}
fmt.Println(coroutineNum, "发送数据(Byte):", sendDtaTolNum)
c <- strconv.Itoa(coroutineNum) + " 协程退出"
}
/*
*   向服务端发送待合并文件的名称及合并指令
*   2013-09-26
*   LvanNeo
*
*   remote          服务端IP及端口号(如:192.168.1.8:9090)
*   mergeFileName   待合并的文件名
*   coroutine       拆分文件的总个数
*/
func sendMergeCommand(remote, mergeFileName string, coroutine int) {
con, err := net.Dial("tcp", remote)
defer con.Close()
if err != nil {
fmt.Println("服务器连接失败.")
os.Exit(-1)
return
}
fmt.Println("连接已建立. 发送合并指令.\n文件合并中...")
var by [1]byte
by[0] = byte(coroutine)
var bys []byte
databuf := bytes.NewBuffer(bys) //数据缓冲变量
databuf.WriteString("fileover")
databuf.Write(by[:])
databuf.WriteString(mergeFileName)
cmm := databuf.Bytes()
in, err := con.Write(cmm)
if err != nil {
fmt.Printf("向服务器发送数据错误: %d\n", in)
}
var msg = make([]byte, 1024)
lengthh, err := con.Read(msg)
if err != nil {
fmt.Printf("读取服务器数据错误.\n", lengthh)
os.Exit(0)
}
str := string(msg[0:lengthh])
fmt.Println("传输完成(服务端信息): ", str)
}

Golang多线程文件传输相关推荐

  1. C#实现HTTP协议:多线程文件传输

    很多人都有过使用网络蚂蚁或网络快车互联网文件的经历,这些软件的使用可以大大加速互联网上文件的传输速度,减少文件传输的时间.这些软件为什么有如此大的魔力呢?其主要原因是这些软件都采用了多线程下载和断点续 ...

  2. 【ASP.net文档】用C#实现HTTP协议下的多线程文件传输

    很多人都有过使用网络蚂蚁或网络快车软件下载互联网文件的经历,这些软件的使用可以大大加速互联网上文件的传输速度,减少文件传输的时间.这些软件为什么有如此大的魔力呢?其主要原因是这些软件都采用了多线程下载 ...

  3. Socket 实现非阻塞式多线程文件传输(jpg mov 等各种格式)

    Socket 基础 非阻塞式TCP socket 实现文件传输, 实测 传输5M的jpg , 30M的 NEF(单反原图) 以及 1G以上的mov文件,均正常接收 客户端可多开,服务器多线程实现服务器 ...

  4. c++实现文件传输之三:断点续传与多线程传输

    继木马编程DIY的上两篇,现在我们开始讨论断点续传与多线程文件传输的实现.其实这两项功能是下载软件所 必不可少的功能了,现在我们把它加到自己的木马中来感受感受.提到多线程下载,首先向网络蚂蚁的作者 洪 ...

  5. c++实现文件传输之三:断点续传与多线程传输转

    转载自:http://blog.csdn.net/zhengkangchen/article/details/3942252 继木马编程DIY的上两篇,现在我们开始讨论断点续传与多线程文件传输的实现. ...

  6. java文件传输(JAVA文件传输的好处)

    JAVA 传输文件 //以前写的一个文件传输的小程序,有客户端和服务器端两部分,服务器可//以一直运行,客户端传输完一个后退出,当然你也可以根据你的需要改. //服务器端可以支持多个客户端同时上传,用 ...

  7. 高仿微信局域网聊天V5版本-无需服务器实现,支持多线程文件收发和跨平台运行

    高仿微信局域网聊天V5版本-无需服务器实现,支持多线程文件收发和跨平台运行 近年来,随着人们对通信技术和网络的需求日益增长,基于局域网的即时通讯软件已经成为了当今社会中不可或缺的一部分.其中,微信是最 ...

  8. c++ udp多线程 例子_[内附完整源码和文档] 基于udp实现tcp功能进行大文件传输

    一.项目要求 Please choose one of following programing languages: C, C++, Java, Python; 本项目采用的是python3.6 L ...

  9. 网络编程 UDP通信的过程 TCP通信过程 多线程文件上传

    网络概述 协议 在网络之间传出数据时需要按照指定的标准来传输,标准中规定了数据的格式.大小.传输的方式.传输速率.形成统一规范->按照规范开发的代码->协议(应用层.传输层.网络层.链路层 ...

  10. 一步一步做一个linux文件传输软件(一)

    曾经在linux上实现过一个文件传输软件,客户端可以向服务器一次传输多个文件或者图片,并且在客户端可以看到文件传输的进度. 功能非常简单,但是涉及到的知识挺多的:GDB的调试:socket编程:多线程 ...

最新文章

  1. 使用三防漆来保护PCB的敷铜面
  2. shardingjdbc全局表_Sharding-JDBC动态分表实现
  3. fastjson 返回json字符串,JSON.parse 报错
  4. 两种通过代码访问SalesOrder header text内容的办法
  5. 【Elasticsearch】es 插入数据 性能优化 以及 影响插入的因素
  6. android spinner保存对象,Android Spinner默认值问题
  7. 2018福布斯全球科技女性TOP 50榜单:李飞飞、滴滴柳青上榜
  8. IHttpModule与IHttpHandler的区别整理
  9. AngularJS Documents 官方英文文档
  10. 【九天教您南方cass 9.1】 08 绘制等高线及对其处理
  11. 阿里云 ECS Ubuntu 14.04 无法访问之磁盘 IO 跑满问题排查
  12. 交换机cad图例_网络交换机cad图
  13. 纯php 给pdf加水印,如何使用PHP为现有PDF文件添加水印?
  14. 光耦电流传输比(CTR)的理解
  15. easyswoole not controller class match
  16. “StarRocks 极客营” 重磅来袭,和技术大牛一起推开数据库梦想之门!
  17. 苹果怎么设置下载软件不要密码?手机技巧分享
  18. C/C++---字符分布分割得到数字,适用于STM32/ESP32等等
  19. 植被覆盖度(FVC)计算
  20. MRCC和SRCC的理解

热门文章

  1. CVPR2021投稿要求
  2. android-keystool
  3. Win10强制更新关闭方法
  4. 介绍产品(软件开发)比较好用的工具(项目管理、文件整理等)
  5. 如何利用html+css动画 实现水墨动画?
  6. 关于Outline Effect 高亮插件不能生效-爻览SDK-MR混合现实开发日志
  7. win10 纯净版系统如何添加字体的方法
  8. 明星隐私倒卖链:只要150 你对朱一龙行踪比他妈还了解
  9. 论文写不下去时怎么办?
  10. 电子宠物游戏(附C++源码)