mirror of
https://github.com/Kioubit/ColorPing
synced 2024-12-04 21:50:41 +08:00
Remove unnecessary clientID, fix imageSmoothing parameter reset
This commit is contained in:
parent
cc9be27ab6
commit
c2be44274b
3 changed files with 89 additions and 98 deletions
146
http.go
146
http.go
|
@ -2,13 +2,13 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"log"
|
|
||||||
"math"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,8 +19,8 @@ var htmlTemplate *template.Template
|
||||||
type clientState int
|
type clientState int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
INITIAL = 0
|
INITIAL = iota
|
||||||
ACTIVE = iota
|
ACTIVE
|
||||||
)
|
)
|
||||||
|
|
||||||
type client struct {
|
type client struct {
|
||||||
|
@ -29,43 +29,25 @@ type client struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
clientCounter uint32 = 0
|
clients = make([]*client, 0)
|
||||||
clientCounterMutex sync.Mutex
|
clientMutex sync.Mutex
|
||||||
|
|
||||||
clients = make(map[uint32]*client)
|
|
||||||
clientMutex sync.RWMutex
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func getClientID() uint32 {
|
func deleteClient(client *client) {
|
||||||
clientCounterMutex.Lock()
|
for i := 0; i < len(clients); i++ {
|
||||||
defer clientCounterMutex.Unlock()
|
if clients[i] == client {
|
||||||
clientCounter++
|
clients[i] = clients[len(clients)-1]
|
||||||
if clientCounter == math.MaxUint32 {
|
clients = clients[:len(clients)-1]
|
||||||
clientCounter = 0
|
return
|
||||||
clearClients()
|
}
|
||||||
}
|
}
|
||||||
return clientCounter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func clearClients() {
|
func httpServer() error {
|
||||||
clientMutex.Lock()
|
|
||||||
defer clientMutex.Unlock()
|
|
||||||
for _, c := range clients {
|
|
||||||
close(c.channel)
|
|
||||||
}
|
|
||||||
clients = make(map[uint32]*client)
|
|
||||||
}
|
|
||||||
|
|
||||||
func httpServer() {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
htmlTemplate = template.Must(template.ParseFS(embedFS, "template.html"))
|
htmlTemplate = template.Must(template.ParseFS(embedFS, "template.html"))
|
||||||
http.HandleFunc("/stream", stream)
|
http.HandleFunc("/stream", stream)
|
||||||
http.HandleFunc("/", serveRoot)
|
http.HandleFunc("/", serveRoot)
|
||||||
err = http.ListenAndServe(":9090", nil)
|
return http.ListenAndServe(":9090", nil)
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getInterfaceBaseIP() string {
|
func getInterfaceBaseIP() string {
|
||||||
|
@ -107,8 +89,7 @@ func getInterfaceBaseIP() string {
|
||||||
|
|
||||||
func serveRoot(w http.ResponseWriter, r *http.Request) {
|
func serveRoot(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.RequestURI != "/" {
|
if r.RequestURI != "/" {
|
||||||
w.WriteHeader(404)
|
http.Error(w, "Not found", http.StatusNotFound)
|
||||||
_, _ = w.Write([]byte("404 not found"))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
type pageData struct {
|
type pageData struct {
|
||||||
|
@ -126,64 +107,65 @@ func serveRoot(w http.ResponseWriter, r *http.Request) {
|
||||||
CanvasWidth: 512,
|
CanvasWidth: 512,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
fmt.Println("Error executing HTML template:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var streamServerRunning atomic.Bool
|
||||||
|
|
||||||
func streamServer() {
|
func streamServer() {
|
||||||
for {
|
if !streamServerRunning.CompareAndSwap(false, true) {
|
||||||
clientMutex.RLock()
|
return
|
||||||
if len(clients) == 0 {
|
}
|
||||||
for {
|
go func() {
|
||||||
if len(clients) == 0 {
|
for {
|
||||||
clientMutex.RUnlock()
|
clientMutex.Lock()
|
||||||
time.Sleep(1 * time.Second)
|
if len(clients) == 0 {
|
||||||
clientMutex.RLock()
|
streamServerRunning.Store(false)
|
||||||
|
clientMutex.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
requiresInitial := false
|
||||||
|
requiresUpdate := false
|
||||||
|
for _, v := range clients {
|
||||||
|
if v.state == INITIAL {
|
||||||
|
requiresInitial = true
|
||||||
} else {
|
} else {
|
||||||
|
requiresUpdate = true
|
||||||
|
}
|
||||||
|
if requiresInitial && requiresUpdate {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
requiresInitial := false
|
dataInitial, dataUpdate := getPicture(requiresInitial, requiresUpdate)
|
||||||
requiresUpdate := false
|
tmp := clients[:0]
|
||||||
for _, v := range clients {
|
for _, v := range clients {
|
||||||
if v.state == INITIAL {
|
if v.state == INITIAL {
|
||||||
requiresInitial = true
|
v.state = ACTIVE
|
||||||
} else {
|
|
||||||
requiresUpdate = true
|
|
||||||
}
|
|
||||||
if requiresInitial && requiresUpdate {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dataInitial, dataUpdate := getPicture(requiresInitial, requiresUpdate)
|
|
||||||
|
|
||||||
for clientID, v := range clients {
|
|
||||||
if v.state == INITIAL {
|
|
||||||
v.state = ACTIVE
|
|
||||||
select {
|
|
||||||
case v.channel <- dataInitial:
|
|
||||||
default:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if dataUpdate != "0" {
|
|
||||||
select {
|
select {
|
||||||
case v.channel <- dataUpdate:
|
case v.channel <- dataInitial:
|
||||||
default:
|
default:
|
||||||
// Client cannot keep up
|
}
|
||||||
close(v.channel)
|
} else {
|
||||||
delete(clients, clientID)
|
if dataUpdate != "0" {
|
||||||
continue
|
select {
|
||||||
|
case v.channel <- dataUpdate:
|
||||||
|
default:
|
||||||
|
// Client cannot keep up
|
||||||
|
close(v.channel)
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tmp = append(tmp, v)
|
||||||
}
|
}
|
||||||
|
clients = tmp
|
||||||
|
clientMutex.Unlock()
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
}
|
}
|
||||||
clientMutex.RUnlock()
|
}()
|
||||||
time.Sleep(500 * time.Millisecond)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func stream(w http.ResponseWriter, r *http.Request) {
|
func stream(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -192,17 +174,17 @@ func stream(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
|
http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
streamServer()
|
||||||
w.Header().Set("Content-Type", "text/event-stream")
|
w.Header().Set("Content-Type", "text/event-stream")
|
||||||
w.Header().Set("Cache-Control", "no-cache")
|
w.Header().Set("Cache-Control", "no-cache")
|
||||||
w.Header().Set("Connection", "keep-alive")
|
w.Header().Set("Connection", "keep-alive")
|
||||||
messageChan := make(chan string, 40)
|
messageChan := make(chan string, 40)
|
||||||
id := getClientID()
|
newClient := &client{
|
||||||
newClient := client{
|
|
||||||
channel: messageChan,
|
channel: messageChan,
|
||||||
state: INITIAL,
|
state: INITIAL,
|
||||||
}
|
}
|
||||||
clientMutex.Lock()
|
clientMutex.Lock()
|
||||||
clients[id] = &newClient
|
clients = append(clients, newClient)
|
||||||
clientMutex.Unlock()
|
clientMutex.Unlock()
|
||||||
|
|
||||||
// For when clients are removed prior to connection closed, to avoid a call to delete(clients, id)
|
// For when clients are removed prior to connection closed, to avoid a call to delete(clients, id)
|
||||||
|
@ -212,7 +194,7 @@ func stream(w http.ResponseWriter, r *http.Request) {
|
||||||
<-r.Context().Done()
|
<-r.Context().Done()
|
||||||
clientMutex.Lock()
|
clientMutex.Lock()
|
||||||
if !channelClosedFirst {
|
if !channelClosedFirst {
|
||||||
delete(clients, id)
|
deleteClient(newClient)
|
||||||
}
|
}
|
||||||
close(messageChan)
|
close(messageChan)
|
||||||
clientMutex.Unlock()
|
clientMutex.Unlock()
|
||||||
|
|
30
main.go
30
main.go
|
@ -9,24 +9,32 @@ import (
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"image/png"
|
"image/png"
|
||||||
"log"
|
"os"
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
const interfaceName = "canvas"
|
const interfaceName = "canvas"
|
||||||
const handlerCount = 4
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
prePopulatePixelArray()
|
prePopulatePixelArray()
|
||||||
packetChan := make(chan *[]byte, 1000)
|
packetChan := make(chan *[]byte, 1000)
|
||||||
for i := 0; i < handlerCount; i++ {
|
for i := 0; i < runtime.NumCPU(); i++ {
|
||||||
go packetHandler(packetChan)
|
go packetHandler(packetChan)
|
||||||
}
|
}
|
||||||
go startInterface(packetChan)
|
go func() {
|
||||||
go streamServer()
|
err := startInterface(packetChan)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Interface handler error:", err)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}()
|
||||||
fmt.Println("Kioubit ColorPing started")
|
fmt.Println("Kioubit ColorPing started")
|
||||||
fmt.Println("Interface name:", interfaceName, "HTTP server port: 9090")
|
fmt.Println("Interface name:", interfaceName, "HTTP server port: 9090")
|
||||||
httpServer()
|
if err := httpServer(); err != nil {
|
||||||
|
fmt.Println("Error starting HTTP server:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func prePopulatePixelArray() {
|
func prePopulatePixelArray() {
|
||||||
|
@ -45,21 +53,21 @@ var pktPool = sync.Pool{
|
||||||
New: func() interface{} { return make([]byte, 2000) },
|
New: func() interface{} { return make([]byte, 2000) },
|
||||||
}
|
}
|
||||||
|
|
||||||
func startInterface(packetChan chan *[]byte) {
|
func startInterface(packetChan chan *[]byte) error {
|
||||||
config := water.Config{
|
config := water.Config{
|
||||||
DeviceType: water.TUN,
|
DeviceType: water.TUN,
|
||||||
}
|
}
|
||||||
config.Name = interfaceName
|
config.Name = interfaceName
|
||||||
iFace, err := water.New(config)
|
iFace, err := water.New(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
packet := pktPool.Get().([]byte)
|
packet := pktPool.Get().([]byte)
|
||||||
n, err := iFace.Read(packet)
|
n, err := iFace.Read(packet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
packet = packet[:n]
|
packet = packet[:n]
|
||||||
packetChan <- &packet
|
packetChan <- &packet
|
||||||
|
@ -167,7 +175,7 @@ func getPicture(fullUpdate bool, incrementalUpdate bool) (string, string) {
|
||||||
buff := new(bytes.Buffer)
|
buff := new(bytes.Buffer)
|
||||||
err := encoder.Encode(buff, canvasIncrementalUpdate)
|
err := encoder.Encode(buff, canvasIncrementalUpdate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err.Error())
|
fmt.Println("PNG encoding error:", err)
|
||||||
}
|
}
|
||||||
incrementalUpdateResult = "event: u\ndata:" + base64.StdEncoding.EncodeToString(buff.Bytes()) + "\n\n"
|
incrementalUpdateResult = "event: u\ndata:" + base64.StdEncoding.EncodeToString(buff.Bytes()) + "\n\n"
|
||||||
}
|
}
|
||||||
|
@ -177,7 +185,7 @@ func getPicture(fullUpdate bool, incrementalUpdate bool) (string, string) {
|
||||||
buff := new(bytes.Buffer)
|
buff := new(bytes.Buffer)
|
||||||
err := encoder.Encode(buff, canvasFullUpdate)
|
err := encoder.Encode(buff, canvasFullUpdate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err.Error())
|
fmt.Println("PNG encoding error:", err)
|
||||||
}
|
}
|
||||||
fullUpdateResult = "event: u\ndata:" + base64.StdEncoding.EncodeToString(buff.Bytes()) + "\n\n"
|
fullUpdateResult = "event: u\ndata:" + base64.StdEncoding.EncodeToString(buff.Bytes()) + "\n\n"
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,13 +103,9 @@
|
||||||
</body>
|
</body>
|
||||||
<script>
|
<script>
|
||||||
const canvas = document.getElementById("display");
|
const canvas = document.getElementById("display");
|
||||||
const ctx = canvas.getContext("2d")
|
|
||||||
ctx.imageSmoothingEnabled = false;
|
|
||||||
ctx.mozImageSmoothingEnabled = false;
|
|
||||||
ctx.webkitImageSmoothingEnabled = false;
|
|
||||||
|
|
||||||
canvas.width = 1024;
|
canvas.width = 1024;
|
||||||
canvas.height = 1024;
|
canvas.height = 1024;
|
||||||
|
const ctx = canvas.getContext("2d")
|
||||||
|
|
||||||
async function resizeCanvas() {
|
async function resizeCanvas() {
|
||||||
let desiredSize = document.documentElement.clientWidth - 20;
|
let desiredSize = document.documentElement.clientWidth - 20;
|
||||||
|
@ -118,6 +114,11 @@
|
||||||
}
|
}
|
||||||
canvas.style.width = desiredSize.toString() + "px";
|
canvas.style.width = desiredSize.toString() + "px";
|
||||||
canvas.style.height = desiredSize.toString() + "px";
|
canvas.style.height = desiredSize.toString() + "px";
|
||||||
|
|
||||||
|
// These properties get reset on resize
|
||||||
|
ctx.imageSmoothingEnabled = false;
|
||||||
|
ctx.mozImageSmoothingEnabled = false;
|
||||||
|
ctx.webkitImageSmoothingEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.onresize = resizeCanvas;
|
window.onresize = resizeCanvas;
|
||||||
|
|
Loading…
Reference in a new issue