Golang TCP文件传输在中途卡住了。

huangapple go评论62阅读模式
英文:

Golang TCP File Transfer Gets Stuck In the Middle

问题

我在使用go语言进行TCP文件传输时遇到了一些问题。文件传输有时候可以正常工作,有时候会在中途卡住。当它卡住时,看起来它在通信通道中等待数据,但是既没有数据也没有错误。因此,它会无限期地卡住。让事情更加混乱的是,对于同一个文件,有时候可以正常工作,有时候又不能正常工作。

这是我的程序工作方式。它会监听传入的请求。请求以JSON格式发送。根据请求类型,它会执行不同的操作。我将贴出与文件传输相关的代码片段。

server.go

package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	_ "io"
	"net"
	"os"
)

const (
	COMMAND_RECEIVE_FILE = "TRANSFER_FILE"
	COMMAND_EXIT         = "EXIT"

	CONNECTION_TYPE = "tcp"
	CONNECTION_PORT = "3645"
	CONNECTION_HOST = ""
	BUFFER_SIZE     = 1024
)

type Command struct {
	Identifier string `json:"identifier"`
	Name       string `json:"name"`
	Size       int64  `json:"size"`
}

type Result struct {
	Message string `json:"message"`
}

func receiveFile(connection net.Conn, fileName string, fileSize int64) Result {
	fmt.Println("Receiving file")
	result := Result{Message: ""}

	file, err := os.Create(fileName)
	if err != nil {
		fmt.Println(err)
		result.Message = "Error opening file: " + fileName
		return result
	}

	defer file.Close()

	fileBuffer := make([]byte, BUFFER_SIZE)
	bytesRead := int64(0)
	count := 0
	for {
		if fileSize-bytesRead < int64(BUFFER_SIZE) {
			fileBuffer = make([]byte, fileSize-bytesRead)
		}

		fmt.Println("Reading ", BUFFER_SIZE, " bytes of data")
		n, err := connection.Read(fileBuffer)
		count++
		fmt.Println("Completed reading", n, " bytes of data, count=", count)
		file.Write(fileBuffer[0:n])
		bytesRead += int64(n)

		if err != nil {
			result.Message = "File transfer incomplete"
			break
		}

		if bytesRead >= fileSize {
			result.Message = "File transfer complete"
			break
		}
	}

	file.Chmod(0777)

	return result
}

func main() {
	ln, err := net.Listen(CONNECTION_TYPE, CONNECTION_HOST+":"+CONNECTION_PORT)
	if err != nil {
		fmt.Println("error opening a tcp connection")
	}

	for {
		fmt.Println("waiting for new connection")
		conn, err := ln.Accept()
		if err != nil {

		} else {
			var commandStr string
			reader := bufio.NewReader(conn)

			var exitStatus = 1
			for exitStatus == 1 {
				fmt.Println("Waiting for new command: ")
				line, _, err := reader.ReadLine()
				if err != nil {
					conn.Close()
					exitStatus = 0
					break
				} else {
					fmt.Println("Size read :", len(line))
				}
				commandStr = string(line)
				fmt.Println("CommandStr: ", commandStr)

				var msg Command
				err = json.Unmarshal([]byte(commandStr), &msg)
				if err != nil {
					fmt.Println("Error")
					conn.Close()
					break
				}

				result := Result{}
				fmt.Println("Received new command: ", msg.Identifier)
				switch msg.Identifier {

				case COMMAND_RECEIVE_FILE:
					result = receiveFile(conn, msg.Name, msg.Size)

				case COMMAND_EXIT:
					exitStatus = 0
					conn.Close()
				default:
					result = Result{Message: "Unrecognized command"}
				}

				out, _ := json.Marshal(result)
				fmt.Fprint(conn, string(out)+"\n")
			}
		}
	}
}

test.go

package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"strings"
	"time"
)

const (
	COMMAND_TRANSFER_FILE = "TRANSFER_FILE"
	COMMAND_EXIT          = "EXIT"

	CONNECTION_TYPE = "tcp"
	CONNECTION_PORT = "3645"
	CONNECTION_HOST = ""
)

type Command struct {
	Identifier string `json:"identifier"`
	Name       string `json:"name"`
	Size       int64  `json:"size"`
}

type Result struct {
	Message string `json:"message"`
}

func main() {
	conn, _ := net.Dial(CONNECTION_TYPE, CONNECTION_HOST+":"+CONNECTION_PORT)
	decoder := json.NewDecoder(conn)
	com := Command{}

	sourceFileName := ""
	destinationFileName := ""
	for {
		com = Command{}
		reader := bufio.NewReader(os.Stdin)
		identifier, _ := reader.ReadString('\n')
		com.Identifier = strings.TrimSpace(identifier)

		switch com.Identifier {
		case COMMAND_TRANSFER_FILE:
			fmt.Print("Source file name:")
			sourceFileName, _ = reader.ReadString('\n')
			sourceFileName = strings.TrimSpace(sourceFileName)

			fmt.Print("Destination file name:")
			destinationFileName, _ = reader.ReadString('\n')
			com.Name = strings.TrimSpace(destinationFileName)

			file, err := os.Open(sourceFileName)
			if err != nil {
				log.Fatal(err)
			}
			defer file.Close()

			fileInfo, err := file.Stat()
			fileSize := fileInfo.Size()
			com.Size = fileSize

		case COMMAND_EXIT:
			conn.Close()
			os.Exit(0)
		}

		out, _ := json.Marshal(com)
		conn.Write([]byte(string(out) + "\n"))

		if strings.Compare(com.Identifier, COMMAND_TRANSFER_FILE) == 0 {
			file, err := os.Open(sourceFileName)
			if err != nil {
				log.Fatal(err)
			}
			defer file.Close()

			n, err := io.Copy(conn, file)
			if err != nil {
				log.Fatal(err)
			}
			fmt.Println(n, "bytes sent")
		}

		var msg Result
		err := decoder.Decode(&msg)
		if err != nil {
			fmt.Println(err)
		}
		fmt.Println(msg)
	}
}

我在Linux和Windows上都进行了测试,结果在两个系统上都表现出相同的行为。我唯一能想到的是发送方比接收方快,即使我在同一台机器上运行。如果是这种情况,除了握手机制之外,还有什么最佳实践可以解决这个问题呢?

英文:

I'm having some file transfer issue over TCP in go. The file transfer works sometimes and sometimes it gets stuck in the middle. When it gets stuck, it looks like it is expecting data in the communication channel but there is no data and no error as well. Hence it gets stuck indefinitely. To make thing confusing it shows this behavior for same file i.e for same file it works sometimes and sometimes it doesn't work.

This is how my program works. It'll listen for incoming requests. The requests are in JSON format. Based on request type it'll do different operation. I'm posting the code segment related to file transfer.

server.go

package main
import (
&quot;bufio&quot;
&quot;encoding/json&quot;
&quot;fmt&quot;
_&quot;io&quot;
&quot;net&quot;
&quot;os&quot;
)
const (
COMMAND_RECEIVE_FILE    = &quot;TRANSFER_FILE&quot;
COMMAND_EXIT            = &quot;EXIT&quot;
CONNECTION_TYPE = &quot;tcp&quot;
CONNECTION_PORT = &quot;3645&quot;
CONNECTION_HOST = &quot;&quot;
BUFFER_SIZE     = 1024
)
type Command struct {
Identifier string `json:&quot;identifier&quot;`
Name       string `json:&quot;name&quot;`
Size       int64  `json:&quot;size&quot;`
}
type Result struct {
Message     string        `json:&quot;message&quot;`
}
func receiveFile(connection net.Conn, fileName string, fileSize int64) Result {
fmt.Println(&quot;Receiving file&quot;)
result := Result{Message: &quot;&quot;}
file, err := os.Create(fileName)
if err != nil {
fmt.Println(err)
result.Message = &quot;Error opening file: &quot; + fileName
return result
}
defer file.Close()
fileBuffer := make([]byte, BUFFER_SIZE)
bytesRead := int64(0)
count := 0
for {
if fileSize-bytesRead &lt; int64(BUFFER_SIZE) {
fileBuffer = make([]byte, fileSize-bytesRead)
}
fmt.Println(&quot;Reading &quot;, BUFFER_SIZE, &quot; bytes of data&quot;)
n, err := connection.Read(fileBuffer)
count++
fmt.Println(&quot;Completed reading&quot;, n, &quot; bytes of data, count=&quot;, count)
file.Write(fileBuffer[0:n])
bytesRead += int64(n)
if err != nil {
result.Message = &quot;File transfer incomplete&quot;
break
}
if bytesRead &gt;= fileSize {
result.Message = &quot;File transfer complete&quot;
break
}
}
file.Chmod(0777)
return result
}
func main() {
ln, err := net.Listen(CONNECTION_TYPE, CONNECTION_HOST + &quot;:&quot;+CONNECTION_PORT)
if err != nil {
fmt.Println(&quot;error opening a tcp connection&quot;)
}
for {
fmt.Println(&quot;waiting for new connection&quot;)
conn, err := ln.Accept()
if err != nil {
} else {
var commandStr string
reader := bufio.NewReader(conn)
var exitStatus = 1
for exitStatus == 1 {
fmt.Println(&quot;Waiting for new command: &quot;)
line,_,err := reader.ReadLine()
if err != nil {
conn.Close()
exitStatus = 0
break
} else {
fmt.Println(&quot;Size read :&quot;, len(line))
}
commandStr = string(line)
fmt.Println(&quot;CommandStr: &quot;, commandStr)
var msg Command
err = json.Unmarshal([]byte(commandStr), &amp;msg)
if err != nil {
fmt.Println(&quot;Error&quot;)
conn.Close()
break
}
result := Result{}
fmt.Println(&quot;Received new command: &quot;, msg.Identifier)
switch msg.Identifier {
case COMMAND_RECEIVE_FILE:
result = receiveFile(conn, msg.Name, msg.Size)
case COMMAND_EXIT:
exitStatus = 0
conn.Close()
default:
result = Result{Message: &quot;Unrecognized command&quot;}
}
out, _ := json.Marshal(result)
fmt.Fprint(conn, string(out)+&quot;\n&quot;)
}
}
}
}

test.go

package main
import (
&quot;bufio&quot;
&quot;encoding/json&quot;
&quot;fmt&quot;
&quot;io&quot;
&quot;log&quot;
&quot;net&quot;
&quot;os&quot;
&quot;strings&quot;
_&quot;time&quot;
)
const (
COMMAND_TRANSFER_FILE   = &quot;TRANSFER_FILE&quot;
COMMAND_EXIT            = &quot;EXIT&quot;
CONNECTION_TYPE = &quot;tcp&quot;
CONNECTION_PORT = &quot;3645&quot;
CONNECTION_HOST = &quot;&quot;
)
type Command struct {
Identifier string `json:&quot;identifier&quot;`
Name       string `json:&quot;name&quot;`
Size       int64  `json:&quot;size&quot;`
}
type Result struct {
Message     string        `json:&quot;message&quot;`
}
func main() {
conn, _ := net.Dial(CONNECTION_TYPE, CONNECTION_HOST + &quot;:&quot; + CONNECTION_PORT)
decoder := json.NewDecoder(conn)
com := Command{}
sourceFileName := &quot;&quot;
destinationFileName := &quot;&quot;
for {
com = Command{}
reader := bufio.NewReader(os.Stdin)
identifier, _ := reader.ReadString(&#39;\n&#39;)
com.Identifier = strings.TrimSpace(identifier)
switch com.Identifier {
case COMMAND_TRANSFER_FILE:
fmt.Print(&quot;Source file name:&quot;)
sourceFileName, _ = reader.ReadString(&#39;\n&#39;)
sourceFileName = strings.TrimSpace(sourceFileName)
fmt.Print(&quot;Destination file name:&quot;)
destinationFileName, _ = reader.ReadString(&#39;\n&#39;)
com.Name = strings.TrimSpace(destinationFileName)
file, err := os.Open(sourceFileName)
if err != nil {
log.Fatal(err)
}
defer file.Close()
fileInfo, err := file.Stat()
fileSize := fileInfo.Size()
com.Size = fileSize
case COMMAND_EXIT:
conn.Close()
os.Exit(0)
}
out, _ := json.Marshal(com)
conn.Write([]byte(string(out) + &quot;\n&quot;))
if strings.Compare(com.Identifier, COMMAND_TRANSFER_FILE) == 0 {
file, err := os.Open(sourceFileName)
if err != nil {
log.Fatal(err)
}
defer file.Close()
n, err := io.Copy(conn, file)
if err != nil {
log.Fatal(err)
}
fmt.Println(n, &quot;bytes sent&quot;)
}
var msg Result
err := decoder.Decode(&amp;msg)
if err != nil {
fmt.Println(err)
}
fmt.Println(msg)
}
}

I tested it on both Linux and Windows and it shows same behavior on both system. The only thing I can think of is that the sender is faster than the receiver even though I'm running it on the same machine. If that is the case, what will be a best practice to solve it other than the handshaking mechanism.

答案1

得分: 2

你不能将net.Conn包装在bufio.Reader中,然后继续使用net.Conn。你的函数被阻塞的原因是因为你在reader中留下了缓冲的数据,所以你永远无法达到所需的消息大小。

为了不丢失缓冲的数据,你需要将reader传递给receiveFile函数。

你还忽略了ReadLineisPrefix返回值。如果你不打算处理该方法的所有情况,我建议按照文档使用ReadBytes

英文:

You can't wrap the net.Conn in a bufio.Reader, then continue to use the net.Conn. The reason your function is blocked is because you left data buffered in the reader, so you won't ever reach the desired message size.

You need to pass the reader to the receiveFile function in order to not lose the buffered data.

You are also ignoring the isPrefix return value from ReadLine. I would follow the documentation and use ReadBytes instead if you're not going to handle all cases from that method.

huangapple
  • 本文由 发表于 2017年2月16日 04:27:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/42259364.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定