Golang客户端服务器程序中如何使用用户输入

Golang客户端服务器程序中如何使用用户输入 我正在尝试使用RESTful Web服务开发一个客户端-服务器应用程序。

我希望将用户输入作为用户名。但是,当我尝试使用bufio.NewReader(os.Stdin)时,程序的其他部分无法运行,并且没有将该名称用作clientName。

有人能帮我解决这个问题吗?

我用注释标注了相关位置。

服务器

package main

import (
  "github.com/gorilla/mux"
	"fmt"
	//"./myUtils"
	"net/http"
	"bufio"
	"os"
)
var helpInfo = [...]string {"help and command info:",
"/help: use this command to get some help",
"/createroom roomName : creates a room with the name roomName",
"/listrooms: lists all rooms available for joining",
"/join roomName: adds you to a chatroom",
"/leave removes you from current room",
}
var ClientArray []*Client
var RoomArray []*Room
var sender *Client
//STRUCTURES
/*****************Rooms*****************/
type Room struct{
  name string
  clientList []*Client
  chatLog []*ChatMessage
  creator *Client
}
//Structure holding messages sent to a chat, stores meta information on the client who sent it
type ChatMessage struct {
	client *Client
	message string
}
//Clients have names, and a reader and writer as well as a link to their connection
//Client names are guaranteed by the generateName function to be unique for the duration of program execution (NOT persisted)
type Client struct {
	currentRoom *Room
	outputChannel chan string
	name string
}
//Main function for starting the server, will open the server on the SERVER_IP and the SERVER_PORT
func main() {
	router := mux.NewRouter().StrictSlash(true)
	router.HandleFunc("/", Connect).
		Methods("GET")
	router.HandleFunc("/help", help).
		Methods("GET")
	router.HandleFunc("/{USER}/messages", MessageClient).
		Methods("GET")
	router.HandleFunc("/rooms", listrooms).
		Methods("GET")
	router.HandleFunc("/rooms/{ROOMNAME}", createroom).
		Methods("POST")
	router.HandleFunc("/rooms/{ROOMNAME}/{USER}", join).
		Methods("POST")
	router.HandleFunc("/{USER}/leaveroom", leaveroom).
		Methods("DELETE")
	router.HandleFunc("/{USER}/messageRoom", SendMessageToCurrentRoom).
		Methods("POST")
	http.ListenAndServe(":8080", router)
	fmt.Println("Launching server...")
	//Start the server on the constant IP and port
}
//checks the room name against the current list of rooms to make sure it is unique, returns true if it is, false if not
//returns true if a user is already in the room, false otherwise
func (room Room) isClientInRoom(client *Client) bool {
  for _, roomClient := range room.clientList {
    if client.name == roomClient.name {
      return true
    }
  }
  return false
}
//checks to see if a room with the given name exists in the RoomArray, if it does return it, if not return nil
func getRoomByName(roomName string) *Room{
  for _, room := range RoomArray{
    if room.name == roomName{
      return room
    }
  }
  return nil
}
//this function will check the room
func getClientByName(clientName string) *Client{
  for _, cli := range ClientArray{
    if cli.name == clientName{
      return cli
    }
  }
  return nil
}
/*
creates a new client with a name and returns the name
*/
func addClient() string{
   createOutputChannel := make(chan string)
   ////////////////////////////////////////////////////////////////////////////////////////////////////////
   //////////////////////////////////////////////////here///////////////////////////////////////////////////////////////////
   reader := bufio.NewReader(os.Stdin)
   clientName, _ := reader.ReadString('\n')
	cli := Client{
    currentRoom: nil, //starts as nil because the user is not initally in a room
    outputChannel: createOutputChannel,
    name: clientName,
  }
  ClientArray = append(ClientArray, &cli)
  return cli.name
}

//adds message to the clients output channel, messages should be single line, NON delimited strings, that is the message should not include a new line
//the name of the sender will be added to the message to form a final message in the form of "sender says: message\n"
func (cli *Client) messageClientFromClient(message string, sender *Client){
  cli.outputChannel <- (sender.name)+": "+message+"\n"
}

//removes the client from his current room and takes the client out of the rooms list of users
func (cli *Client)removeClientFromCurrentRoom(){
//not in a current room so just return
  if cli.currentRoom == nil {
    return
  } else {
    sendMessageToCurrentRoom(cli, "CLIENT HAS LEFT THE ROOM")
    cl := cli.currentRoom.clientList
    for i,roomClients := range cl{
      if cli == roomClients {
        cli.currentRoom.clientList = append(cl[:i], cl[i+1:]...)//deletes the element
      }
    }
    cli.currentRoom = nil
    return
  }
}
/**********************************/
//wrapper for the internal function of the same name so that it can be used internally as well as by the client

func SendMessageToCurrentRoom(w http.ResponseWriter, r *http.Request)  {
  clientName := mux.Vars(r)["USER"]
  message := r.Header.Get("message")
  sender := getClientByName(clientName)
  sendMessageToCurrentRoom(sender, message)
}

//sends a message to the clients current room, this function will replacee the WriteToAllChans function which sends a message to every client on the server
func sendMessageToCurrentRoom(sender *Client, message string){
  //check if the client is currently in a room warn otherwise
  if sender.currentRoom == nil {
    //sender is not in room yet warn and exit
    sender.outputChannel <- "You are not in a room yet\n"
    return
  }
  //get the current room and its list of clients
  //send the message to everyone in the room list that is CURRENTLY in the room
  room := sender.currentRoom
  chatMessage := &ChatMessage{
		client: sender,
		message: message,
	}
  fmt.Println("current room UserArray: ")
  fmt.Println(room.clientList)
  fmt.Println(room.clientList[0].currentRoom)
  for _, roomUser := range room.clientList {
    //check to see if the user is currently active in the room
    if roomUser.currentRoom.name == room.name {
      go roomUser.messageClientFromClient(chatMessage.message, chatMessage.client)
    }
  }
  //save the message into the array of the rooms messages
  room.chatLog = append(room.chatLog, chatMessage)
}
//*****************RPC SERVER OBJECT************************
//here we will create a server object and expose the processing commands to the client, this way the client will be able to
//directly call the below functions
//the client must call this one time, it will set the client up on the server and send the server back their unique name
func Connect(w http.ResponseWriter, r *http.Request) {
   reply := ""
    if len(ClientArray) < 10{//server can have more clients
      reply = addClient()
    }else{
      reply = "ERROR_FULL"
    }
    fmt.Fprintf(w, reply)
}
//passes the latest message on the clients output channel to the client
func  MessageClient(w http.ResponseWriter, r *http.Request)  {
  clientName := mux.Vars(r)["USER"]
  cli := getClientByName(clientName)
  reply := <-cli.outputChannel
  fmt.Fprintf(w, reply)
}
//creates a room and logs to the console
func createroom(w http.ResponseWriter, r *http.Request){

  roomName := mux.Vars(r)["ROOMNAME"]
  clientName := r.Header.Get("username")
  client := getClientByName(clientName)

  //Creates a new room, with a specified roomCreator and roomName. the room will be added to the global list of rooms, if room is not unique, the client will be messaged
  //check uniqueness of name, warn user and abort if not unique
  for _, room := range RoomArray {
  	if roomName == room.name {
  		client.outputChannel <- "The room name you have specified is already in use\n"
  		return
  	}
  }
  newRoom := &Room{
  	name: roomName,
  	clientList: make([]*Client, 0),//room will start empty, we wont add the creator in
  	chatLog: nil,
  	creator: client,
  }
  RoomArray = append(RoomArray, newRoom)
  if newRoom == nil { //name of room was not unique
    return
  }
  client.outputChannel <- client.name+" created a new room called "+roomName + "\n"
}
func leaveroom(w http.ResponseWriter, r *http.Request){
  clientName := mux.Vars(r)["USER"]
  client := getClientByName(clientName)
  client.removeClientFromCurrentRoom()
  client.outputChannel <- "You have left the room\n"
}
//Loops through the HELP_INFO array and sends all the lines of help info to the user
func help(w http.ResponseWriter, r *http.Request){
  clientName := r.Header.Get("username")
  fmt.Println(clientName)
  client := getClientByName(clientName)
  for _, helpLine := range helpInfo{
      client.outputChannel <- helpLine + "\n"
  }
}
//sends the list of rooms to the client
func listrooms(w http.ResponseWriter, r *http.Request){
  clientName := r.Header.Get("username")
  client := getClientByName(clientName)
  client.outputChannel <- "List of rooms:\n"
  for _, roomName := range RoomArray{
    client.outputChannel <- roomName.name + "\n"
  }
}
//returns true of the room was joined successfully, returns false if there was a problem like the room does not exist
func join(w http.ResponseWriter, r *http.Request){
  clientName := mux.Vars(r)["USER"]
  roomName := mux.Vars(r)["ROOMNAME"]
  client := getClientByName(clientName)
  roomToJoin := getRoomByName(roomName)
  if roomToJoin == nil{ //the room doesnt exist
    fmt.Println(client.name+" tried to enter room: "+roomName+" which does not exist")
    client.outputChannel <- "The room "+roomName+" does not exist\n"
    return
  }
  //Room exists so now we can join it.
  //check if user is already in the room
  //add user to room if not in it already
  if roomToJoin.isClientInRoom(client) {
      client.outputChannel <- "You are already in that room\n"
  } else {//join room and display all the rooms messages
    client.removeClientFromCurrentRoom()
    roomToJoin.clientList = append(roomToJoin.clientList, client)// add client to the rooms list
    //switch users current room to room
    client.currentRoom = roomToJoin
    sendMessageToCurrentRoom(client, "CLIENT HAS JOINED THE ROOM")
    //display all messages in the room when a user first joins a room
	if roomToJoin.chatLog == nil{
    	return
	}
	for _, messages := range roomToJoin.chatLog {
		client.messageClientFromClient(messages.message, messages.client)
	}
	client.outputChannel <- "----------------------\n"
  }
}
**客户端**:

  
package main

    import "io/ioutil"
    import "fmt"
    import "bufio"
    import "os"
    import "strings"
    import "net/http"
    import "errors"

    var stayAlive = true
    var myName = ""
    var site = ""

    //starts up the client, starts the receiving thread and the input threads and then loops forever
    func main() {

      arguments := os.Args[1:]
      if len(arguments) == 0 {
        //no arguments start on localhost 8080
      } else if len(arguments) != 2 {
        fmt.Println("I cannot understand your arguments, you must specify no arguments or exactly 2, first the IP and the second as the port")
        return
      }
      fmt.Println("Attempting to connect to localhost: 8080")
      fmt.Print("Enter your name: ")
      site = "http://localhost:8080"
      resp, err := http.Get(site)
      if err != nil{
        fmt.Printf("Something went wrong with the connection, check that the server exists and that your IP/Port are correct:\nError Message: %v", err)
        return
      }
      defer resp.Body.Close()
      body, _ := ioutil.ReadAll(resp.Body)
      fmt.Println(string(body))
      ////////////////////////////////////////////////////////////////////
      //////////////////////and here I use that name as a user name/////////////////////////////////////////
      myName = string(body)
      fmt.Println("Your Username is: " + myName)
      go getfromUser()
      go getFromServer()
      for stayAlive {
        //loops  forever until stayAlive is set to false and then it shuts down
      }
    }
        //continually asks the server for input by calling for messages for the user
        func getFromServer(){
          for stayAlive {
            resp, err := http.Get(site+"/"+myName+"/messages")
            if err != nil{
              fmt.Println("error in getting messages")
              fmt.Println(err)
              stayAlive = false
              return
            }
            defer resp.Body.Close()
            body, _ := ioutil.ReadAll(resp.Body)
            fmt.Print(string(body))
          }
        }
        //creates the http message that will be sent to the server
        func messageHelper(method string, url string) error{
          client := &http.Client{
            CheckRedirect: nil,
          }
            reply, err  := http.NewRequest(method, url, nil)
            reply.Header.Add("username", myName)
            client.Do(reply)
            return err
        }
        //Handles user input, reads from stdin and then posts that line to the server
        func getfromUser(){
            for stayAlive{
              reader := bufio.NewReader(os.Stdin)
              message, _ := reader.ReadString('\n')//read from stdin till the next newline
              var err error
              message = strings.TrimSpace(message)//strips the newlines from the input
              isCommand := strings.HasPrefix(message, "/")//checks to see if the line starts with /
              if isCommand{
                	//parse command line, commands should be in the exact form of "/command arg arg arg" where args are not required
                	parsedCommand := strings.Split(message, " ")
        			if parsedCommand[0] == "/help" {
        			  err = messageHelper("GET", site+"/help")
        			}else if parsedCommand[0] == "/createroom" {
        			  // not enough arguments to the command
        				  if len(parsedCommand) < 2{
        					err = errors.New("not enough args for create room")
        				  }else{
        					err = messageHelper("POST", site+"/rooms/"+parsedCommand[1])
        				  }
        			}else if parsedCommand[0] == "/listrooms" {
        			  err = messageHelper("GET", site+"/rooms")
        			}else if parsedCommand[0] == "/join" {
        				  //not enough given to the command
        				  if len(parsedCommand) < 2{
        					err = errors.New("you must specify a room to join")
        				  }else{
        					err = messageHelper("POST", site+"/rooms/"+parsedCommand[1]+"/"+myName)
        				  }
        			}else if parsedCommand[0] == "/leave"{
        			  err = messageHelper("DELETE", site+"/"+myName+"/leaveroom")
        			}

              }else if stayAlive{ // message is not a command
                //we need to create a post request to send the message to the server
                client := &http.Client{
                  CheckRedirect: nil,
                }
                  sendReply, _  := http.NewRequest("POST", site+"/"+myName+"/messageRoom", nil)
                  sendReply.Header.Add("message", message)
                  client.Do(sendReply)
              }
              if err != nil{
                fmt.Println(err)
              }
            }
          }

更多关于Golang客户端服务器程序中如何使用用户输入的实战教程也可以访问 https://www.itying.com/category-94-b0.html

7 回复

@parastoo 你找到这个问题的解决方案了吗?

更多关于Golang客户端服务器程序中如何使用用户输入的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


如果你将下划线替换为 err 并检查是否为 nil,ReadString() 是否仍然不会返回任何错误?

不,它不会返回任何错误。

reader := bufio.NewReader(os.Stdin)
clientName, err := reader.ReadString('\n')
if err != nil{
panic(err)
}

我猜测服务器正在等待客户端完成其请求,但由于某种原因客户端未能完成。

检查 client.Do() 返回的错误。并验证调用是否完全返回。一些适当放置的日志语句可能有助于揭示代码在何处开始挂起(以及原因)。

感谢您的建议。这些建议都是相互关联的,因此我无法删除部分代码,但您说得完全正确。

当我从用户那里获取输入后,程序就没有任何反应了,应用程序的其余部分也无法继续运行。

你能否创建一个展示相同问题的简化代码示例?请记住,你的代码会暴露在许多读者面前,他们都需要花时间阅读代码并尝试识别可能的问题。

当你说代码无法运行时,是指它出现错误信息而失败吗?还是仅仅退出?或者继续运行但似乎没有任何反应?

还有一个故障排除建议:务必检查错误。下面这行代码中被忽略的错误

clientName, _ := reader.ReadString(’\n’)

很可能隐藏了能帮助你解决问题的错误信息。

最后一个提示:在这里发布代码时请使用代码围栏。也就是说,将你的代码放在包含三个反引号的行之间,可以选择性地加上语言名称:

// Your go code here
package main

这样显示效果如下:

// Your go code here
package main

在你的代码中,问题出现在服务器端的 addClient() 函数中使用了 bufio.NewReader(os.Stdin) 来读取用户输入。由于服务器程序通常作为后台服务运行,不应该从标准输入读取数据,这会导致程序阻塞等待输入,从而影响其他功能的正常运行。

正确的做法是在客户端获取用户输入,然后通过 HTTP 请求将用户名发送到服务器。以下是修改后的代码:

服务器端修改

移除 addClient() 函数中的标准输入读取,改为从 HTTP 请求中获取用户名:

func addClient(clientName string) string {
   createOutputChannel := make(chan string)
   cli := Client{
     currentRoom: nil,
     outputChannel: createOutputChannel,
     name: clientName,
   }
   ClientArray = append(ClientArray, &cli)
   return cli.name
}

func Connect(w http.ResponseWriter, r *http.Request) {
   reply := ""
   if len(ClientArray) < 10 {
     // 从请求参数中获取用户名
     clientName := r.URL.Query().Get("name")
     if clientName == "" {
       // 如果没有提供用户名,生成一个默认的
       clientName = generateDefaultName()
     }
     reply = addClient(clientName)
   } else {
     reply = "ERROR_FULL"
   }
   fmt.Fprintf(w, reply)
}

客户端修改

在客户端连接时先获取用户输入的用户名,然后将其作为参数发送到服务器:

func main() {
  arguments := os.Args[1:]
  if len(arguments) == 0 {
    site = "http://localhost:8080"
  } else if len(arguments) != 2 {
    fmt.Println("I cannot understand your arguments, you must specify no arguments or exactly 2, first the IP and the second as the port")
    return
  }
  
  fmt.Print("Enter your name: ")
  reader := bufio.NewReader(os.Stdin)
  userName, _ := reader.ReadString('\n')
  userName = strings.TrimSpace(userName)
  
  fmt.Println("Attempting to connect to", site)
  
  // 将用户名作为查询参数发送
  resp, err := http.Get(site + "/?name=" + userName)
  if err != nil {
    fmt.Printf("Something went wrong with the connection, check that the server exists and that your IP/Port are correct:\nError Message: %v", err)
    return
  }
  defer resp.Body.Close()
  body, _ := ioutil.ReadAll(resp.Body)
  
  myName = string(body)
  fmt.Println("Your Username is: " + myName)
  
  go getfromUser()
  go getFromServer()
  for stayAlive {
    // 保持主循环运行
  }
}

辅助函数(如果需要生成默认用户名):

func generateDefaultName() string {
    return "user_" + fmt.Sprintf("%d", time.Now().UnixNano())
}

这样修改后,用户输入在客户端处理,然后通过 HTTP 请求发送到服务器,避免了服务器阻塞在标准输入读取上,确保了程序的正常运行。

回到顶部