musique/server/src/main.go

189 lines
4.0 KiB
Go
Raw Normal View History

package main
import (
"bufio"
2022-11-29 21:15:39 +01:00
"fmt"
"log"
"net"
"os"
2022-12-09 15:04:41 +01:00
"sync"
2022-12-05 21:50:30 +01:00
"time"
2022-12-09 15:04:41 +01:00
"strings"
)
2022-11-29 21:15:39 +01:00
func scanError(scanResult []string, conn net.Conn) {
if scanResult == nil {
conn.Write([]byte("Empty scan result, run 'scan' first.\n"))
}
}
2022-12-09 15:04:41 +01:00
type timeExchange struct {
before, after, remote int64
}
type client struct {
timeExchange
id int
addr string
}
func remotef(host, command, format string, args ...interface{}) (int, error) {
connection, err := net.Dial("tcp", host)
if err != nil {
return 0, fmt.Errorf("remotef: establishing connection: %v", err)
}
defer connection.Close()
connection.SetDeadline(time.Now().Add(1 * time.Second))
fmt.Fprintln(connection, command)
if len(format) > 0 {
parsedCount, err := fmt.Fscanf(connection, format, args...)
if err != nil {
return 0, fmt.Errorf("remotef: parsing: %v", err)
}
return parsedCount, nil
}
return 0, nil
}
func (e *timeExchange) estimateFor(host string) bool {
e.before = time.Now().UnixMilli()
parsedCount, err := remotef(host, "time", "%d\n", &e.remote)
e.after = time.Now().UnixMilli()
if err != nil {
log.Println("estimateFor: %v", err)
return false
}
if parsedCount != 1 {
log.Printf("berkeley: expected to parse number, instead parsed %d items\n", parsedCount)
return false
}
return true
}
func timesync(hosts []string) []client {
wg := sync.WaitGroup{}
wg.Add(len(hosts))
// Gather time from each host
responses := make(chan client, len(hosts))
for id, host := range hosts {
id, host := id, host
go func() {
defer wg.Done()
exchange := timeExchange{};
if exchange.estimateFor(host) {
responses <- client{exchange, id, host}
}
}()
}
wg.Wait()
close(responses)
clients := make([]client, 0, len(hosts))
for client := range responses {
clients = append(clients, client)
}
return clients
}
2022-12-09 15:04:41 +01:00
const maxReactionTime = 300
2022-12-09 15:04:41 +01:00
func notifyAll(clients []client) {
wg := sync.WaitGroup{}
wg.Add(len(clients))
startDeadline := time.After(maxReactionTime * time.Millisecond)
for _, client := range clients {
client := client
go func() {
myTime := time.Now().UnixMilli()
startTime := myTime + maxReactionTime - (client.remote - client.before) + (client.after - client.before) / 2
_, err := remotef(client.addr, fmt.Sprintf("start %d", startTime), "")
if err != nil {
log.Printf("failed to notify %s: %v\n", client.addr, err)
}
wg.Done()
}()
}
<-startDeadline
return
}
2022-12-09 15:04:41 +01:00
func main() {
l, err := net.Listen("tcp", ":8081")
if err != nil {
log.Fatal(err)
}
defer l.Close()
for {
conn, err := l.Accept()
if err != nil {
log.Fatal(err)
}
go func(c net.Conn) {
s := bufio.NewScanner(c)
2022-11-29 21:15:39 +01:00
var scanResult []string
2022-12-09 15:04:41 +01:00
var clients []client
for s.Scan() {
resp := s.Text()
if resp == "scan" {
conn.Write([]byte("Scanning...\n"))
2022-11-29 21:15:39 +01:00
scanResult = scan()
conn.Write([]byte("Scanning done!\n"))
fmt.Println(len(scanResult))
continue
}
if resp == "time" {
2022-12-09 15:04:41 +01:00
fmt.Fprintln(conn, time.Now().UnixMilli())
2022-11-29 21:15:39 +01:00
continue
}
if resp == "hosts" {
scanError(scanResult, conn)
for _, host := range scanResult {
conn.Write([]byte(host + "\n"))
2022-12-05 21:50:30 +01:00
fmt.Println("CONNECTED")
2022-11-29 21:15:39 +01:00
}
continue
}
2022-12-05 21:50:30 +01:00
if resp == "showtime" {
cTime := showTime()
conn.Write([]byte(cTime.String() + "\n"))
2022-12-09 15:04:41 +01:00
continue
2022-12-05 21:50:30 +01:00
}
if resp == "timesync" {
2022-12-09 15:04:41 +01:00
clients = timesync(scanResult)
continue
}
if strings.HasPrefix(resp, "start") {
startTimeString := strings.TrimSpace(resp[len("start"):])
startTime := int64(0)
fmt.Scanf(startTimeString, "%d", &startTime)
currentTime := time.Now().UnixMilli()
if currentTime > startTime {
log.Println("cannot start after given time")
continue
2022-11-29 21:15:39 +01:00
}
2022-12-09 15:04:41 +01:00
time.Sleep(time.Duration(startTime - currentTime) * time.Millisecond)
log.Println("Started #start")
continue
}
if resp == "notify" {
notifyAll(clients)
log.Println("Started #notify")
continue
}
if resp == "quit" {
c.Close()
os.Exit(0)
}
}
c.Close()
}(conn)
}
}