Home > Enterprise >  Communicating with a TCP server via PuTTY
Communicating with a TCP server via PuTTY

Time:11-19

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

  1. Added a deferred 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.
  2. 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 follows

    ReadString reads until the first occurrence of delim 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)
  }
}
  • Related