TCP 客户端或服务器卡在处理数据
TCP client or server stucks at processing data
我正在尝试编写一个简单的 tcp 服务器和客户端程序。
当我运行 下面的服务器和客户端代码时,客户端将简单地从服务器接收时间消息并退出,服务器继续接受新连接。我预期的程序行为是我希望服务器也从客户端接收“hello world”消息并关闭连接,客户端也使用“responseConnection”函数关闭连接。
但问题是当我启动客户端时,服务器似乎卡在了“responseConnection”函数中的conn.Read函数并且出现错误“EOF
exit status 1" 当我首先停止客户端时,这对于调试来说确实是模棱两可的。当我首先停止服务器时,客户端将从服务器接收时间数据。
如果你有任何想法为什么,请帮助回答,因为我完全是 Golang 的新手。非常感谢!
server.go
package main
import (
"fmt"
"io"
"log"
"net"
"time"
)
type connection struct {
host string
port string
network string
}
func checkError(err error) {
if err != nil {
log.Fatalln(err)
}
}
func responseConnection(conn net.Conn) {
defer conn.Close() // <-- When responseConnection() is used, add this
buf := make([]byte, 0, 4096)
tmp := make([]byte, 256)
for {
n, err := conn.Read(tmp)
if err != nil {
checkError(err)
if err != io.EOF {
fmt.Println("Error reading: ", err)
}
// fmt.Println(err)
break
}
buf = append(buf, tmp[:n]...)
}
fmt.Println("Data from client ========> ", string(buf))
// c <- buf
}
func handleConnection(conn net.Conn) {
defer conn.Close() // <- When responseConnection() is used, remove this
dayTime := time.Now().String()
conn.Write([]byte(dayTime))
// responseConnection(conn)
fmt.Println("The end of handleConnection")
}
func main() {
localConn := connection{
host: "",
port: "5555",
network: "tcp",
}
// servicePort := ":5555"
tcpAddr, err := net.ResolveTCPAddr(localConn.network, ":"+localConn.port)
checkError(err)
l, err := net.ListenTCP("tcp", tcpAddr)
fmt.Println("Server starts listening on port", localConn.port)
checkError(err)
for {
fmt.Println("Accepting a new connection....")
conn, err := l.Accept()
checkError(err)
handleConnection(conn)
// responseConnection(conn)
}
}
client.go
package main
import (
"bytes"
"fmt"
"io"
"log"
"net"
"os"
)
func checkError(err error) {
if err != nil {
log.Fatalln(err)
}
}
var buf bytes.Buffer
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage: %s host:port ", os.Args[0])
os.Exit(1)
}
service := os.Args[1]
tcpAddr, err := net.ResolveTCPAddr("tcp", service)
checkError(err)
conn, err := net.DialTCP("tcp", nil, tcpAddr)
checkError(err)
message := "Hello world from client"
conn.Write([]byte(message))
io.Copy(&buf, conn)
buf.Cap()
fmt.Printf("Data from server =======>: %s\n Buffer length: %d\n", buf.String(), buf.Len())
defer conn.Close()
}
我认为您的问题很标准:没有注意到 TCP 不实现消息边界 并且仅通过连接传输两个不透明的字节流这一事实。
这意味着,当您将字节串“Hello world from client”发送到连接的套接字(已建立的 TCP 连接)时,连接的另一端不知道客户端的消息在哪里结束 除非客户以某种方式自行传达;根本没有办法使用 TCP 本身来划分各个消息。
输入“应用程序级协议”:除非您打算使用 TCP 的数据交换任务自然地传输单个“消息”——想象一个服务器将单个文件的内容转储到每个连接的客户端并关闭连接 — 您必须为客户端发明某种方式来告诉服务器它发送的每条消息实际结束的位置。
考虑你的例子:在读取过程中,你基本上有一个循环,它重复地从一个套接字中读取数据块,只有一个退出条件:到达该套接字上的文件末尾。仅当远程端(在我们的示例中为客户端)关闭其连接端时才会报告 EOF,而客户端从不这样做:它发送一个字符串,然后等待服务器发回某些内容,但服务器从不回复因为它永远不会读完。
有多种方法可以解决这个问题。
发明一个自定义协议(比如,在 TLV family 中)来实现消息框架。
比如说,在最简单的形式中,协议可以定义为包含后续消息长度的单个无符号字节,以字节为单位。
服务器将有一个两步过程来读取每个客户端的消息:
- 读取单个字节;
- 如果成功,读取由前导字节的值定义的尽可能多的后续字节;
- 成功后,返回步骤 1 阅读以下消息。
提出一个消息定界符,例如 ASCII LF 字符——在 Go 的字符串文字中可以将其编码为 \n
,——并让服务器继续读取直到遇到低频;一旦它发现了一个 LF,它就知道它应该处理该消息然后开始阅读另一个。
Go 在其标准包中有一个方便的类型 bufio.Reader
,它可以从任何 io.Reader
由 LF 分隔的单独行读取。
具有更复杂的消息框架,例如使用 JSON streaming 发送 JSON 文档。
stock encoding/json
包实现的解码器的一个经常被监督的特性是它可以很好地解码 JSON 个对象的流,以 this 为例。
可能性实际上很多,所以我只是触及表面,我想你应该明白了。
我正在尝试编写一个简单的 tcp 服务器和客户端程序。
当我运行 下面的服务器和客户端代码时,客户端将简单地从服务器接收时间消息并退出,服务器继续接受新连接。我预期的程序行为是我希望服务器也从客户端接收“hello world”消息并关闭连接,客户端也使用“responseConnection”函数关闭连接。
但问题是当我启动客户端时,服务器似乎卡在了“responseConnection”函数中的conn.Read函数并且出现错误“EOF exit status 1" 当我首先停止客户端时,这对于调试来说确实是模棱两可的。当我首先停止服务器时,客户端将从服务器接收时间数据。
如果你有任何想法为什么,请帮助回答,因为我完全是 Golang 的新手。非常感谢!
server.go
package main
import (
"fmt"
"io"
"log"
"net"
"time"
)
type connection struct {
host string
port string
network string
}
func checkError(err error) {
if err != nil {
log.Fatalln(err)
}
}
func responseConnection(conn net.Conn) {
defer conn.Close() // <-- When responseConnection() is used, add this
buf := make([]byte, 0, 4096)
tmp := make([]byte, 256)
for {
n, err := conn.Read(tmp)
if err != nil {
checkError(err)
if err != io.EOF {
fmt.Println("Error reading: ", err)
}
// fmt.Println(err)
break
}
buf = append(buf, tmp[:n]...)
}
fmt.Println("Data from client ========> ", string(buf))
// c <- buf
}
func handleConnection(conn net.Conn) {
defer conn.Close() // <- When responseConnection() is used, remove this
dayTime := time.Now().String()
conn.Write([]byte(dayTime))
// responseConnection(conn)
fmt.Println("The end of handleConnection")
}
func main() {
localConn := connection{
host: "",
port: "5555",
network: "tcp",
}
// servicePort := ":5555"
tcpAddr, err := net.ResolveTCPAddr(localConn.network, ":"+localConn.port)
checkError(err)
l, err := net.ListenTCP("tcp", tcpAddr)
fmt.Println("Server starts listening on port", localConn.port)
checkError(err)
for {
fmt.Println("Accepting a new connection....")
conn, err := l.Accept()
checkError(err)
handleConnection(conn)
// responseConnection(conn)
}
}
client.go
package main
import (
"bytes"
"fmt"
"io"
"log"
"net"
"os"
)
func checkError(err error) {
if err != nil {
log.Fatalln(err)
}
}
var buf bytes.Buffer
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage: %s host:port ", os.Args[0])
os.Exit(1)
}
service := os.Args[1]
tcpAddr, err := net.ResolveTCPAddr("tcp", service)
checkError(err)
conn, err := net.DialTCP("tcp", nil, tcpAddr)
checkError(err)
message := "Hello world from client"
conn.Write([]byte(message))
io.Copy(&buf, conn)
buf.Cap()
fmt.Printf("Data from server =======>: %s\n Buffer length: %d\n", buf.String(), buf.Len())
defer conn.Close()
}
我认为您的问题很标准:没有注意到 TCP 不实现消息边界 并且仅通过连接传输两个不透明的字节流这一事实。
这意味着,当您将字节串“Hello world from client”发送到连接的套接字(已建立的 TCP 连接)时,连接的另一端不知道客户端的消息在哪里结束 除非客户以某种方式自行传达;根本没有办法使用 TCP 本身来划分各个消息。
输入“应用程序级协议”:除非您打算使用 TCP 的数据交换任务自然地传输单个“消息”——想象一个服务器将单个文件的内容转储到每个连接的客户端并关闭连接 — 您必须为客户端发明某种方式来告诉服务器它发送的每条消息实际结束的位置。
考虑你的例子:在读取过程中,你基本上有一个循环,它重复地从一个套接字中读取数据块,只有一个退出条件:到达该套接字上的文件末尾。仅当远程端(在我们的示例中为客户端)关闭其连接端时才会报告 EOF,而客户端从不这样做:它发送一个字符串,然后等待服务器发回某些内容,但服务器从不回复因为它永远不会读完。
有多种方法可以解决这个问题。
发明一个自定义协议(比如,在 TLV family 中)来实现消息框架。
比如说,在最简单的形式中,协议可以定义为包含后续消息长度的单个无符号字节,以字节为单位。
服务器将有一个两步过程来读取每个客户端的消息:- 读取单个字节;
- 如果成功,读取由前导字节的值定义的尽可能多的后续字节;
- 成功后,返回步骤 1 阅读以下消息。
提出一个消息定界符,例如 ASCII LF 字符——在 Go 的字符串文字中可以将其编码为
\n
,——并让服务器继续读取直到遇到低频;一旦它发现了一个 LF,它就知道它应该处理该消息然后开始阅读另一个。Go 在其标准包中有一个方便的类型
bufio.Reader
,它可以从任何io.Reader
由 LF 分隔的单独行读取。具有更复杂的消息框架,例如使用 JSON streaming 发送 JSON 文档。
stock
encoding/json
包实现的解码器的一个经常被监督的特性是它可以很好地解码 JSON 个对象的流,以 this 为例。
可能性实际上很多,所以我只是触及表面,我想你应该明白了。