函数中的循环问题
Looping issue in function
我有一个小型 TCP 服务器 运行,用户可以在其中通过 telnet 进行连接。有一个“菜单”选项循环(例如触发单字符输入),选择“L”选项允许连接的用户“留言”。
但是:
- 从连接菜单中选择 (L) 时 -- 它 duplicates/repeats 菜单
- 然后,需要 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
}
}
所以你几乎成功了!
- 需要额外按键的原因是您的 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]
}
}
我有一个小型 TCP 服务器 运行,用户可以在其中通过 telnet 进行连接。有一个“菜单”选项循环(例如触发单字符输入),选择“L”选项允许连接的用户“留言”。
但是:
- 从连接菜单中选择 (L) 时 -- 它 duplicates/repeats 菜单
- 然后,需要 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
}
}
所以你几乎成功了!
- 需要额外按键的原因是您的 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]
}
}