【问题标题】:Looping issue in function函数中的循环问题
【发布时间】:2021-03-15 00:10:49
【问题描述】:

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

但是:

  1. 从连接菜单中选择 (L) 时,会复制/重复菜单
  2. 然后,需要额外/不必要的按键才能看到回显给用户的类型

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

有什么想法吗?

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("\033")) {
                            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]

    }

}


【问题讨论】:

    标签: loops go telnet


    【解决方案1】:

    所以你几乎明白了!

    1. 需要额外按键的原因是您的选择案例在收到另一个字符之前未处理。基本上你落后了一个角色。当您从用户那里收集消息时,您还需要注意这一点。

    这就是我能够开始工作的地方。我的 telnet 客户端直到我点击返回才会发送(我在 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("\033")) || bytes.Equal(data, []byte("\033\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]
    
        }
    
    }
    
    

    【讨论】:

    • 成功了!该建议非常有帮助 - 我定义。需要花更多时间在案例逻辑上:)
    【解决方案2】:

    当使用stock telnet 命令行程序时,它通常会与服务器协商以在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
        }
    }
    

    【讨论】:

    • 是否可以在非 LINEMODE 下运行 telnet ?无缓冲。
    • 服务器代码的意图是有条件地支持 2 个用例:一个只需要一个按键(菜单 - 不需要 \n),然后一个需要它。您是说上面的“关键”用例实际上仍在寻找“\n”吗?
    • 没有。我要说的是telnet 客户端工具(我假设您正在使用它来测试)使用行缓冲。因此telnet 不会发送单独的击键,而是在本地缓存它们,直到您点击EnterCtrl-D,然后您的服务器会看到流量。您可以编写自己的客户端来实时发送每次击键以获得您想要的行为。
    • 仍然不确定我是否理解。在我的服务器代码中,从标准 telnet 客户端实时接收单键按下 (无需按下 Enter),并且函数 正在 响应。但是在接受下一个输入之前,它需要一个额外的按键——任何按键,而不是 Return。这就是为什么我认为有一个循环错误......
    猜你喜欢
    • 2014-08-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多