From 2172b7410566e6a082f6dab82cb1bef61a149f89 Mon Sep 17 00:00:00 2001 From: Richard Date: Sun, 24 Sep 2023 16:01:57 +0200 Subject: [PATCH] Chat --- chat.go | 210 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 4 +- 2 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 chat.go diff --git a/chat.go b/chat.go new file mode 100644 index 0000000..b16ff07 --- /dev/null +++ b/chat.go @@ -0,0 +1,210 @@ +package main + +import ( +"net" +"os" +"fmt" +"strings" +"bufio" +"regexp" +) + +type ChatMessage struct { + from uint + mtype uint + data string +} + +type ChatServer struct { + port uint16 + numSessions uint + q chan ChatMessage + clients map[uint]*ChatSession +} + +func NewChatServer(port uint16) *ChatServer { + return &ChatServer{ + port, + 0, + make(chan ChatMessage), + make(map[uint]*ChatSession), + } +} + +type ChatSession struct{ + sessionId uint + username string + q chan string + backend chan ChatMessage + con net.Conn + isOnline bool +} + +func NewChatSession(sessionId uint, con net.Conn, backend chan ChatMessage) *ChatSession { + return &ChatSession{ + sessionId, + "", + make(chan string), + backend, + con, + false, + } +} + +func (s *ChatServer) broadcaster(){ + + for m := range s.q { + client, ok := s.clients[m.from] + if ok != true { continue } + switch m.mtype { + case 1: s.handleJoin(m, client) + case 2: s.handleMsg(m, client) + case 3: s.handleLeave(m, client) + } + } +} + +func (s *ChatServer) chatNames(except uint) string { + first := true + result := "" + for key, session := range s.clients { + if key == except { + continue + } + if first { + first = false + } else { + result += ", " + } + result += session.username + } + if first { + result = "empty space" + } + return result +} +func (s *ChatServer) handleJoin(m ChatMessage, c *ChatSession) { + msg := fmt.Sprintf("* You see %s floating around here\n", s.chatNames(m.from)) + c.q <- msg + msg = fmt.Sprintf("* %s just docked!\n", c.username) + fmt.Printf(msg) + for key, session := range s.clients { + if key == m.from || session.isOnline == false { + continue + } + session.q <- msg + } +} + +func (s *ChatServer) handleMsg(m ChatMessage, c *ChatSession) { + msg := fmt.Sprintf("[%s] %s\n", c.username, m.data) + fmt.Printf(msg) + for key, session := range s.clients { + if key == m.from || session.isOnline == false{ + continue + } + session.q <- msg + } +} + +func (s *ChatServer) handleLeave(m ChatMessage, c *ChatSession) { + msg := fmt.Sprintf("* %s just fired up his hyperdrive!\n", c.username) + delete(s.clients, c.sessionId) + fmt.Println(msg) + for key, session := range s.clients { + if key == m.from || session.isOnline == false { + continue + } + session.q <- msg + } +} + +func (s *ChatServer) Run() { + go s.broadcaster() + addr := fmt.Sprintf("0.0.0.0:%d", s.port) + server, err := net.Listen("tcp", addr) + if err != nil { + fmt.Println("Error listening:", err.Error()) + os.Exit(1) + } + defer server.Close() + fmt.Println("ChatServer waiting for client...") + for { + connection, err := server.Accept() + if err != nil { + fmt.Println("Error accepting: ", err.Error()) + os.Exit(1) + } + fmt.Println("client connected") + s.processClient(connection) + } + close(s.q) +} + +func (s *ChatServer) processClient(con net.Conn) { + sessionId := s.numSessions + s.numSessions += 1 + session := NewChatSession( sessionId, con, s.q) + s.clients[sessionId] = session + go session.chatReceiver() + go session.chatSender() +} + +func chatUsernameValid(username string) bool { + var invalidUsername = regexp.MustCompile(`[^0-9a-zA-Z]`) + + if len(username) < 1 { + return false + } + return ! invalidUsername.MatchString(username) +} +func (s *ChatSession) logon(r *bufio.Reader) error { + fmt.Fprintf(s.con, "Howdy! welcome to station 42!\n") + username, err := r.ReadString('\n') + if err != nil { return err } + username = strings.TrimSpace(username) + if ! chatUsernameValid(username) { + return fmt.Errorf("Invalid username %s", username) + } + s.isOnline = true + s.username = username + m := ChatMessage{s.sessionId, 1, username} + s.backend <- m + return nil +} + +func (s *ChatSession) chat(r *bufio.Reader) error { + msg, err := r.ReadString('\n') + if err != nil { return err } + msg = strings.TrimSpace(msg) + m := ChatMessage{s.sessionId, 2, msg} + s.backend <- m + return nil +} + +func (s *ChatSession) close() { + fmt.Printf("close %d\n", s.sessionId) + if s.isOnline { + m := ChatMessage{s.sessionId, 3, s.username} + s.backend <- m + } + s.isOnline = false + close(s.q) + s.con.Close() +} + +func (s *ChatSession) chatReceiver() { + defer s.close() + r := bufio.NewReader(s.con) + err := s.logon(r) + for err == nil { + err = s.chat(r) + } +} + +func (s *ChatSession) chatSender() { + for m := range s.q { + s.con.Write([]byte(m)) + } +} + diff --git a/main.go b/main.go index b507018..30dc7bf 100644 --- a/main.go +++ b/main.go @@ -12,7 +12,7 @@ type Server interface { func main() { var challenge int - flag.IntVar(&challenge, "challenge",2, "Challenge number") + flag.IntVar(&challenge, "challenge",3, "Challenge number") flag.Parse() var port uint16 @@ -25,6 +25,8 @@ func main() { server = NewPrimeServer(port); case 2: server = NewMeansServer(port); + case 3: + server = NewChatServer(port); default: fmt.Printf("Unknown challenge\n") os.Exit(1)