TCP客户端或服务器在处理数据时卡住了。

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

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() // <--- 当使用responseConnection()时,添加这个
	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() // <--- 当使用responseConnection()时,删除这个
	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()
}
英文:

I'm trying to write a simple tcp server and client program.

When I run both the server and client code below, the client will simply receive the time message from server and exit, and the server continues to accept a new connection. My expected program behaviour is I want the server to also receive the "hello world" message from the client and and close the connection and so does client by using the "responseConnection" function.

But the problem is when I started the client, the server seems to be stuck at the conn.Read function inside "responseConnection" function and has error "EOF
exit status 1" when I stopped client first, which is really ambiguous to debug. When I stopped server first, client will receive the time data from server.

If you have any ideas why, please help to answer as I'm totally a newbie to Golang. Thank you so much!

server.go

package main
import (
&quot;fmt&quot;
&quot;io&quot;
&quot;log&quot;
&quot;net&quot;
&quot;time&quot;
)
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() // &lt;-- 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(&quot;Error reading: &quot;, err)
}
// fmt.Println(err)
break
}
buf = append(buf, tmp[:n]...)
}
fmt.Println(&quot;Data from client ========&gt; &quot;, string(buf))
// c &lt;- buf
}
func handleConnection(conn net.Conn) {
defer conn.Close() // &lt;- When responseConnection() is used, remove this
dayTime := time.Now().String()
conn.Write([]byte(dayTime))
// responseConnection(conn)
fmt.Println(&quot;The end of handleConnection&quot;)
}
func main() {
localConn := connection{
host:    &quot;&quot;,
port:    &quot;5555&quot;,
network: &quot;tcp&quot;,
}
// servicePort := &quot;:5555&quot;
tcpAddr, err := net.ResolveTCPAddr(localConn.network, &quot;:&quot;+localConn.port)
checkError(err)
l, err := net.ListenTCP(&quot;tcp&quot;, tcpAddr)
fmt.Println(&quot;Server starts listening on port&quot;, localConn.port)
checkError(err)
for {
fmt.Println(&quot;Accepting a new connection....&quot;)
conn, err := l.Accept()
checkError(err)
handleConnection(conn)
// responseConnection(conn)
}
}

client.go

package main
import (
&quot;bytes&quot;
&quot;fmt&quot;
&quot;io&quot;
&quot;log&quot;
&quot;net&quot;
&quot;os&quot;
)
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, &quot;Usage: %s host:port &quot;, os.Args[0])
os.Exit(1)
}
service := os.Args[1]
tcpAddr, err := net.ResolveTCPAddr(&quot;tcp&quot;, service)
checkError(err)
conn, err := net.DialTCP(&quot;tcp&quot;, nil, tcpAddr)
checkError(err)
message := &quot;Hello world from client&quot;
conn.Write([]byte(message))
io.Copy(&amp;buf, conn)
buf.Cap()
fmt.Printf(&quot;Data from server =======&gt;: %s\n Buffer length: %d\n&quot;, buf.String(), buf.Len())
defer conn.Close()
}

答案1

得分: 2

我认为你的问题是一个非常常见的问题:没有注意到TCP不实现消息边界,而只是在连接上传输两个不透明的字节流。

这意味着,当你向一个已连接的套接字(建立的TCP连接)发送一个字节串"Hello world from client"时,连接的另一端不知道客户端的消息在哪里结束,除非客户端自己传达这一点;使用TCP本身没有办法标记单个消息。

这就引入了"应用层协议":除非你打算使用TCP进行的数据交换任务自然地传输单个"消息",比如一个服务器将单个文件的内容转储到每个连接的客户端并关闭连接,否则你必须想出一种方法,让客户端告诉服务器它发送的每个消息实际上在哪里结束。

考虑你的例子:在读取过程中,你基本上有一个循环,它从套接字中重复读取数据块,只有一个退出条件:在该套接字上达到文件结尾。只有当远程端(在我们的情况下是客户端)关闭连接时,才会报告EOF,而客户端从不关闭连接:它发送一个字符串,然后等待服务器发送回一些内容,但服务器从不回复,因为它从不完成读取。

解决这个问题有多种方法。

  • 发明一个自定义协议(比如,TLV系列)来实现消息分帧。

    例如,在其最简单的形式中,该协议可以定义为一个包含后续消息长度(以字节为单位)的单个无符号字节。
    服务器将为读取客户端的每个消息执行两个步骤:

    1. 读取一个字节;
    2. 如果成功,按照前导字节的值读取相应数量的后续字节;
    3. 一旦成功,返回步骤1以读取下一个消息。
  • 想出一个消息分隔符,比如ASCII LF字符——在Go的字符串字面量中可以编码为\n,并让服务器继续读取,直到遇到LF;一旦发现LF,服务器就知道应该处理该消息,然后开始读取另一个消息。

    Go的标准包中有一个方便的类型bufio.Reader,可以从任何io.Reader中读取由LF分隔的单独行。

  • 采用更复杂的消息分帧方式,比如使用JSON流发送JSON文档。

    常常被忽视的是,encoding/json包实现的解码器可以很好地解码JSON对象流,可以参考这个示例。

实际上有很多可能性,我只是浅尝辄止,我想你应该明白了。

英文:

I think your problem is quite the standard one: not paying attention to the fact TCP does not implement message boundaries and merely transports two opaque streams of bytes over a connection.

This means, when you send a string of bytes "Hello world from client" to a connected socket (an established TCP connection) the other end of the connection have no idea where the client's message ends unless the client somehow conveys that itself; there simply are no means to demarcate individual messages using TCP itself.

Enter "application-level protocols": unless the data exchange task for which you intend to use TCP naturally transfer a single "message" — imagine a server which dumps the contents of a single file into each connected client and closes the connection — you have to invent some way for the client to tell the server where each of the messages it sends actually ends.

Consider your example: in the reading procedure you basically have a loop wihch repeatedly reads chunks of data from a socket, with a single exit condition: reaching end-of-file on that socket. A EOF is only reported when the remote side — the client in our case — closes its side of the conection, and the client never does that: it sends a string and then waits for the sever to send something back, but the server never replies because it never finishes reading.

There are multiple ways to solve the problem.

  • Invent a custom protocol (say, in the TLV family) which would implement message framing.

    Say, in its simplest form, the protocol could be defined as a single unsigned byte containing the length of the following message, in bytes.
    The server would then have a two-step process for reading each of the client's messages:

    1. Read a single byte;
    2. If successful, read as many following bytes as defined by the value of the leading byte;
    3. Once successful, go back to step 1 to read the following message.
  • Come up with a message delimiter such as ASCII LF character — wich can be encoded as \n in Go's string literals, ­— and make the server continue reading until it encounters an LF; once it has spotted an LF, it knows it should process the message then start reading another.

    Go has a convenient type bufio.Reader right in its standard package which can read from any io.Reader individual lines separated by LFs.

  • Have a more involved message framing such as sending JSON documents using JSON streaming.

    An oft-overseen feature of the decoder implemented by the stock encoding/json package is that it's fine with decoding streams of JSON objects, consider this as an example.

The possibilities are actually many, so I were just scratching the surface, and I think you should get the idea.

huangapple
  • 本文由 发表于 2021年8月4日 01:26:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/68640430.html
匿名

发表评论

匿名网友

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

确定