How do I close the connection by typing the word "exit" from the PuTTY client? This is my code:
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)
}
}
CodePudding user response:
If memory serves me right, PuTTY is able to use several protocols to access servers, with SSH and Telnet being used the most.
Most of the time people use PuTTY to access SSH servers, and typing "exit" in an SSH session to terminate it has relatively complex semantics: most of the time, the server runs a command-line shell, the SSH protocol is carrying data between that shell and the SSH client, so when the user types "exit" in the client (PuTTY, for instance), the client sends that to the SSH-server and it sends that to the shell; the shell interprets that command, terminates itsef, the SSH server detects that and shuts down its session.
To recap, PuTTY does not in any way handles typing "exit" into it by itself; it merely sends that string to the remote side.
You did not tell us, but form your code, it looks like you have written a plain TCP server (I mean, not an SSH server) and hence looks like PuTTY uses Telnet to access your server.
In this case, typing "exit" into the PuTTY will make it send that line to your Go server, directly, so to terminate your server you have to interpret what is being sent to it, and once it receives a line "exit", it has to terminate the processing loop and close the connection.
Judging from your code, it should be something like
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
}
}
}
Here, we
- Added a
defer
red statement which closes the connection — so that this happens each time we exit from the connection "handler".
Not including that was a bug in your original code which would lead to eventual recource exhaustion on the server. - Added an
if
statement which analyzes the line of text sent from the client and terminates the handler once that line is "exit".
Update after the comment thread.
The problem
There is a twist which renders the fix I have initially offerred unusable:
bufio.Reader.ReadString(delim byte)
is documented as followsReadString
reads until the first occurrence ofdelim
in the input, returning a string containing the data up to and including the delimiter.Since the code asked
ReadString
to read until\n
, when the client sends "exit" followed by a newline character (ASCII LF, 0x0a),ReadString
returns precisely "exit" plus that LF character.The Telnet protocol which PuTTY is apparently using, terminates each line the client sends with a CRLF sequence, 0x0b, 0x0a, and that's what gets sent to the client, so the
if
statement in my example is receiving "exit\r\n".
Actually, both facts could have been easily seen if the OP were using something like fmt.Printf("%q\n", data)
in their debug output statement — as the %q
formatting verb makes any non-printable character "visible" in the output.
The solutions
First, the original code has a bug I haven't spotted on the first read: a new instance of a bufio.Reader
is created on each loop iteration—throwing away the previous one, if any. But this type is explicitly documented as having permission to buffer any amount of data from the source reader—that is, it's able to consume from the reader more data than it is going to return to the client which made a call to any of the type's reading methods.
This means when a bufio.Reader
is discarded, the data it has buffered since the last reading call is thrown away, too.
This bug is grave, and is needed to be fixed.
Second, we need to decide what to do with the newline termination character(s).
There are multiple choices:
Specify precisely which newline format the server supports in its protocol and then implement support for exactly that specification.
For instance, we can postulate each line in our wire protocol must be terminated by exactly single LF.
In this case we might leave the code (almost) as is, but compare the string submitted by the client with "exit\n".In this case, talking to the server via the Telnet protocol will not work as a Telnet client terminates the lines submitted by the user with CRLF sequences.
Be lax about the protocol and allow both LFs and CRLFs.
To support this, the code should somehow deal with the possibility for the client's lines to be terminated either way.
This could be done, for instance, by trimming the input line before using it in the comparison statement.
I would say the simplest approach is to go with the second option and also use the bufio.Scanner
type:
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)
}
}