Created
August 27, 2025 17:24
-
-
Save scottyob/9e4970a53c8abf05b0da201058997eba to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bufio" | |
"fmt" | |
"log" | |
"net" | |
"strings" | |
) | |
const ( | |
ServerPort = ":9091" | |
WelcomeMessage = `Welcome to the chat server! | |
Please be respectful to others. | |
` | |
) | |
type Nickname string | |
type Client struct { | |
Conn net.Conn | |
Nick Nickname | |
} | |
// SendMessage sends a message to the client | |
func (c *Client) SendMessage(msg string) { | |
fmt.Fprint(c.Conn, msg) | |
} | |
// SendBytes sends raw bytes to the client | |
func (c *Client) SendBytes(data []byte) { | |
c.Conn.Write(data) | |
} | |
type addClientRequest struct { | |
ClientToAdd *Client | |
SuccessCallback chan bool | |
} | |
type broadcastMessageRequest struct { | |
ClientSending *Client | |
Message string | |
} | |
func main() { | |
// Create a listener to listen on the chat server port | |
l, err := net.Listen("tcp", ServerPort) | |
if err != nil { | |
log.Fatal(err) | |
} | |
defer l.Close() | |
// Channels for clients to interact with the | |
// connection manager | |
addRequest := make(chan addClientRequest) | |
disconnected := make(chan Nickname) // Nickname disconnected | |
broadcastRequest := make(chan broadcastMessageRequest) | |
// Connection manager to handle interop between | |
// clients | |
go func() { | |
clients := make(map[Nickname]Client) | |
for { | |
select { | |
case r := <-addRequest: | |
// Check if the nickname already exists, if not, add it. | |
if _, found := clients[r.ClientToAdd.Nick]; found { | |
r.SuccessCallback <- false | |
continue | |
} | |
clients[r.ClientToAdd.Nick] = *r.ClientToAdd | |
// Send a welcome message with a list of users connected | |
nicks := make([]string, 0, len(clients)) | |
for nick := range clients { | |
nicks = append(nicks, string(nick)) | |
} | |
r.ClientToAdd.SendBytes([]byte(WelcomeMessage)) | |
r.ClientToAdd.SendMessage(fmt.Sprintf("Welcome %s! Clients online: %q\r\n", r.ClientToAdd.Nick, nicks)) | |
r.SuccessCallback <- true | |
case nick := <-disconnected: | |
// Remove the clients from the map, send a disconnect message | |
delete(clients, nick) | |
for _, client := range clients { | |
client.SendMessage(fmt.Sprintf("%s has left the chat.\r\n", nick)) | |
} | |
case req := <-broadcastRequest: | |
// Broadcast a message to every client except the one sending | |
for nick, client := range clients { | |
if nick == req.ClientSending.Nick { | |
continue | |
} | |
client.SendMessage(req.Message) | |
} | |
} | |
} | |
}() | |
for { | |
// Accept a new client connection | |
c, err := l.Accept() | |
if err != nil { | |
log.Println(err) | |
continue | |
} | |
// Goroutine to handle this client | |
go func() { | |
defer c.Close() | |
scanner := bufio.NewScanner(c) | |
me := Client{ | |
Conn: c, | |
} | |
// Loop until a valid nickname is given | |
for validNick := false; !validNick; { | |
me.SendMessage("Enter your nickname: ") | |
if !scanner.Scan() { | |
// Client disconnected | |
return | |
} | |
nickAddSuccess := make(chan bool) | |
me.Nick = Nickname(scanner.Text()) | |
addRequest <- addClientRequest{ | |
ClientToAdd: &me, | |
SuccessCallback: nickAddSuccess, | |
} | |
validNick = <-nickAddSuccess | |
if !validNick { | |
me.SendMessage("Sorry, That nick is already taken.\r\n") | |
} | |
} | |
me.SendMessage("> ") | |
broadcastRequest <- broadcastMessageRequest{ | |
ClientSending: &me, | |
Message: fmt.Sprintf("%s has joined the chat\r\n", me.Nick), | |
} | |
// Handle messages | |
for scanner.Scan() { | |
me.SendMessage("> ") | |
msg := strings.TrimSpace(scanner.Text()) | |
if msg == "" { | |
continue | |
} | |
broadcastRequest <- broadcastMessageRequest{ | |
ClientSending: &me, | |
Message: fmt.Sprintf("[%s] %s\r\n", me.Nick, scanner.Text()), | |
} | |
} | |
// Client disconnected | |
disconnected <- me.Nick | |
}() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.