TCP 客户端或服务器卡在处理数据上 [英] TCP client or server stucks at processing data

查看:38
本文介绍了TCP 客户端或服务器卡在处理数据上的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试编写一个简单的 tcp 服务器和客户端程序.

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

当我同时运行下面的服务器和客户端代码时,客户端将简单地从服务器接收时间消息并退出,服务器继续接受新连接.我预期的程序行为是我希望服务器也收到hello world"来自客户端的消息并关闭连接,客户端也通过使用responseConnection"来关闭连接.功能.

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.

但问题是当我启动客户端时,服务器似乎卡在了responseConnection"里面的 conn.Read 函数上函数并有错误EOF"退出状态 1"当我首先停止客户端时,调试起来真的很模糊.当我先停止服务器时,客户端将从服务器接收时间数据.

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.

如果您有任何想法,请帮助回答,因为我完全是 Golang 的新手.非常感谢!

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 (
    "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 不实现消息边界并且只传输两个连接上的不透明字节流.

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.

这意味着,当您发送一串字节Hello world from client"时连接到已连接的套接字(已建立的 TCP 连接),连接的另一端不知道客户端的消息结束,除非客户端以某种方式自行传达;根本无法使用 TCP 本身来划分单个消息.

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.

进入应用级协议":除非您打算使用 TCP 的数据交换任务自然地传输单个消息"— 想象一个服务器将单个文件的内容转储到每个连接的客户端并关闭连接 — 您必须发明某种方法让客户端告诉服务器它发送的每条消息实际上在哪里结束.

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.

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

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.

  • 发明自定义协议(例如,在 TLV 系列) 将实现消息帧.

说,在最简单的形式中,协议可以定义为一个无符号字节,包含以下消息的长度,以字节为单位.
然后服务器将有一个两步过程来读取每个客户端的消息:

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. 读取单个字节;
  2. 如果成功,读取前导字节的值定义的尽可能多的后续字节;
  3. 成功后,返回第 1 步阅读以下消息.

  • 想出一个消息分隔符,例如 ASCII LF 字符——它可以在 Go 的字符串文字中被编码为 \n,并让服务器继续读取直到遇到 LF;一旦它发现了一个 LF,它就知道它应该处理该消息,然后开始读取另一个.

  • 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 有一个方便的类型 bufio.Reader 就在它的标准包可以从任何由 LF 分隔的 io.Reader 单独的行中读取.

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

    具有更复杂的消息框架,例如使用 JSON 流发送 JSON 文档.

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

    由股票 encoding/json 包实现的解码器的一个经常被监督的特性是它可以解码 JSON 对象流,考虑 为例.

    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.

    这篇关于TCP 客户端或服务器卡在处理数据上的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆