dataRT/data/cl104/cl104.go

208 lines
3.9 KiB
Go

package cl104
import (
"context"
"datart/config"
"datart/log"
"sync"
"time"
"github.com/gorilla/websocket"
)
type info struct {
IOA int `json:"ioa"`
Val float64 `json:"val"`
Q int `json:"q"`
MS int64 `json:"ms"`
}
type Msg struct {
TI int `json:"ti"`
COT int `json:"cot"`
PN int `json:"pn"`
CA int `json:"ca"`
Infos []*info `json:"infos"`
}
const UpConnLimit int64 = 10
type StatusCh struct {
Status byte
UpChan chan []byte
}
var broadcastCh = make(chan []byte, 1024)
var UpChans []*StatusCh = make([]*StatusCh, UpConnLimit)
var UpChansMu sync.RWMutex
var cl2Chan = map[string]chan []byte{}
var CA2Chan = map[int]chan []byte{}
func init() {
conf := config.Conf().CL104ConfAll()
for _, addr2CAs := range conf {
for addr, cas := range addr2CAs {
clChan := make(chan []byte, 16)
cl2Chan[addr] = clChan
for _, ca := range cas {
CA2Chan[ca] = clChan
}
}
}
for i := 0; i < len(UpChans); i++ {
UpChans[i] = &StatusCh{
Status: 0,
UpChan: make(chan []byte, 16),
}
}
go func() {
for data := range broadcastCh {
d := append([]byte(nil), data...)
UpChansMu.RLock()
for i := 0; i < len(UpChans); i++ {
if UpChans[i].Status == 1 {
select {
case UpChans[i].UpChan <- d:
default:
}
}
}
UpChansMu.RUnlock()
}
}()
}
func ConnectCLs(ctx context.Context) {
for cl, ch := range cl2Chan {
go func(ctx context.Context, cl string, ch chan []byte) {
connectingCL(ctx, cl, ch)
}(ctx, cl, ch)
}
}
func connectingCL(ctx context.Context, cl string, ch chan []byte) {
for {
time.Sleep(time.Second * 5)
subctx, cancel := context.WithCancel(ctx)
newConnectCL(subctx, cancel, cl, ch)
}
}
func newConnectCL(ctx context.Context, cancel context.CancelFunc, cl string, ch chan []byte) {
c, _, err := websocket.DefaultDialer.DialContext(ctx, "ws://"+cl+"/api/104", nil)
if err != nil {
log.Error("client dial:", err)
return
}
defer c.Close()
go monitoringCLRead(ctx, cancel, c)
go monitoringCLWrite(ctx, cancel, c, ch)
<-ctx.Done()
}
func monitoringCLRead(ctx context.Context, cancel context.CancelFunc, c *websocket.Conn) {
for {
select {
case <-ctx.Done():
return
default:
mt, rm, err := c.ReadMessage()
if err != nil {
if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
cancel()
return
}
if ce, ok := err.(*websocket.CloseError); ok {
log.Info("client closed with code:", ce.Code, "text:", ce.Text)
cancel()
return
}
log.Error("server read:", err)
cancel()
return
}
switch mt {
case websocket.TextMessage:
select {
case broadcastCh <- rm:
case <-time.After(time.Second * 5):
log.Error("drop cl msg:", string(rm))
}
default:
log.Error("invalid msg type:", mt)
}
}
}
}
func monitoringCLWrite(ctx context.Context, cancel context.CancelFunc, c *websocket.Conn, ch chan []byte) {
var err error
for {
select {
case <-ctx.Done():
return
case <-time.After(time.Second * 54):
err = c.WriteControl(websocket.PingMessage, nil, time.Now().Add(time.Second*10))
if err != nil {
cancel()
return
}
case msg := <-ch:
err = c.WriteMessage(websocket.TextMessage, msg)
if err != nil {
cancel()
return
}
}
}
}
func AcquireUpSlot() (int, bool) {
UpChansMu.Lock()
defer UpChansMu.Unlock()
for i := 0; i < len(UpChans); i++ {
if UpChans[i].Status == 0 {
UpChans[i].Status = 1
return i, true
}
}
return -1, false
}
func ReleaseUpSlot(idx int) {
if idx < 0 || idx >= len(UpChans) {
return
}
UpChansMu.Lock()
old := UpChans[idx].UpChan
UpChans[idx].UpChan = make(chan []byte, 16)
UpChans[idx].Status = 0
UpChansMu.Unlock()
cleanupDone := make(chan struct{})
go func() {
defer close(cleanupDone)
for {
select {
case <-old:
continue
default:
return
}
}
}()
select {
case <-cleanupDone:
case <-time.After(100 * time.Millisecond):
}
}