-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit ebc05a3
Showing
3 changed files
with
169 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
module github.com/ivanfetch/chat-server | ||
|
||
go 1.20 | ||
|
||
require ( | ||
github.com/sirupsen/logrus v1.9.2 // indirect | ||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect | ||
) |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= | ||
github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= | ||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= | ||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
package main | ||
|
||
import ( | ||
"bufio" | ||
"fmt" | ||
"net" | ||
"strings" | ||
|
||
log "github.com/sirupsen/logrus" | ||
) | ||
|
||
type message struct { | ||
sender, text string | ||
} | ||
|
||
// String formats the message sender and text. | ||
func (m message) String() string { | ||
return fmt.Sprintf("%s: %s\n", m.sender, m.text) | ||
} | ||
|
||
var debugLog *log.Logger = log.New() | ||
|
||
// startConnectionAndMessageManager runs a goroutine that tracks connections to the chat | ||
// server, and processes messages sent by clients. | ||
func startConnectionAndMessageManager(addConnCh, removeConnCh chan net.Conn, addMessageCh chan message) { | ||
var connections []net.Conn | ||
// TODO: Add context to respond to signals and cleanup connections? | ||
go func() { | ||
debugLog.Println("starting connection manager") | ||
for { | ||
select { | ||
case newConn := <-addConnCh: | ||
debugLog.Printf("adding connection from %s", newConn.RemoteAddr()) | ||
connections = append(connections, newConn) | ||
case removeConn := <-removeConnCh: | ||
debugLog.Printf("removing connection %s", removeConn.RemoteAddr()) | ||
newConnections := make([]net.Conn, len(connections)-1) | ||
newI := 0 | ||
for _, conn := range connections { | ||
if conn != removeConn { | ||
newConnections[newI] = conn | ||
newI++ | ||
} | ||
} | ||
connections = newConnections | ||
case newMessage := <-addMessageCh: | ||
debugLog.Printf("processing new message from %s: %s", newMessage.sender, newMessage.text) | ||
go broadcast(newMessage, connections, removeConnCh) | ||
default: | ||
// do nothing | ||
} | ||
} | ||
}() | ||
} | ||
|
||
// processInput scans a chat connection for text and hands chat commands or | ||
// messages. | ||
func processInput(con net.Conn, addMessageCh chan message, removeConnCh chan net.Conn) { | ||
log.Println("saying hello to our connection") | ||
fmt.Fprintln(con, `Well hello there! | ||
Anything you type to me will be displayed in the output of this chat-server. | ||
`) | ||
scanner := bufio.NewScanner(con) | ||
for scanner.Scan() { | ||
line := scanner.Text() | ||
debugLog.Printf("received from %s: %s", con.RemoteAddr(), line) | ||
if strings.HasPrefix(line, "/") { | ||
exiting := processCommands(line, con) | ||
if exiting { | ||
con.Close() | ||
removeConnCh <- con | ||
return | ||
} | ||
continue | ||
} | ||
addMessageCh <- message{ | ||
text: line, | ||
sender: con.RemoteAddr().String(), | ||
} | ||
} | ||
err := scanner.Err() | ||
if err != nil { | ||
debugLog.Println("while reading from %s: %v", con.RemoteAddr(), err) | ||
} | ||
} | ||
|
||
func broadcast(msg message, allConnections []net.Conn, removeConnCh chan net.Conn) { | ||
debugLog.Printf("broadcasting to %d connections: %s", len(allConnections), msg) | ||
for _, con := range allConnections { | ||
if con.RemoteAddr().String() == msg.sender { | ||
_, err := fmt.Fprintf(con, "> %s\n", msg.text) | ||
if err != nil { | ||
debugLog.Printf("error writing to %v: %v", con, err) | ||
removeConnCh <- con | ||
} | ||
continue | ||
} | ||
_, err := fmt.Fprintf(con, msg.String()) | ||
if err != nil { | ||
debugLog.Printf("error writing to %v: %v", con, err) | ||
removeConnCh <- con | ||
} | ||
} | ||
} | ||
|
||
func processCommands(input string, con net.Conn) (clientIsLeaving bool) { | ||
fields := strings.Fields(input) | ||
switch strings.ToLower(fields[0][1:]) { // first word minus its first character(/) | ||
case "quit", "exit", "leave": | ||
fmt.Fprintf(con, "You're leaving? Ok - have a nice day. :)\n") | ||
debugLog.Printf("client %s has signed off", con.RemoteAddr()) | ||
return true | ||
case "help": | ||
fmt.Fprintf(con, `Available commands are: | ||
/quit|leave|exit - Sign off and disconnect from chat. | ||
/nick|nickname - Set your nickname, to be displayed with your messages. | ||
`) | ||
default: | ||
fmt.Fprintf(con, "%q is an invalid command, please use /help for a list of commands.\n", fields[0]) | ||
} | ||
return false | ||
} | ||
|
||
func main() { | ||
debugLog.SetFormatter(&log.TextFormatter{ | ||
PadLevelText: true, | ||
}) | ||
|
||
const listenAddress = ":8080" | ||
l, err := net.Listen("tcp", listenAddress) | ||
if err != nil { | ||
debugLog.Fatalf("cannot listen: %v", err) | ||
} | ||
debugLog.Printf("listening for connections on %s", listenAddress) | ||
addConnCh := make(chan net.Conn) | ||
removeConnCh := make(chan net.Conn) | ||
addMessageCh := make(chan message) | ||
startConnectionAndMessageManager(addConnCh, removeConnCh, addMessageCh) | ||
debugLog.Println("waiting for new connections") | ||
for { | ||
con, err := l.Accept() | ||
if err != nil { | ||
debugLog.Printf("while accepting a connection: %v", err) | ||
continue | ||
} | ||
addConnCh <- con | ||
go processInput(con, addMessageCh, removeConnCh) | ||
} | ||
} |