通过 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
}
}
}
在这里,我们
- 添加了一个
defer
red 语句来关闭连接——这样每次我们退出连接“处理程序”时都会发生这种情况。
不包括那是您原始代码中的一个错误,它会导致服务器上的资源最终耗尽。
- 添加了一个
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)
}
}
如何通过在 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
}
}
}
在这里,我们
- 添加了一个
defer
red 语句来关闭连接——这样每次我们退出连接“处理程序”时都会发生这种情况。
不包括那是您原始代码中的一个错误,它会导致服务器上的资源最终耗尽。 - 添加了一个
if
语句,该语句分析从客户端发送的文本行,并在该行“退出”时终止处理程序。
在评论帖后更新。
问题
有一个扭曲导致我最初提供的修复无法使用:
bufio.Reader.ReadString(delim byte)
记录如下ReadString
reads until the first occurrence ofdelim
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)
}
}