finish budgetchat
This commit is contained in:
parent
0499ea30fe
commit
8f0f04e2f2
|
@ -0,0 +1,96 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type client struct {
|
||||||
|
c net.Conn
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidName(name string) bool {
|
||||||
|
pattern := "^[a-zA-Z0-9]+$"
|
||||||
|
nameReg, err := regexp.Compile(pattern)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
return nameReg.MatchString(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanString(buf []byte) string {
|
||||||
|
s := string(buf)
|
||||||
|
idx := strings.Index(s, "\n")
|
||||||
|
if idx != -1 {
|
||||||
|
s = s[:idx]
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func newClient(c net.Conn) (*client, error) {
|
||||||
|
cl := &client{
|
||||||
|
c: c,
|
||||||
|
name: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
cl.sendMessageString("Welcome to budgetchat! What shall I call you?")
|
||||||
|
|
||||||
|
r := make(chan string, 1)
|
||||||
|
var err error = nil
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
buf := make([]byte, 32)
|
||||||
|
_, err = c.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
c.Close()
|
||||||
|
r <- ""
|
||||||
|
return
|
||||||
|
}
|
||||||
|
name := cleanString(buf)
|
||||||
|
|
||||||
|
// Check legality
|
||||||
|
if !isValidName(name) {
|
||||||
|
cl.sendMessageString("The name %s is invalid", string(name))
|
||||||
|
err = errors.New("name invalid")
|
||||||
|
c.Close()
|
||||||
|
r <- name
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for duplicate name
|
||||||
|
for _, cls := range room {
|
||||||
|
if cls.name == name {
|
||||||
|
cl.sendMessageString("The name %s is already taken", string(name))
|
||||||
|
err = errors.New("name already taken")
|
||||||
|
c.Close()
|
||||||
|
r <- name
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r <- name
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
cl.name = <-r
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) sendMessage(m []byte) {
|
||||||
|
_, err := cl.c.Write(m)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) sendMessageString(m string, vars ...any) {
|
||||||
|
cl.sendMessage([]byte(fmt.Sprintf(m, vars...) + "\n"))
|
||||||
|
}
|
|
@ -3,53 +3,83 @@ package main
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"protohackers/pkg/conn"
|
"protohackers/pkg/conn"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type message struct {
|
||||||
|
msg []byte
|
||||||
|
client *client
|
||||||
|
}
|
||||||
|
|
||||||
|
func msg(s string, c *client) message {
|
||||||
|
return message{[]byte(s), c}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
room = make([]*client, 0)
|
||||||
|
messages = make(chan message, 32)
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
l, err := net.Listen(conn.Type, conn.Port)
|
go broadcast()
|
||||||
|
err := conn.StartSimple(newUserHandler)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
defer l.Close()
|
}
|
||||||
|
|
||||||
fmt.Println(fmt.Sprintf("Listening on port %s", conn.Port))
|
func broadcast() {
|
||||||
|
|
||||||
r := createRoom()
|
|
||||||
for {
|
for {
|
||||||
c, err := l.Accept()
|
select {
|
||||||
if err != nil {
|
case m := <-messages:
|
||||||
|
for _, cl := range room {
|
||||||
|
if m.client == cl {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cl.sendMessage(m.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func userList() []byte {
|
||||||
|
m := "* The room contains: "
|
||||||
|
if len(room) > 0 {
|
||||||
|
for _, c := range room {
|
||||||
|
m += fmt.Sprintf("%s, ", c.name)
|
||||||
|
}
|
||||||
|
m = m[:len(m)-2]
|
||||||
|
}
|
||||||
|
return []byte(m + "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUserHandler(c net.Conn) {
|
||||||
|
cl, err := newClient(c)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cl.sendMessage(userList())
|
||||||
|
fmt.Printf("New client %s\n", cl.name)
|
||||||
|
messages <- msg(fmt.Sprintf("* %s has entered the room\n", cl.name), cl)
|
||||||
|
room = append(room, cl)
|
||||||
|
|
||||||
|
input := bufio.NewScanner(cl.c)
|
||||||
|
for input.Scan() {
|
||||||
|
m := fmt.Sprintf("[%s] %s\n", cl.name, input.Text())
|
||||||
|
messages <- msg(m, cl)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, p := range room {
|
||||||
|
if p == cl {
|
||||||
|
name := cl.name
|
||||||
|
cl.c.Close()
|
||||||
|
room = append(room[:i], room[i+1:]...)
|
||||||
|
fmt.Printf("%s has left", cl.name)
|
||||||
|
messages <- msg(fmt.Sprintf("* %s has left the room\n", name), cl)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
go handleConn(r, c)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleConn(r *room, c net.Conn) {
|
|
||||||
u := newUser(c)
|
|
||||||
defer c.Close()
|
|
||||||
go u.sendMessage()
|
|
||||||
|
|
||||||
u.write("Welcome to budgetchat! What shall I call you?")
|
|
||||||
|
|
||||||
s := bufio.NewScanner(u.conn)
|
|
||||||
for s.Scan() {
|
|
||||||
input := s.Text()
|
|
||||||
if !u.online {
|
|
||||||
r.sendToRest(r.joinMsg(u), u)
|
|
||||||
u.write(r.connectMsg())
|
|
||||||
r.joinUser(u)
|
|
||||||
} else {
|
|
||||||
r.sendToRest(msgWrap(input, u), u)
|
|
||||||
}
|
|
||||||
if s.Err() != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func msgWrap(msg string, u *user) string {
|
|
||||||
return fmt.Sprintf("[%s] %s", u.nick, msg)
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type room struct {
|
|
||||||
users []*user
|
|
||||||
}
|
|
||||||
|
|
||||||
func createRoom() *room {
|
|
||||||
return &room{
|
|
||||||
users: make([]*user, 16),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *room) joinUser(u *user) {
|
|
||||||
for _, usr := range r.users {
|
|
||||||
if usr.nick == u.nick {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r.users = append(r.users, u)
|
|
||||||
u.online = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *room) sendToAll(msg string) {
|
|
||||||
for _, u := range r.users {
|
|
||||||
if u.online {
|
|
||||||
u.write(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *room) sendToRest(msg string, skip *user) {
|
|
||||||
for _, u := range r.users {
|
|
||||||
if u.online && u != skip {
|
|
||||||
u.write(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *room) connected() (nicks []string) {
|
|
||||||
for _, u := range r.users {
|
|
||||||
if u.online {
|
|
||||||
nicks = append(nicks, u.nick)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nicks
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *room) joinMsg(u *user) string {
|
|
||||||
return fmt.Sprintf("* %s has joined the channel", u.nick)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *room) connectMsg() string {
|
|
||||||
return fmt.Sprintf("* The room contains: %s", strings.Join(r.connected(), ", "))
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import "net"
|
|
||||||
|
|
||||||
type user struct {
|
|
||||||
conn net.Conn
|
|
||||||
nick string
|
|
||||||
online bool
|
|
||||||
output chan string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newUser(conn net.Conn) *user {
|
|
||||||
return &user{
|
|
||||||
conn: conn,
|
|
||||||
output: make(chan string),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *user) write(s string) {
|
|
||||||
u.output <- s + "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *user) sendMessage() {
|
|
||||||
for line := range u.output {
|
|
||||||
u.conn.Write([]byte(line))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c=
|
||||||
|
go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk=
|
Loading…
Reference in New Issue