better networking

This commit is contained in:
Beric Bearnson 2024-08-15 20:08:23 -06:00
parent 35d8ebc459
commit e90b9c26b1
6 changed files with 265 additions and 307 deletions

View File

@ -1,95 +1,107 @@
package main
import (
"bufio"
"fmt"
"io"
"log"
"os"
"sshpong/internal/client"
"sshpong/internal/netwrk"
"strings"
"google.golang.org/protobuf/proto"
)
var exit chan bool
func main() {
lobbyChan := make(chan netwrk.LobbyPlayerStatus)
interrupter := make(chan netwrk.Interrupter)
messageOutput := make(chan *netwrk.LobbyMessage)
inputChan := make(chan string)
fmt.Println("Welcome to sshpong!")
fmt.Println("Please enter your username")
go func() {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
text := scanner.Text()
inputChan <- text
}
}()
reader := bufio.NewReader(os.Stdin)
username, err := reader.ReadString('\n')
if err != nil {
fmt.Println("Error reading from your shit bro...")
}
go netwrk.ConnectToLobby(username, messageOutput, lobbyChan, interrupter)
egress := make(chan *netwrk.LobbyMessage)
ingress := make(chan *netwrk.LobbyMessage)
buf := make([]byte, 1024)
n, err := os.Stdin.Read(buf)
if err != nil {
log.Panic("Bro your input is no good...")
}
username := string(buf[:n])
for {
select {
case msg := <-interrupter:
fmt.Println(msg.Message)
default:
n, err := os.Stdin.Read(buf)
if err != nil {
fmt.Println("Error reading from stdin")
return
}
input := string(buf[:n])
args := strings.Fields(input)
switch args[0] {
case "invite":
if args[1] != "" {
messageOutput <- &netwrk.LobbyMessage{
PlayerId: username,
Type: "invite",
Content: args[1],
}
} else {
fmt.Println("Please provide a player to invite ")
}
case "chat":
if args[1] != "" {
messageOutput <- &netwrk.LobbyMessage{
PlayerId: username,
Type: "chat",
Content: strings.Join(args[1:], " "),
}
}
case "/":
if args[1] != "" {
messageOutput <- &netwrk.LobbyMessage{
PlayerId: username,
Type: "chat",
Content: strings.Join(args[1:], " "),
}
}
case "quit":
return
case "q":
return
case "help":
fmt.Println("use invite <player name> to invite a player\nchat or / to send a message to the lobby\nq or quit to leave the game")
case "h":
fmt.Println("use invite <player name> to invite a player\nchat or / to send a message to the lobby\nq or quit to leave the game")
default:
fmt.Println("use invite <player name> to invite a player\nchat or / to send a message to the lobby\nq or quit to leave the game")
}
}
conn, err := netwrk.ConnectToLobby(username)
if err != nil {
log.Panic(err)
}
// User input handler
go func(egress chan *netwrk.LobbyMessage) {
buf := make([]byte, 1024)
for {
n, err := os.Stdin.Read(buf)
if err != nil {
log.Panic("Bro your input wack as fuck")
}
userMessage, err := client.HandleUserInput(buf[:n])
if err != nil {
fmt.Println(err)
continue
}
userMessage.PlayerId = username
egress <- userMessage
}
}(egress)
// Ingress Handler
go func(oc chan *netwrk.LobbyMessage) {
for {
msg := <-ingress
client.HandleServerMessage(msg)
}
}(ingress)
// Network writer
go func(userMessages chan *netwrk.LobbyMessage) {
for {
msg := <-userMessages
bytes, err := proto.Marshal(msg)
if err != nil {
log.Panic("Malformed proto message", err)
}
_, err = conn.Write(bytes)
if err == io.EOF {
log.Panic("Server disconnected sorry...")
} else if err != nil {
log.Panic("Error reading from server connection...")
}
}
}(egress)
// Network reader
go func(serverMessages chan *netwrk.LobbyMessage) {
buf := make([]byte, 1024)
for {
n, err := conn.Read(buf)
if err == io.EOF {
log.Panic("Server disconnected sorry...")
} else if err != nil {
log.Panic("Error reading from server connection...")
}
message := &netwrk.LobbyMessage{}
err = proto.Unmarshal(buf[:n], message)
if err != nil {
log.Panic("Error reading message from server")
}
serverMessages <- message
}
}(ingress)
_ = <-exit
}

View File

@ -5,9 +5,12 @@ import (
"sshpong/internal/netwrk"
)
var exit chan bool
func main() {
fmt.Println("Starting sshpong server!")
netwrk.Listen()
_ = <-exit
}

View File

@ -0,0 +1,71 @@
package client
import (
"fmt"
"io"
"log"
"sshpong/internal/netwrk"
"strings"
)
func HandleUserInput(buf []byte) (*netwrk.LobbyMessage, error) {
input := string(buf)
args := strings.Fields(input)
switch args[0] {
case "invite":
if args[1] != "" {
return &netwrk.LobbyMessage{
Type: "invite",
Content: args[1],
}, nil
} else {
fmt.Println("Please provide a player to invite ")
}
case "chat":
if args[1] != "" {
return &netwrk.LobbyMessage{
Type: "chat",
Content: strings.Join(args[1:], " "),
}, nil
}
case "/":
if args[1] != "" {
return &netwrk.LobbyMessage{
Type: "chat",
Content: strings.Join(args[1:], " "),
}, nil
}
case "quit":
return nil, io.EOF
case "q":
return nil, io.EOF
case "help":
return nil, fmt.Errorf("use invite <player name> to invite a player\nchat or / to send a message to the lobby\nq or quit to leave the game")
case "h":
return nil, fmt.Errorf("use invite <player name> to invite a player\nchat or / to send a message to the lobby\nq or quit to leave the game")
default:
return nil, fmt.Errorf("use invite <player name> to invite a player\nchat or / to send a message to the lobby\nq or quit to leave the game")
}
return nil, nil
}
func HandleServerMessage(message *netwrk.LobbyMessage) {
switch message.Type {
case "invite":
log.Println(message.PlayerId, "is inviting you to a game.", message.Content)
case "accepted":
log.Println(message.PlayerId, "accepted your invite.", message.Content)
case "text":
log.Println(message.PlayerId, ":", message.Content)
case "decline_game":
log.Println("Invite was declined:", message.Content)
case "disconnect":
log.Println("Got disconnect for player:", message.Content)
case "connect":
log.Println("Got connect for player:", message.Content)
case "pong":
log.Println("Received", message.Content)
default:
log.Println("Received", message.Content)
}
}

View File

@ -2,152 +2,26 @@ package netwrk
import (
"fmt"
"log"
"net"
"strings"
"google.golang.org/protobuf/proto"
)
type LobbyPlayerStatus struct {
Username string
Status string
}
type Interrupter struct {
MessageType string
Message string
ReplyChan chan string
}
var username string
var lobby chan LobbyPlayerStatus
var interruptChan chan Interrupter
func ConnectToLobby(playerUsername string, messageOutputChan chan *LobbyMessage, lobbyMessageChan chan LobbyPlayerStatus, interruptChannel chan Interrupter) {
username = playerUsername
lobby = lobbyMessageChan
interruptChan = interruptChannel
func ConnectToLobby(username string) (net.Conn, error) {
conn, err := net.Dial("tcp", "127.0.0.1:12345")
if err != nil {
fmt.Println("Sorry, failed to connect to server...")
return
return nil, fmt.Errorf("Sorry, failed to connect to server...")
}
loginMsg, err := proto.Marshal(&LobbyMessage{Type: "name", Content: username})
if err != nil {
fmt.Println("Sorry bro but your username is wack AF...")
return nil, fmt.Errorf("Sorry bro but your username is wack AF...")
}
_, err = conn.Write(loginMsg)
if err != nil {
fmt.Println("Sorry, could not communicate with server...")
return nil, fmt.Errorf("Sorry, could not communicate with server...")
}
fmt.Println("Starting client loop")
messageInputChan := make(chan *LobbyMessage)
go func() {
for {
messageBytes := make([]byte, 1024)
n, err := conn.Read(messageBytes)
if err != nil {
fmt.Println("Sorry, failed to read message from server...", err)
}
message := &LobbyMessage{}
err = proto.Unmarshal(messageBytes[:n], message)
if err != nil {
fmt.Println("Sorry, the server sent something weird back...")
}
messageInputChan <- message
}
}()
for {
select {
case msg := <-messageInputChan:
if isDone, gameID := handleLobbyMessage(conn, msg); isDone {
if gameID != "" {
interruptChan <- Interrupter{
MessageType: "game",
Message: gameID,
ReplyChan: make(chan string),
}
return
} else {
return
}
}
case msg := <-messageOutputChan:
fmt.Println("Sending message out", msg)
err := SendMessageToServer(conn, msg)
if err != nil {
fmt.Println("Error!", err)
}
}
}
}
func handleLobbyMessage(serverConn net.Conn, message *LobbyMessage) (bool, string) {
switch message.Type {
case "text":
fmt.Println(message.Content)
return false, ""
case "error":
fmt.Println("Error:", message.Content)
return false, ""
case "invite":
fmt.Println("GOT INVITE!")
replyChan := make(chan string)
interruptChan <- Interrupter{
MessageType: "invite",
Message: fmt.Sprintf("Invite from player %s\nAccept: Y Decline: N", message.Content),
ReplyChan: replyChan,
}
input := <-replyChan
if strings.ToLower(input) == "yes" || strings.ToLower(input) == "y" {
SendMessageToServer(serverConn, &LobbyMessage{Type: "accept_game", Content: username})
SendMessageToServer(serverConn, &LobbyMessage{Type: "quit", Content: username})
return true, message.Content
} else {
SendMessageToServer(serverConn, &LobbyMessage{Type: "decline_game", Content: username})
}
return false, ""
case "accept":
fmt.Println(message.Content, "accepted your invite. Game starting...")
SendMessageToServer(serverConn, &LobbyMessage{Type: "quit", Content: username})
return true, message.Content
case "decline_game":
fmt.Println("Sorry,", message.Content, "declined your game invite...")
return false, ""
case "connect":
lobby <- LobbyPlayerStatus{Username: message.Content, Status: "connected"}
fmt.Println(message.Content, "connected!")
return false, ""
case "disconnect":
lobby <- LobbyPlayerStatus{Username: message.Content, Status: "disconnected"}
fmt.Println(message.Content, "disconnected!")
return false, ""
case "pong":
log.Println("PoNg!")
return false, ""
default:
log.Println("Got message", message)
}
return false, ""
}
func SendMessageToServer(connection net.Conn, message *LobbyMessage) error {
bytes, err := proto.Marshal(message)
if err != nil {
return fmt.Errorf("Error marshalling message. Your protobuf is wack yo.")
}
_, err = connection.Write(bytes)
if err != nil {
return fmt.Errorf("Error writing to client connection")
}
return nil
return conn, nil
}

View File

@ -1,7 +1,6 @@
package netwrk
import (
"fmt"
"io"
"log"
"net"
@ -14,15 +13,16 @@ func handleLobbyConnection(conn net.Conn) {
messageBytes := make([]byte, 4096)
recvMessageChan := make(chan *LobbyMessage)
ingress := make(chan *LobbyMessage)
egress := make(chan *LobbyMessage)
// Network Reader
go func() {
for {
fmt.Println("READING!")
n, err := conn.Read(messageBytes)
if err == io.EOF {
return
}
fmt.Println("READ something!")
if err != nil {
log.Printf("Error reading message %v", err)
return
@ -32,106 +32,103 @@ func handleLobbyConnection(conn net.Conn) {
err = proto.Unmarshal(messageBytes[:n], &message)
if err != nil {
log.Println("Invalid message received from client")
log.Println("Invalid message received from client", err)
}
recvMessageChan <- &message
ingress <- &message
}
}()
for {
select {
case msg := <-recvMessageChan:
if isDone, err := handleClientLobbyMessage(conn, msg); err != nil || isDone {
log.Println(err)
return
// Network Writer
go func() {
for {
msg := <-egress
bytes, err := proto.Marshal(msg)
if err != nil {
log.Println("Error marshalling message to send to user...", err)
}
_, err = conn.Write(bytes)
if err == io.EOF {
log.Println("User has disconnected", err)
ingress <- &LobbyMessage{Type: "disconnect"}
}
if err != nil {
log.Println("Error writing to user...", err)
}
fmt.Println("Handled message")
}
}
}()
// Client message handler
go func() {
for {
msg := <-ingress
serverMsg, err := handleClientLobbyMessage(msg)
if err != nil {
log.Println("Error handling client lobby message...", err)
}
if serverMsg != nil {
egress <- serverMsg
}
}
}()
}
// Returns a bool of whether the player has disconnected from the lobby and an error
func handleClientLobbyMessage(playerConnection net.Conn, message *LobbyMessage) (bool, error) {
func handleClientLobbyMessage(message *LobbyMessage) (*LobbyMessage, error) {
switch message.Type {
case "name":
_, ok := clientPool.clients[message.Content]
_, ok := lobbyMembers.Load(message.Content)
if ok {
SendMessageToClient(playerConnection, &LobbyMessage{Type: "error", Content: "Sorry, that name is already taken"})
return false, nil
}
playerID := message.Content
clientPool.clients[playerID] = Client{
name: playerID,
conn: playerConnection,
ready: false,
}
for _, player := range clientPool.clients {
err := SendMessageToClient(playerConnection, &LobbyMessage{PlayerId: player.name, Type: "connect", Content: player.name})
if err != nil {
log.Println("There was an error sending the list of lobby players to client", message.Content)
}
return &LobbyMessage{Type: "name_error", Content: "Sorry, that name is already taken, please try a different name"}, nil
}
username := message.Content
// Send all client messages
lobbyMembers.Range(func(lobbyUsername string, client Client) bool {
externalMessageChan <- ExternalMessage{Target: username, Message: &LobbyMessage{Type: "connect", Content: lobbyUsername}}
return true
})
log.Println("Broadcasting new player", message.Content)
broadcastToLobby(&LobbyMessage{PlayerId: "", Type: "connect", Content: playerID})
broadcastToLobby(&LobbyMessage{PlayerId: "", Type: "connect", Content: username})
return false, SendMessageToClient(playerConnection, &LobbyMessage{PlayerId: playerID, Type: "name", Content: playerID})
return &LobbyMessage{PlayerId: username, Type: "name", Content: username}, nil
case "invite":
log.Println("Got invite for player:", message.Content)
invitee, ok := clientPool.clients[message.Content]
invitee, ok := lobbyMembers[message.Content]
if !ok {
SendMessageToClient(playerConnection, &LobbyMessage{Type: "text", Content: "Sorry, that player is not available..."})
return false, nil
return &LobbyMessage{Type: "text", Content: "Sorry, that player is not available..."}, nil
}
SendMessageToClient(invitee.conn, &LobbyMessage{Type: "invite", Content: message.PlayerId})
return false, nil
return &LobbyMessage{Type: "invite", Content: message.PlayerId}, nil
case "accept_game":
player := clientPool.clients[message.Content]
player := lobbyMembers[message.Content]
if err := SendMessageToClient(player.conn, &LobbyMessage{Type: "accept", Content: ""}); err != nil {
SendMessageToClient(playerConnection, &LobbyMessage{Type: "error", Content: "Sorry that game is no longer available..."})
return false, nil
}
return &LobbyMessage{Type: "accept", Content: ""}, nil
return true, nil
case "chat":
broadcastToLobby(&LobbyMessage{PlayerId: message.PlayerId, Type: "text", Content: message.Content})
return false, nil
return nil, nil
case "decline_game":
inviter := clientPool.clients[message.Content]
SendMessageToClient(inviter.conn, &LobbyMessage{Type: "decline_game", Content: message.PlayerId})
return false, nil
inviter := lobbyMembers[message.Content]
return &LobbyMessage{Type: "decline_game", Content: message.PlayerId}, nil
case "quit":
delete(clientPool.clients, message.PlayerId)
delete(lobbyMembers, message.PlayerId)
broadcastToLobby(&LobbyMessage{Type: "disconnect", Content: message.PlayerId})
return true, nil
return nil, nil
case "ping":
SendMessageToClient(playerConnection, &LobbyMessage{Type: "pong", Content: "pong"})
return false, nil
return &LobbyMessage{Type: "pong", Content: "pong"}, nil
default:
SendMessageToClient(playerConnection, &LobbyMessage{Type: "pong", Content: "pong"})
return false, nil
return &LobbyMessage{Type: "pong", Content: "pong"}, nil
}
}
func SendMessageToClient(connection net.Conn, message *LobbyMessage) error {
bytes, err := proto.Marshal(message)
if err != nil {
return fmt.Errorf("Error marshalling message. Your protobuf is wack yo.")
}
_, err = connection.Write(bytes)
if err != nil {
return fmt.Errorf("Error writing to client connection")
}
fmt.Println("Sent message to client")
return nil
}
func broadcastToLobby(message *LobbyMessage) {
for _, player := range clientPool.clients {
err := SendMessageToClient(player.conn, message)
for _, player := range lobbyMembers {
bytes, err := proto.Marshal(message)
if err != nil {
log.Println("Error marshalling broadcast message", err)
}
_, err = player.Conn.Write(bytes)
if err != nil {
log.Println("Error broadcasting to clients...", err)
}

View File

@ -3,36 +3,40 @@ package netwrk
import (
"log"
"net"
sync "sync"
)
type Client struct {
name string
conn net.Conn
ready bool
Username string
Conn net.Conn
}
type ClientPool struct {
clients map[string]Client
type LobbyPlayersMessage struct {
Type string
Username string
IsAvailable chan bool
}
type GameClients struct {
client1 chan GameMessage
client2 chan GameMessage
type ExternalMessage struct {
Target string
Message *LobbyMessage
}
type GameChans struct {
games map[string]GameClients
}
var lobbyListener chan LobbyPlayersMessage
var externalMessageChan chan ExternalMessage
var clientPool *ClientPool
var gameChans *GameChans
var lobbyMembers sync.Map
func init() {
lobbyListener = make(chan LobbyPlayersMessage)
externalMessageChan = make(chan ExternalMessage)
lobbyMembers = sync.Map{}
}
// Starts listening on port 12345 for TCP connections
// Also creates client pool and game connection singletons
func Listen() {
clientPool = &ClientPool{
clients: map[string]Client{},
}
func LobbyListen() {
listener, err := net.Listen("tcp", "127.0.0.1:12345")
if err != nil {
@ -41,21 +45,18 @@ func Listen() {
defer listener.Close()
go func() {
for {
conn, err := listener.Accept()
log.Println("got a connection!")
if err != nil {
log.Println(err)
continue
}
go handleLobbyConnection(conn)
for {
conn, err := listener.Accept()
log.Println("got a connection!")
if err != nil {
log.Println(err)
continue
}
}()
gameChans = &GameChans{
games: map[string]GameClients{},
go handleLobbyConnection(conn)
}
}
func GamesListen() {
gameListener, err := net.Listen("tcp", "127.0.0.1:42069")
if err != nil {