通过 PuTTY 与 TCP 服务器通信

Communicating with a TCP server via PuTTY

如何通过在 PuTTY 客户端中输入“exit”来关闭连接?这是我的代码:

package main

import (
    "bufio"
    "fmt"
    "net"
)

func main() {

    //Ascolta richiesta
    datastream, err := net.Listen("tcp", ":8080")

    if err != nil {
        fmt.Println(err)
        return
    }
    defer datastream.Close()

    //Accetta richiesta
    for {
        connessione, err := datastream.Accept()
        if err != nil {
            fmt.Println(err)
            return
        }

        go handle(connessione)
    }
}

//Connessione Handle > Thread
func handle(connessione net.Conn) {

    fmt.Println("Scrivere exit per uscire")

    for {
        data, err := bufio.NewReader(connessione).ReadString('\n')

        

        if err != nil {
            fmt.Print(err)
            return
        }
        fmt.Println(data)

    }

}

如果没记错的话,PuTTY 可以使用多种协议来访问服务器,其中 SSH and Telnet 使用最多。

大多数时候人们使用 PuTTY 访问 SSH 服务器,在 SSH 会话中键入“exit”来终止它具有相对复杂的语义:大多数时候,服务器运行一个 command-line shell, SSH 协议在 shell 和 SSH 客户端之间传输数据,因此当用户在客户端(例如 PuTTY)中键入“exit”时,客户端将其发送到 SSH 服务器,然后再将其发送到shell; shell 解释该命令,终止其自身,SSH 服务器检测到并关闭其会话。
回顾一下,PuTTY 本身不会以任何方式处理输入“exit”;它只是将该字符串发送到远程端。

您没有告诉我们,但是从您的代码来看,您似乎编写了一个普通的 TCP 服务器(我的意思是,不是 SSH 服务器),因此看起来 PuTTY 使用 Telnet 访问您的服务器。
在这种情况下,在 PuTTY 中输入“exit”将使它直接将该行发送到您的 Go 服务器,因此要终止您的服务器,您必须解释发送给它的内容,一旦它收到一行“exit”,它必须终止处理循环并关闭连接。

从你的代码来看,应该是这样的

func handle(conn net.Conn) {
    defer conn.Close()

    fmt.Println("Scrivere exit per uscire")

    for {
        data, err := bufio.NewReader(conn).ReadString('\n') 

        if err != nil {
            fmt.Print(err)
            return
        }

        fmt.Println(data)
        if data == "exit" {
            return
        }
    }
}

在这里,我们

  1. 添加了一个 deferred 语句来关闭连接——这样每次我们退出连接“处理程序”时都会发生这种情况。
    不包括那是您原始代码中的一个错误,它会导致服务器上的资源最终耗尽。
  2. 添加了一个 if 语句,该语句分析从客户端发送的文本行,并在该行“退出”时终止处理程序。

在评论帖后更新

问题

有一个扭曲导致我最初提供的修复无法使用:

  • bufio.Reader.ReadString(delim byte)记录如下

    ReadString reads until the first occurrence of delim in the input, returning a string containing the data up to and including the delimiter.

    由于代码要求 ReadString 读取到 \n,当客户端发送“exit”后跟换行符(ASCII LF,0x0a)时,ReadString returns 恰好是“exit”加上一个 LF 字符。

  • PuTTY 显然使用的 Telnet 协议终止客户端发送的每一行 CRLF sequence、0x0b、0x0a,这就是发送到服务器的内容,因此 if 我示例中的语句正在接收“exit\r\n”。

实际上,如果 OP 在他们的调试输出语句中使用类似 fmt.Printf("%q\n", data) 的东西,那么这两个事实都可以很容易地看到——因为 %q 格式化动词使任何不可打印的字符“可见”在输出中。

解决方案

首先,原始代码有一个我在第一次阅读时没有发现的错误:在每次循环迭代时都会创建一个 bufio.Reader 的新实例 - 丢弃前一个实例(如果有的话)。但是这种类型被明确记录为有权缓冲来自源 reader 的任意数量的数据——也就是说,它能够从 reader 中消耗比它打算 return 中更多的数据调用任何类型的读取方法的客户端。
这意味着当 bufio.Reader 被丢弃时,它自上次读取调用以来缓冲的数据也被丢弃。
这个bug很严重,需要修复。

其次,我们需要决定如何处理换行符终止符。
有多个选择:

  • 准确指定服务器在其协议中支持的换行格式,然后实现对该规范的支持。

    例如,我们可以假设有线协议中的每一行都必须恰好由一个 LF 终止。
    在这种情况下,我们可能(几乎)保持代码不变,但将客户端提交的字符串与“exit\n”进行比较。

    在这种情况下,通过 Telnet 协议与服务器对话将无效,因为 Telnet 客户端会终止用户使用 CRLF 序列提交的行。

  • 对协议松懈并允许 LF 和 CRLF。

    为了支持这一点,代码应该以某种方式处理客户端线路以任何方式终止的可能性。
    这可以通过在比较语句中使用之前修剪输入行来完成。

我想说最简单的方法是使用第二个选项并使用 bufio.Scanner 类型:

func handle(conn net.Conn) {
  defer conn.Close()

  scanner = bufio.NewScanner(conn)
  for scanner.Scan() {
    data := scanner.Text()

    fmt.Printf("%q\n", data)

    if data == "exit" {
      return
    }
  }

  if err := scanner.Err(); err != nil {
    fmt.Println("error:", err)
  }
}