Skip to content

Instantly share code, notes, and snippets.

@tsnow
Last active August 29, 2015 14:00
Show Gist options
  • Save tsnow/11385848 to your computer and use it in GitHub Desktop.
Save tsnow/11385848 to your computer and use it in GitHub Desktop.
Go chat server

Installation

go run server.go

Usage

telnet localhost 9000 #A
# separate terminal
telnet localhost 9000 #B

Then chat between the users:

#A
SexyT
#B
Josh
Yo
#A
hi

Which should result in the following output:

#A should see:
The room is empty.
Username:
You've logged on as SexyT.
Josh has joined.
Josh: Yo
SexyT: hi

#B should see:
Username:
Participants: SexyT
You've logged on as Josh.
Josh: Yo
SexyT: hi
package main
import (
"fmt"
"net"
"strings"
"sync"
)
type message struct {
user []byte
body []byte
}
type client struct {
user []byte
client net.Conn
close chan struct{}
}
type room struct {
m chan message
clients []*client
clientsMutex sync.Mutex // RWMutex might make more sense. Maybe.
}
func (c *client) sendMsg(b []byte) {
fmt.Fprintf(c.client, "%s %s\n", c.user, b)
}
func (c *client) hear(msg message) {
fmt.Fprintf(c.client, "%s: %s", msg.user, msg.body)
}
func (r *room) handleConnection(conn net.Conn) {
reqLen, err := fmt.Fprintf(conn, "Username: ")
if err != nil {
fmt.Println(err)
}
buf := make([]byte, 1024)
reqLen, err = conn.Read(buf)
user := client{
user: ([]byte)(strings.TrimSpace(string(buf[0:reqLen]))),
client: conn,
}
r.addClient(user)
fmt.Fprintf(conn, "You've logged on as %s\n", user.user)
for {
reqLen, err := conn.Read(buf)
if err != nil {
fmt.Println(err)
}
if strings.TrimSpace(string(buf[0:reqLen])) == "/quit" {
conn.Close()
return // can't write to this connection any more
}
m := message{
user: user.user,
body: buf[0:(reqLen - 1)],
}
r.m <- m
}
}
func (r *room) addClient(c client) {
//r.clientsMutex.Lock()
//defer r.clientsMutex.Unlock()
for i := range r.clients {
fmt.Fprintf(r.clients[i].client, "%s has joined \n", c.user)
}
fmt.Fprintf(c.client, "Participants: %#v.\n", r.clients)
r.clients = append(r.clients, &c)
}
func (r *room) broadcastMsg(m message) {
for i := range r.clients {
r.clients[i].hear(m)
}
}
func (r *room) broadcast() {
for {
select {
case i := <-r.m:
r.broadcastMsg(i)
}
}
}
func (r *room) listener() {
ln, err := net.Listen("tcp", ":9000")
if err != nil {
fmt.Println(err)
}
go r.broadcast()
for {
conn, err := ln.Accept()
if err != nil {
fmt.Println(err)
continue
}
go r.handleConnection(conn)
}
}
func main() {
oneRoomToRuleThemAll := room{
m: make(chan message),
}
oneRoomToRuleThemAll.listener()
}
@tsnow
Copy link
Author

tsnow commented May 5, 2014

Without this https://gist.github.com/tsnow/11385848#file-server-go-L52-L55 return I get the following panic:

bash-3.2$ go run server.go
use of closed network connection
panic: runtime error: slice bounds out of range

goroutine 5 [running]:
main.(*room).handleConnection(0xc200082040, 0xc200087060, 0xc2000000b8)
    /Users/tsnow/go/src/chat/server.go:59 +0x582
created by main.(*room).listener
    /Users/tsnow/go/src/chat/server.go:101 +0x1f6

goroutine 1 [IO wait]:
net.runtime_pollWait(0x28bf00, 0x72, 0x0)
    /usr/local/go/src/pkg/runtime/znetpoll_darwin_amd64.c:118 +0x82
net.(*pollDesc).WaitRead(0xc200083080, 0x23, 0xc20007b2d0)
    /usr/local/go/src/pkg/net/fd_poll_runtime.go:75 +0x31
net.(*netFD).accept(0xc200083000, 0x12ecd0, 0x0, 0xc20007b2d0, 0x23, ...)
    /usr/local/go/src/pkg/net/fd_unix.go:385 +0x2c1
net.(*TCPListener).AcceptTCP(0xc200000090, 0x2f56, 0x27ae90, 0x2f56)
    /usr/local/go/src/pkg/net/tcpsock_posix.go:229 +0x45
net.(*TCPListener).Accept(0xc200000090, 0xc200087060, 0xc2000000e8, 0x0, 0x0, ...)
    /usr/local/go/src/pkg/net/tcpsock_posix.go:239 +0x25
main.(*room).listener(0xc200082040)
    /Users/tsnow/go/src/chat/server.go:96 +0x12f
main.main()
    /Users/tsnow/go/src/chat/server.go:110 +0x8b

goroutine 2 [syscall]:

goroutine 4 [runnable]:
main.(*room).broadcast(0xc200082040)
    /Users/tsnow/go/src/chat/server.go:83 +0x4b
created by main.(*room).listener
    /Users/tsnow/go/src/chat/server.go:94 +0x119

goroutine 6 [IO wait]:
net.runtime_pollWait(0x28bdc0, 0x72, 0x0)
    /usr/local/go/src/pkg/runtime/znetpoll_darwin_amd64.c:118 +0x82
net.(*pollDesc).WaitRead(0xc2000831a0, 0x23, 0xc20007b2d0)
    /usr/local/go/src/pkg/net/fd_poll_runtime.go:75 +0x31
net.(*netFD).Read(0xc200083120, 0xc200052800, 0x400, 0x400, 0x0, ...)
    /usr/local/go/src/pkg/net/fd_unix.go:195 +0x2b3
net.(*conn).Read(0xc2000000e8, 0xc200052800, 0x400, 0x400, 0x29bea8, ...)
    /usr/local/go/src/pkg/net/net.go:123 +0xc3
main.(*room).handleConnection(0xc200082040, 0xc200087060, 0xc2000000e8)
    /Users/tsnow/go/src/chat/server.go:48 +0x3c3
created by main.(*room).listener
    /Users/tsnow/go/src/chat/server.go:101 +0x1f6
exit status 2

@tsnow
Copy link
Author

tsnow commented May 5, 2014

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment