Golang多线程文件传输

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)
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值