英文:
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 (
"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)
}
}
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
函数。
你还忽略了ReadLine
的isPrefix
返回值。如果你不打算处理该方法的所有情况,我建议按照文档使用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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论