Golang中TCP套接字陷入io.EOF错误的解决方法
Golang中TCP套接字陷入io.EOF错误的解决方法 我的目标是构建一个Go语言套接字服务器。该服务器将接收来自已连接设备的数据。设备将以十六进制格式发送数据,例如:
000000000000006008010000018063BE5660003BD7AAC40337AAFCFFED00B5090015001609EF01F00150011503C80045010101B300713E0A42392D180015430E6F44008C0900AD0D029E11003912FFDA13FFFE0F03E803C700000697100008D9E70C0000895600010000E5F2
以下是我的Java代码片段,它运行得很好。
while (true) {
if((firstByte=r.readByte()) != -1){
if (firstByte == 0x00) {
// rest of the logic
}
}
在Java中,我的做法是:一旦设备连接,我就逐个字节地读取,然后根据特定逻辑来处理一组字节。我试图在Go语言中实现相同的功能,以下是我的Go代码。
package main
import (
"fmt"
"net"
"os"
"bufio"
"io"
)
const (
SERVER_HOST = "localhost"
SERVER_PORT = "9999"
SERVER_TYPE = "tcp"
)
func main() {
fmt.Println("Server Running...")
server, err := net.Listen("tcp", ":9999")
if err != nil {
fmt.Println("Error listening:", err.Error())
os.Exit(1)
}
defer server.Close()
fmt.Println("Listening on " + SERVER_HOST + ":" + SERVER_PORT)
fmt.Println("Waiting for client...")
for {
connection, err := server.Accept()
if err != nil {
fmt.Println("Error accepting: ", err.Error())
os.Exit(1)
}
fmt.Println("client connected")
go processClient(connection)
}
}
func processClient(connection net.Conn) {
r := bufio.NewReader(connection)
for {
//a := -1
line, err := r.ReadBytes(byte(0xff))
switch err {
case nil:
break
case io.EOF:fmt.Println("IO EOF", err)
default:
fmt.Println("ERROR", err)
}
//str:=string(line)
// do something with the data, in this case echo it back
fmt.Printf(" TRY PINT NOW %s\n", line)
//fmt.Println("Output binary is what we have read so far %v \n", line)
//fmt.Println("Output hex is what we have read so far %x \n", line)
//connection.Write(line)
}
connection.Close()
}
有人能帮我看看我的Go版本哪里出错了吗?我试图先复制相同的基本逻辑,然后再进一步完成它。
更多关于Golang中TCP套接字陷入io.EOF错误的解决方法的实战教程也可以访问 https://www.itying.com/category-94-b0.html
是的,这是两种不同的情况。这些确实是可能遇到的情形,当然还有其他一些情况。但为了简化问题,我想首先只关注这两种类型。
更多关于Golang中TCP套接字陷入io.EOF错误的解决方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
你好 Skillian,
谢谢,我会尝试你的解决方案。不过,正确关闭这个连接的地方是哪里?是在最顶行使用 defer.close,还是我应该把它留在底部?
以下是你寻找的仅有的两种情况,还是你打算寻找其他的“标签”?
- 0x00, 0x00, 0x00, 0x00
- 0x00, 0x0f
你好 Skillian,
谢谢,我刚读完关于 defer 的深入内容,它确实在完成最终清理工作方面非常强大。那么在我的案例中,我放置 defer 的位置是正确的,对吧?是的,我有 Java 背景,所以像你提到的 try-catch 这样的概念更容易理解和关联。我打算在你的解决方案基础上进一步扩展。假设在我的案例中,我还需要使用数据库连接。我猜最好在 main 函数中建立连接。然后在 processClient 中使用 defer 来关闭连接,对吗?
好问题:我认为我的建议是将其放在 defer 语句中,这样,如果在你的连接处理程序中发生任何意外的 panic(也许在你添加额外的函数调用时),你可以确保连接会被关闭。我唯一看到清理代码不放在 defer 中的情况是在对性能要求极高的代码中(例如,一些锁定和解锁互斥锁的代码)。但对于 I/O 操作,比如关闭网络连接,我认为最好使用 defer。
// 示例代码:使用 defer 确保连接关闭
func handleConnection(conn net.Conn) {
defer conn.Close()
// ... 处理连接的代码 ...
}
我复制了你的代码并运行,看起来问题在于请求处理完毕并收到 EOF 后,你的程序会一直打印:
IO EOF EOF
TRY PINT NOW
无限循环。这是因为你没有跳出 for { ... } 循环,所以 r.ReadBytes 会一直被调用,并且每次都处于 EOF 状态。我稍微调整了一下错误处理的结构:
line, err := r.ReadBytes(byte(0xff))
if errors.Is(err, io.EOF) {
fmt.Println("IO EOF", err)
break
}
if err != nil {
fmt.Println("ERROR", err)
break
}
现在代码在遇到错误时会中断。
请查看 Go by Example: Defer。如果你熟悉 Python、C# 或 Java 等其他语言,defer 在这些语言中相当于 try ... finally。在 Java 这样的语言中,我想你会这样写(免责声明:我其实不懂 Java):
class ConnectionProcessor {
public void processClient(connection Connection) {
try {
// 函数体
} finally {
connection.close()
}
}
}
在 Go 中,你会这样做:
func processClient(connection net.Conn) {
defer connection.Close()
// 函数体
}
我的理解是,这样做的动机是为了确保清理操作总是在获取资源的地方附近明确指定。例如,文件也必须显式关闭,所以你可以这样做:
func doSomethingWithFilename(filename string) error {
f, err := os.Open(filename)
if err != nil {
return fmt.Errorf("failed to open %q: %w", filename, err)
}
defer f.Close()
// 对文件 f 进行一些操作。
// 如果这段代码很长,你也不必担心忘记关闭 f,因为它在 f 打开后立即就安排好了。
}
你好 Skillian,
抱歉,我有点新手。我注意到很多例子,比如数据库、socket连接等,都使用了 defer close()。我仍然不太明白,如果我们进行普通的关闭操作与使用 defer 相比,好处是什么?另外,我注意到 defer 是在你打开连接后立即放在最顶部的,对吗?那么在这种情况下,这是正确的关闭位置吗?
func processClient(connection net.Conn) {
defer.close()
r := bufio.NewReader(connection)
for {
//a := -1
line, err := r.ReadBytes(byte(0xff))
switch err {
case nil:
break
case io.EOF:fmt.Println("IO EOF", err)
default:
fmt.Println("ERROR", err)
}
//str:=string(line)
// do something with the data, in this case echo it back
fmt.Printf(" TRY PINT NOW %s\n", line)
//fmt.Println("Output binary is what we have read so far %v \n", line)
//fmt.Println("Output hex is what we have read so far %x \n", line)
//connection.Write(line)
}
}
你好 Skillian,
我理解了你对 x := doSomething() 的解释。那么,在 Go 语言中,为了确保将来不会遇到任何问题,哪种方法是正确的呢?
也许提供一个更具体的例子能更好地说明问题。以下是我将从设备读取的示例数据类型。
000000000000006008010000018063BE5660003BD7AAC40337AAFCFFED00B5090015001609EF01F00150011503C80045010101B300713E0A42392D180015430E6F44008C0900AD0D029E11003912FFDA13FFFE0F03E803C700000697100008D9E70C0000895600010000E5F2
这个例子是为了满足第一个条件,我现在已经嵌套了那些 if 语句。问题是,我需要先读取第一个 00,然后下一个是 00 吗?再下一个是 00,再下一个是 00。所以,如果前 4 个字节都是 00,那么我就开始处理这种类型的数据。否则,还会有其他类型的数据,其第一个字节是 00,但下一个是 0F,这就是我写 else if secondByte == 15 这行代码的原因。也许你能建议一个更高效的方法?
newbiegolang:
但对于数据库。我读到的是这样:他们说数据库创建会创建一个连接池,然后在函数中最好关闭连接池以释放资源,但也有一些矛盾的说法认为不需要关闭。
是的,*sql.DB 是一个连接池,如果该池中的连接将在服务器进程的整个生命周期中使用,那么就不需要显式地打开和关闭它们。同一个 *sql.DB 对象可以并发使用。
newbiegolang:
基于我目前的 Java 解决方案,我逐字节读取,对于 Go 你有什么建议?
你可以使用 (*bufio.Reader).ReadByte 而不是 (*bufio.Reader).ReadBytes。ReadByte 会返回一个单独的 byte 和一个 error。根据你的例子,在我看来 Java 在无法读取字节时返回 -1,但在 Go 中,你会得到一个 error。
errors 是标准库中的一个包名,它定义了 errors.Is 函数,我用来检查一个错误是否表示文件结束(“EOF”)条件。大多数代码在遇到文件结束条件时直接返回 io.EOF,但我通常使用 if errors.Is(err, io.EOF) { ... } 而不是 if err == io.EOF { ... },以防错误在某个地方被包装,例如:
func myReader(r io.Reader) error {
n, err := r.Read(buffer)
if err == io.EOF {
// myReader 不直接返回 io.EOF。相反,它将 io.EOF 包装到一个包含更多信息的新错误中:
return fmt.Errorf("unexpected EOF while reading %v: %w", r, err)
}
}
func readSomething(something io.Reader) error {
if err := myReader(something); err != nil {
// 这个函数 readSomething,在 myReader 返回 io.EOF 时是可以接受的。
// 问题是下面的 `if err == io.EOF` 会是 `false`
// 因为 myReader 不直接返回 io.EOF。相反,它
// 返回一个"包装"了 io.EOF 的新错误(参见 fmt.Errorf 使用 %w 时的文档)并带有更多信息。
if err == io.EOF {
// 这段代码永远不会执行:
//
// 与其使用 `if err == io.EOF`,readSomething 应该
// 使用 `if errors.Is(err, io.EOF)`,它会解开 err 并看到内部错误是 io.EOF。
return nil
}
if errors.Is(err, io.EOF) {
// 当 myReader 返回 fmt.Errorf("unexpected eof ...") 时,这段代码会执行
return nil
}
return err
}
// 做其他事情...
}
在这种情况下,我甚至没有声明
imeiLength,但它却可以工作?但有时它却告诉我未定义的错误?
这是因为你使用了 := 而不是仅仅使用 =。x := doSomething() 等同于定义一个新的 x 变量,其类型与 doSomething 返回的类型完全一致,并将其值设置为 doSomething() 的结果。因此,如果 doSomething() 返回一个 int,它就等同于 var x int = doSomething();但如果 doSomething() 返回一个 error,那么它就等同于 var x error = doSomething()。x 的类型是从 doSomething 的类型推导出来的。
如果你使用 x = doSomething() 而不是 :=,那么如果 x 尚未定义,你就会得到一个错误。
乍一看,你觉得这段代码在 Go 语言中没问题吗?
我不熟悉你正在读取的协议,所以无法确定你做的正确与否,但我可以提出一些额外的建议并指出一些推荐的修改!
我们通常尽量避免嵌套的 if 语句,所以与其写这样的代码:
if (thing1 == whatWeWant1) {
if (thing2 == whatWeWant2) {
if (thing3 == whatWeWant3) {
// 条件满足:在此处运行代码。
} else {
// 处理错误 3
}
} else {
// 处理错误 2
}
} else {
// 处理错误 1
}
我们通常会这样写:
if thing1 != whatWeWant1 {
// 处理错误 1
}
if thing2 != whatWeWant2 {
// 处理错误 2
}
if thing3 != whatWeWant3 {
// 处理错误 3
}
// 条件满足:在此处运行代码。
这样,“正常”的条件就不会缩进,并且你可以从上到下阅读,而不是在查看错误条件时先向内斜着读,然后再向外读。
在你的代码中,你有:
dataFieldLengthArray := make([]byte, 4)
dataFieldLength, errDataFieldLengthArray := r.Read(dataFieldLengthArray)
// ...
stringDataFieldLengthArray := string(dataFieldLengthArray)
intDataFieldLength, errStrconvAtoi := strconv.Atoi(stringDataFieldLengthArray)
这段代码会读取 4 个字节的 ASCII 文本并将其解析为一个十进制数字(例如,'0'、'1'、'2'、'3'(0x30、0x31、0x32、0x33)将被解析为 int 类型的 123)。根据这个协议看起来的低层程度,我认为你原本的意思可能是从流中读取 4 个字节,并将这些字节(可能是网络字节序?)解释为一个包含长度的 32 位整数。如果这是你想要的,那么在将字节读入 dataFieldLengthArray 之后,你应该使用 encoding/binary.BigEndian.Uint32 将它们解释为一个整数,这将以大端序(“网络字节序”)读取一个 uint32:
dataFieldLengthArray := make([]byte, 4)
dataFieldLength, errDataFieldLengthArray := r.Read(dataFieldLengthArray)
u32 := binary.BigEndian.Uint32(dataFieldLengthArray)
intDataFieldLength := int(u32)
好的,我已经实现了错误处理包。我不太理解你最后那个关于 readSomething 和 myReader 的代码示例。无论如何,我已经修改了我的代码,现在运行没有错误,如下所示。乍一看,你觉得这段代码在 Go 语言中没问题吗?有时候我不太理解,比如这个例子:
imeiLength, errDeviceImei := r.Read(deviceImeiByteArray)
if errDeviceImei != nil {
if errDeviceImei != io.EOF {
fmt.Println("read error:", errDeviceImei)
}
break
}
在这种情况下,我甚至没有声明 imeiLength,但它却可以工作?但有时候它又告诉我未定义的错误?
func processClient(connection net.Conn) {
defer connection.Close()
r := bufio.NewReader(connection)
var deviceImei = ""
for {
firstByte, err1 := r.ReadByte()
if firstByte != 0xff {
// rest of the logic
fmt.Println("FIRST IS OO")
secondByte, err2 := r.ReadByte()
if secondByte == 0 {
fmt.Println("second IS OOO0")
thirdByte, err3 := r.ReadByte()
if thirdByte == 0 {
fmt.Println("third IS OO0000")
fourstByte, err4 := r.ReadByte()
if fourstByte == 0 {
fmt.Println("fourst IS OOOOOO")
fmt.Println("Device imei reading fourth")
fmt.Println(deviceImei)
dataFieldLengthArray := make([]byte, 4)
dataFieldLength, errDataFieldLengthArray := r.Read(dataFieldLengthArray)
if errDataFieldLengthArray != nil {
fmt.Println("ERROR errDataFieldLengthArray", errDataFieldLengthArray)
if errDataFieldLengthArray != io.EOF {
fmt.Println("read error errDataFieldLengthArray:", errDataFieldLengthArray)
}
break
}
fmt.Println("int dataFieldLength")
fmt.Println(dataFieldLength)
stringDataFieldLengthArray := string(dataFieldLengthArray)
intDataFieldLength, errStrconvAtoi := strconv.Atoi(stringDataFieldLengthArray)
fmt.Println("String dataFieldLengthArray")
fmt.Println(stringDataFieldLengthArray)
fmt.Println("int intDataFieldLength")
fmt.Println(intDataFieldLength)
if errStrconvAtoi != nil {
fmt.Println("ERROR errStrconvAtoi", errStrconvAtoi)
if errStrconvAtoi != io.EOF {
fmt.Println("read error errStrconvAtoi:", errStrconvAtoi)
}
break
}
}
if err4 != nil {
break
}
}
if err3 != nil {
break
}
} else if secondByte == 15 {
fmt.Println("second IS to read imei now")
deviceImeiByteArray := make([]byte, 15)
imeiLength, errDeviceImei := r.Read(deviceImeiByteArray)
if errDeviceImei != nil {
if errDeviceImei != io.EOF {
fmt.Println("read error:", errDeviceImei)
}
break
}
deviceImei = string(deviceImeiByteArray[:len(deviceImeiByteArray)])
fmt.Println(deviceImei)
fmt.Println(imeiLength)
fmt.Println("total size:", len(deviceImeiByteArray))
}
if err2 != nil {
break
}
}
if errors.Is(err1, io.EOF) {
fmt.Println("IO EOF", err1)
break
}
if err1 != nil {
fmt.Println("ERROR", err1)
break
}
}
}


