Skip to content

Instantly share code, notes, and snippets.

@nabeken
Last active December 27, 2015 20:09
Show Gist options
  • Save nabeken/7382820 to your computer and use it in GitHub Desktop.
Save nabeken/7382820 to your computer and use it in GitHub Desktop.
http://talks.golang.org/2012/chat.slide#14 Some improvement (or not) to detect a connection timeout websocket + tcp6 version
package main
import (
"fmt"
"html/template"
"io"
"log"
"net"
"net/http"
"time"
"code.google.com/p/go.net/websocket"
)
type socket struct {
io.ReadWriteCloser
quit chan bool
done chan bool
}
/*
func (s socket) Close() error {
s.done <- true
return nil
}
*/
type Room struct {
Number int
Close chan bool
Join chan *socket
First *socket
Last *socket
}
type Rooms struct {
N int
R []*Room
}
var rooms = &Rooms{0, make([]*Room, 100)}
var rootTemplate = template.Must(template.New("root").Parse(`
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script>
websocket = new WebSocket("ws://{{.}}/socket");
websocket.onmessage = function (e) {
if (e.data.trim().length > 0) {
$("<li>length: " + e.data.length + e.data + "</li>")
.insertAfter("ul:first");
}
};
websocket.onclose = function (e) {
$("<li>closed!</li>")
.insertAfter("ul:first");
};
$(document).ready(function () {
$("form").submit(function (e) {
e.preventDefault();
websocket.send($("input:text").val());
websocket.onmessage({"data": $("input:text").val()});
$("input:text").val("");
});
});
</script>
</head>
<body>
<form>
<input type="text" size="10" />
<input type="submit" value="Send" />
</form>
<ul>
</ul>
</html>
`))
const listenAddr = "localhost:4001"
const wsListenAddr = "localhost:4000"
func cp(dst io.Writer, src io.Reader, errc chan<- error) {
n, err := io.Copy(dst, src)
log.Println(n, err)
errc <- err
}
func chat(room *Room) {
log.Println("Chatting")
a := room.First
b := room.Last
fmt.Fprintln(a, "Found one! Sa hi.")
fmt.Fprintln(b, "Found one! Sa hi.")
errc := make(chan error)
go cp(a, b, errc)
go cp(b, a, errc)
if err := <-errc; err != nil {
log.Println(err)
}
a.Close()
b.Close()
}
func match(room *Room, s *socket) {
fmt.Fprintf(s, "Waiting for a partner in the room#%d....\n", room.Number)
time.Sleep(3 * time.Second)
log.Printf("Selecting in the room#%d...\n", room.Number)
fmt.Fprint(s, "Selecting....\n")
select {
case room.Join <- s:
// handled by the other goroutine
case first := <-room.Join:
log.Println("Join")
room.First = first
room.Last = s
chat(room)
case <-room.Close:
fmt.Fprintf(s, "Room#%d has been closed. Please reload to enter a new room\n", room.Number)
log.Printf("Room#%d about to Close() %s\n", room.Number, s)
s.Close()
log.Printf("Room#%d Close() %s\n", room.Number, s)
s.done <- true
log.Printf("Room#%d <-done %s\n", room.Number, s)
room.Close <- true
log.Printf("Room#%d <-closed %s\n", room.Number, s)
}
}
func book() *Room {
log.Printf("room#%d, %s\n", rooms.N, rooms.R[rooms.N])
room := rooms.R[rooms.N]
// no room or room is full
if room == nil || (room.First != nil && room.Last != nil) {
log.Println("Open a new room")
rooms.R[rooms.N] = &Room{rooms.N, make(chan bool, 1), make(chan *socket), nil, nil}
return rooms.R[rooms.N]
}
log.Println("Return a existing room")
rooms.N++
return room
}
func keepalive(s *socket) {
buf := make([]byte, 512)
for {
log.Println("Keepaliving %s....", s)
if _, err := s.Write(buf); err != nil {
log.Println("Detect a connection closed")
s.quit <- true
break
}
time.Sleep(500 * time.Millisecond)
}
}
// watching First, Last
func cleanup(room *Room, node *socket) {
log.Printf("Cleanup starting for room#%d...", room.Number)
<-node.quit
log.Printf("Node quitting received in the room#%d", room.Number)
log.Printf("Resetting room#%d...", room.Number)
room.Close <- true
log.Printf("Notify to channel with 6 for room#%d", room.Number)
}
func launch(room *Room, node *socket) {
go match(room, node)
go keepalive(node)
go cleanup(room, node)
<-node.done
}
func wsSocketHandler(ws *websocket.Conn) {
node := &socket{
ws,
make(chan bool),
make(chan bool),
}
room := book()
launch(room, node)
log.Println("wssocket handler...")
}
func tcpSocketHandler(conn net.Conn) {
node := &socket{
conn,
make(chan bool),
make(chan bool),
}
room := book()
launch(room, node)
log.Println("tcp socket handler...")
}
func rootHandler(w http.ResponseWriter, r *http.Request) {
rootTemplate.Execute(w, wsListenAddr)
}
func netListen() {
l, err := net.Listen("tcp6", listenAddr)
if err != nil {
log.Fatal(err)
}
for {
c, err := l.Accept()
if err != nil {
log.Fatal(err)
}
go tcpSocketHandler(c)
}
}
func main() {
go netListen()
http.HandleFunc("/", rootHandler)
http.Handle("/socket", websocket.Handler(wsSocketHandler))
err := http.ListenAndServe(wsListenAddr, nil)
if err != nil {
log.Fatal(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment