函数中的循环问题

Looping issue in function

我有一个小型 TCP 服务器 运行,用户可以在其中通过 telnet 进行连接。有一个“菜单”选项循环(例如触发单字符输入),选择“L”选项允许连接的用户“留言”。

但是:

  1. 从连接菜单中选择 (L) 时 -- 它 duplicates/repeats 菜单
  2. 然后,需要 additional/unnecessary 按键才能看到返回给用户的类型

我怀疑我有循环问题,但我不知道它在哪里崩溃了。

有什么想法吗?

package main

import (
    "bytes"
    "fmt"
    "log"
    "net"
)

const (
    port = "5555"
)

var arr []string

type Client struct {
    c        net.Conn
    dataType string
    menuCurr string
}

func NewClient() *Client {
    return &(Client{})

}

func waitForInput(didInput chan<- bool) {
    // Wait for a valid input here

    didInput <- true
}

func main() {

    log.Printf("Hello Server!")
    service := ":5555"
    tcpAddr, error := net.ResolveTCPAddr("tcp", service)
    if error != nil {
        log.Printf("Error: Could not resolve address")
    } else {
        netListen, error := net.Listen(tcpAddr.Network(), tcpAddr.String())
        if error != nil {
            log.Fatal(error)
        } else {
            defer netListen.Close()
            for {
                log.Printf("Waiting for clients")
                conn, error := netListen.Accept()
                if error != nil {
                    log.Print("Client error: ", error)
                } else {
                    log.Printf("Client connected %s -> %s \n", conn.RemoteAddr(), conn.LocalAddr())
                    go handler(conn)
                }
            }
        }
    }
}

func removeClient(conn net.Conn) {

    log.Printf("Client %s disconnected", conn.RemoteAddr())
    conn.Close()
}

func handler(conn net.Conn) {

    defer removeClient(conn)
    errorChan := make(chan error)
    dataChan := make(chan []byte)

    // Set defaults for incoming connections
    var s *Client
    s = NewClient()
    s.c = conn
    s.dataType = "key"
    s.menuCurr = "connect" // first menu every user sees

    go readWrapper(conn, dataChan, errorChan)
    r := bytes.NewBuffer(make([]byte, 0, 1024))

    // default menu
    fmt.Fprintf(conn, "Select an option:\r\n\n[L] Leave Message\r\n[!] Disconnect\r\n\nCmd? ")

    for {
        select {
        case data := <-dataChan:

            if s.menuCurr == "connect" {
                fmt.Fprintf(conn, "Select an option:\r\n\n[L] Leave Message\r\n[!] Disconnect\r\n\nCmd? ")
            }
            if s.menuCurr == "message" {
                fmt.Fprintf(conn, "Type a mesage. Escape to quit. \r\n\n")
            }
            // "key" responds to single character input
            if s.dataType == "key" {
                switch string(data) {
                default:
                    fmt.Println("client hit invalid key...")
                    continue
                case "L", "l":
                    s.dataType = "text"
                    s.menuCurr = "message"
                    continue
                case "!":
                    fmt.Fprintf(conn, " Bye!")
                    fmt.Println("client chose to exit...")
                    break
                }
            }

            // "Text" allows for free typing
            if s.dataType == "text" {
                for {
                    select {
                    case data := <-dataChan:
                        fmt.Fprintf(conn, string(data))
                        if bytes.Equal(data, []byte("\r\n")) || bytes.Equal(data, []byte("\r")) {
                            fmt.Fprintf(conn, "you typed: %q\r\n", r.String())
                            r.Reset()
                            s.dataType = "key"
                            s.menuCurr = "connect"
                            break
                        }
                        if bytes.Equal(data, []byte("3")) {
                            fmt.Fprintf(conn, "\r\nAborted!\r\n")
                            r.Reset()
                            s.dataType = "key"
                            s.menuCurr = "connect"
                            break
                        }
                        r.Write(data)
                    }
                    log.Printf("Client %s sent: %q", conn.RemoteAddr(), string(data))

                    if s.menuCurr == "connect" {
                        break
                    }
                }
                continue
            }

        case err := <-errorChan:
            log.Println("An error occured:", err.Error())
            return
        }
        conn.Close()
    }

}

func readWrapper(conn net.Conn, dataChan chan []byte, errorChan chan error) {
    for {

        buf := make([]byte, 1024)
        reqLen, err := conn.Read(buf)
        if err != nil {
            errorChan <- err
            return
        }
        dataChan <- buf[:reqLen]

    }

}


使用普通 telnet 命令行程序时,它通常会与服务器协商 运行 in LINEMODE。在这种模式下,为了节省带宽,用户输入将被缓冲。当用户点击时,数据被刷新(即发送到服务器):

  • Enter;或
  • Ctrl-D

您可以在当前状态下使用您的服务器,但您必须键入以下按键:L,然后是 Ctrl-D(以强制刷新)。然后客户端将收到预期的服务器响应:type your message.

您的服务器代码需要一次击键(L!),因此如果您按 L 然后按 Enter - 那么服务器收到的消息将是 L\r\n,这是您的服务器代码出错的地方:

if s.dataType == "key" {
    switch string(data) {
        default:
            fmt.Printf("client hit invalid key... (%q)\n", string(data)) // <- `L\r\n`
            continue
        case "L", "l":
            s.dataType = "text"
            s.menuCurr = "message"
            continue
        case "!":
            fmt.Fprintf(conn, " Bye!")
            fmt.Println("client chose to exit...")
            break
    }
}

所以你几乎成功了!

  1. 需要额外按键的原因是您的 select 案例在收到另一个字符之前不会处理。基本上你落后了一个角色。您在收集用户消息时也需要注意这一点。

这是我能够开始工作的内容。我的 telnet 客户端在我点击 return 之前不会发送(我在 windows 为什么 \r\n 而不是 \n)所以我需要去除那些额外的字符。

package main

import (
    "bytes"
    "fmt"
    "log"
    "net"
    "strings"
)

const (
    port = "5555"
)

var arr []string

type Client struct {
    c        net.Conn
    dataType string
    menuCurr string
}

func NewClient() *Client {
    return &(Client{})

}

func waitForInput(didInput chan<- bool) {
    // Wait for a valid input here

    didInput <- true
}

func main() {

    log.Printf("Hello Server!")
    service := ":5555"
    tcpAddr, error := net.ResolveTCPAddr("tcp", service)
    if error != nil {
        log.Printf("Error: Could not resolve address")
    } else {
        netListen, error := net.Listen(tcpAddr.Network(), tcpAddr.String())
        if error != nil {
            log.Fatal(error)
        } else {
            defer netListen.Close()
            for {
                log.Printf("Waiting for clients")
                conn, error := netListen.Accept()
                if error != nil {
                    log.Print("Client error: ", error)
                } else {
                    log.Printf("Client connected %s -> %s \n", conn.RemoteAddr(), conn.LocalAddr())
                    go handler(conn)
                }
            }
        }
    }
}

func removeClient(conn net.Conn) {

    log.Printf("Client %s disconnected", conn.RemoteAddr())
    conn.Close()
}

func handler(conn net.Conn) {

    defer removeClient(conn)
    errorChan := make(chan error)
    dataChan := make(chan []byte)

    // Set defaults for incoming connections
    var s *Client
    s = NewClient()
    s.c = conn
    s.dataType = "key"
    s.menuCurr = "connect" // first menu every user sees

    go readWrapper(conn, dataChan, errorChan)
    r := bytes.NewBuffer(make([]byte, 0, 1024))

    // default menu
    fmt.Fprintf(conn, "Select an option:\r\n\n[L] Leave Message\r\n[!] Disconnect\r\n\nCmd? ")

    for {
        select {
        case data := <-dataChan:
            // notice how i removed the current menu state

            // "key" responds to single character input
            if s.dataType == "key" {
                t := strings.TrimSuffix(strings.TrimSuffix(string(data), "\r\n"), "\n")
                switch t {
                default:
                    fmt.Println("client hit invalid key...")
                    // remove the continue since the menu prints at the bottom
                    // continue
                case "L", "l":
                    s.dataType = "text"
                    s.menuCurr = "message"

                    // notice the message here and the break instead of the continue.
                    // if we use continue instead it will wait until your user sends something
                    // with a break instead it will fall through and start collecting the text
                    fmt.Fprintf(conn, "Type a mesage. Escape to quit. \r\n\n")
                    break
                case "!":
                    fmt.Fprintf(conn, " Bye!")
                    fmt.Println("client chose to exit...")
                    // tell current menu to exit
                    s.menuCurr = "exit"
                }
            }

            // "Text" allows for free typing
            if s.dataType == "text" {
                for {
                    select {
                    case data := <-dataChan:
                        fmt.Fprintf(conn, string(data))
                        if bytes.Equal(data, []byte("\r\n")) || bytes.Equal(data, []byte("\r")) {
                            fmt.Fprintf(conn, "you typed: %q\r\n", r.String())
                            r.Reset()
                            s.dataType = "key"
                            s.menuCurr = "connect"
                            break
                        }
                        if bytes.Equal(data, []byte("3")) || bytes.Equal(data, []byte("3\r\n")) {
                            fmt.Fprintf(conn, "\r\nAborted!\r\n")
                            r.Reset()
                            s.dataType = "key"
                            s.menuCurr = "connect"
                            break
                        }
                        r.Write(data)
                    }
                    log.Printf("Client %s sent: %q", conn.RemoteAddr(), r.String())
                    if s.menuCurr == "connect" {
                        break
                    }
                }
            }

            if s.menuCurr == "connect" {
                fmt.Fprintf(conn, "Select an option:\r\n\n[L] Leave Message\r\n[!] Disconnect\r\n\nCmd? ")
            }
            // fall through statement to close connection
            if s.menuCurr == "exit" {
                break
            }

            // otherwise continue printing menu for invalid submissions
            continue

        case err := <-errorChan:
            log.Println("An error occured:", err.Error())
            return
        }
        fmt.Println("closing")
        break
    }
    conn.Close()
}

func readWrapper(conn net.Conn, dataChan chan []byte, errorChan chan error) {
    for {

        buf := make([]byte, 1024)
        reqLen, err := conn.Read(buf)
        if err != nil {
            errorChan <- err
            return
        }
        dataChan <- buf[:reqLen]

    }

}