Last active
December 27, 2015 20:09
-
-
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
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
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