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
@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)
}
感谢您的建议。这些建议都是相互关联的,因此我无法删除部分代码,但您说得完全正确。
当我从用户那里获取输入后,程序就没有任何反应了,应用程序的其余部分也无法继续运行。
你能否创建一个展示相同问题的简化代码示例?请记住,你的代码会暴露在许多读者面前,他们都需要花时间阅读代码并尝试识别可能的问题。
当你说代码无法运行时,是指它出现错误信息而失败吗?还是仅仅退出?或者继续运行但似乎没有任何反应?
还有一个故障排除建议:务必检查错误。下面这行代码中被忽略的错误
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 请求发送到服务器,避免了服务器阻塞在标准输入读取上,确保了程序的正常运行。


