init
This commit is contained in:
commit
64732cb9e9
11
client/client.code-workspace
Normal file
11
client/client.code-workspace
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../server"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
}
|
10
client/go.mod
Normal file
10
client/go.mod
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
module main
|
||||||
|
|
||||||
|
go 1.21.5
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
|
github.com/urfave/cli/v2 v2.27.1 // indirect
|
||||||
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||||
|
)
|
8
client/go.sum
Normal file
8
client/go.sum
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
|
||||||
|
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
||||||
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||||
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
272
client/main.go
Normal file
272
client/main.go
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
AddrServer string
|
||||||
|
}
|
||||||
|
|
||||||
|
const HEART_BEAT_INTERVAL = time.Second * 5 // 心跳超时时间
|
||||||
|
|
||||||
|
const (
|
||||||
|
MSG_TYPE_UNKOWNM = iota
|
||||||
|
MSG_TYPE_HEARTBEAT
|
||||||
|
MSG_TYPE_REGISTER
|
||||||
|
MSG_TYPE_SESSION_CREATE
|
||||||
|
MSG_TYPE_SESSION_DATA
|
||||||
|
MSG_TYPE_SESSION_DESTORY
|
||||||
|
MSG_TYPE_TUNNEL_CREATE
|
||||||
|
MSG_TYPE_MAX
|
||||||
|
)
|
||||||
|
|
||||||
|
type Device struct {
|
||||||
|
id string
|
||||||
|
category string
|
||||||
|
desc string /* description of the device */
|
||||||
|
conn net.Conn
|
||||||
|
create_time int64 /* connection time */
|
||||||
|
active time.Time
|
||||||
|
uptime uint32
|
||||||
|
token string
|
||||||
|
registered bool
|
||||||
|
closed uint32
|
||||||
|
send chan []byte // Buffered channel of outbound messages.
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := &cli.App{
|
||||||
|
Name: "XZRobot Ops Server",
|
||||||
|
Usage: "The Server Side For xzrobot ops",
|
||||||
|
Version: "1.0.0",
|
||||||
|
Commands: []*cli.Command{
|
||||||
|
{
|
||||||
|
Name: "run",
|
||||||
|
Usage: "Run Server",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "log",
|
||||||
|
Value: "log.txt",
|
||||||
|
Usage: "log file path",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "conf",
|
||||||
|
Aliases: []string{"c"},
|
||||||
|
Value: "./rttys.conf",
|
||||||
|
Usage: "config file to load",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "addr-dev",
|
||||||
|
Value: ":9011",
|
||||||
|
Usage: "address to listen device",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "addr-user",
|
||||||
|
Value: ":9012",
|
||||||
|
Usage: "address to listen user",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "db",
|
||||||
|
Value: "sqlite://database.db",
|
||||||
|
Usage: "database source",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
runClient(c)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
c.App.Command("run").Run(c)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := app.Run(os.Args)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runClient(c *cli.Context) {
|
||||||
|
|
||||||
|
cfg := &Config{
|
||||||
|
AddrServer: "localhost:9011",
|
||||||
|
}
|
||||||
|
|
||||||
|
tcpConn, err := createTcpConn(cfg.AddrServer)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("TCP Connect Error! " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(tcpConn.LocalAddr().String() + " : Client Connected")
|
||||||
|
|
||||||
|
reader := bufio.NewReader(tcpConn)
|
||||||
|
for {
|
||||||
|
s, err := reader.ReadString('\n')
|
||||||
|
if err != nil || err == io.EOF {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
//接收到new的指令的时候,新建一个tcp连接
|
||||||
|
if s == "new\n" {
|
||||||
|
|
||||||
|
}
|
||||||
|
if s == "hi" {
|
||||||
|
//忽略掉hi的请求
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readLoop(conn *net.TCPConn) {
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
reader := bufio.NewReader(conn)
|
||||||
|
|
||||||
|
for {
|
||||||
|
b, err := reader.Peek(5)
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF && !strings.Contains(err.Error(), "use of closed network connection") {
|
||||||
|
log.Error().Msg(err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.Discard(5)
|
||||||
|
|
||||||
|
msg_type := b[0]
|
||||||
|
|
||||||
|
if msg_type >= MSG_TYPE_MAX {
|
||||||
|
log.Error().Msgf("invalid msg type: %d", msg_type)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msg_length := binary.BigEndian.Uint32(b[1:])
|
||||||
|
data := make([]byte, msg_length)
|
||||||
|
_, err = io.ReadFull(reader, data)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Msg(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// dev.active = time.Now()
|
||||||
|
|
||||||
|
switch msg_type {
|
||||||
|
case MSG_TYPE_HEARTBEAT:
|
||||||
|
parseHeartbeat(b)
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Error().Msgf("invalid msg type: %d", msg_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseHeartbeat(b []byte) {
|
||||||
|
uptime := binary.BigEndian.Uint32(b[:4])
|
||||||
|
log.Info().Msgf("Heartbeat Time: %d", uptime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeLoop(conn *net.TCPConn) {
|
||||||
|
ticker := time.NewTicker(time.Second)
|
||||||
|
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
ninactive := 0
|
||||||
|
lastHeartbeat := time.Now()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case msg, ok := <-dev.send:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := dev.conn.Write(msg)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Msg(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-ticker.C:
|
||||||
|
now := time.Now()
|
||||||
|
if now.Sub(dev.active) > HEART_BEAT_INTERVAL*3/2 {
|
||||||
|
if dev.id == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Error().Msgf("Inactive device in long time: %s", dev.id)
|
||||||
|
if ninactive > 3 {
|
||||||
|
log.Error().Msgf("Inactive 3 times, now kill it: %s", dev.id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ninactive = ninactive + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if now.Sub(lastHeartbeat) > HEART_BEAT_INTERVAL-1 {
|
||||||
|
lastHeartbeat = now
|
||||||
|
if len(dev.send) < 1 {
|
||||||
|
dev.WriteMsg(MSG_TYPE_HEARTBEAT, []byte{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTcpConn(addr string) (*net.TCPConn, error) {
|
||||||
|
tcpConn, err := net.ResolveTCPAddr("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("TCP Address error ! " + err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conn, err := net.DialTCP("tcp", nil, tcpConn)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("TCP Connect error ! " + err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTunnel(localAddr string, remoteAddr string) {
|
||||||
|
localConn, err := createTcpConn(localAddr)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Create LocalConn Error! " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
remoteConn, err := createTcpConn(remoteAddr)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Create remoteAddr Error! " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer localConn.Close()
|
||||||
|
defer remoteConn.Close()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
_, err := io.Copy(localConn, remoteConn)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Copy error! " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
_, err := io.Copy(remoteConn, localConn)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Copy error! " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
159
server/broker.go
Normal file
159
server/broker.go
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Session struct {
|
||||||
|
dev Client
|
||||||
|
user Client
|
||||||
|
confirmed uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type Broker struct {
|
||||||
|
cfg *Config
|
||||||
|
devices map[string]Client
|
||||||
|
sessions map[string]*Session
|
||||||
|
register chan Client
|
||||||
|
unregister chan Client
|
||||||
|
deviceMessageChan chan deviceMessage
|
||||||
|
// userMessageChan chan userMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBroker(cfg *Config) *Broker {
|
||||||
|
return &Broker{
|
||||||
|
cfg: cfg,
|
||||||
|
devices: make(map[string]Client),
|
||||||
|
sessions: make(map[string]*Session),
|
||||||
|
register: make(chan Client, 1000),
|
||||||
|
unregister: make(chan Client, 1000),
|
||||||
|
deviceMessageChan: make(chan deviceMessage, 1000),
|
||||||
|
// userMessageChan: make(chan userMessage, 1000),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br *Broker) run() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case c := <-br.register:
|
||||||
|
devid := c.DeviceID()
|
||||||
|
|
||||||
|
if c.DeviceCategory() == "device" {
|
||||||
|
dev := c.(*Device)
|
||||||
|
err := byte(0)
|
||||||
|
msg := "OK"
|
||||||
|
|
||||||
|
if _, ok := br.devices[devid]; ok {
|
||||||
|
log.Error().Msg("Device ID conflicting: " + devid)
|
||||||
|
msg = "ID conflicting"
|
||||||
|
err = 1
|
||||||
|
} else {
|
||||||
|
dev.registered = true
|
||||||
|
br.devices[devid] = c
|
||||||
|
log.Info().Msgf("Device '%s' registered", devid)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.WriteMsg(MSG_TYPE_REGISTER, append([]byte{err}, msg...))
|
||||||
|
|
||||||
|
if err > 0 {
|
||||||
|
// ensure the last packet was sent
|
||||||
|
time.AfterFunc(time.Millisecond*100, func() {
|
||||||
|
dev.Close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if dev, ok := br.devices[devid]; ok {
|
||||||
|
sid := GenUniqueID("sid")
|
||||||
|
|
||||||
|
s := &Session{
|
||||||
|
dev: dev,
|
||||||
|
user: c,
|
||||||
|
}
|
||||||
|
|
||||||
|
time.AfterFunc(time.Second*3, func() {
|
||||||
|
if atomic.LoadUint32(&s.confirmed) == 0 {
|
||||||
|
c.Close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
br.sessions[sid] = s
|
||||||
|
|
||||||
|
dev.WriteMsg(MSG_TYPE_SESSION_CREATE, []byte(sid))
|
||||||
|
log.Info().Msg("New session: " + sid)
|
||||||
|
} else {
|
||||||
|
log.Error().Msgf("Not found the device '%s'", devid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case c := <-br.unregister:
|
||||||
|
devid := c.DeviceID()
|
||||||
|
|
||||||
|
c.Close()
|
||||||
|
|
||||||
|
if c.DeviceCategory() == "device" {
|
||||||
|
if !c.(*Device).registered {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(br.devices, devid)
|
||||||
|
|
||||||
|
for sid, s := range br.sessions {
|
||||||
|
if s.dev == c {
|
||||||
|
s.user.Close()
|
||||||
|
delete(br.sessions, sid)
|
||||||
|
log.Info().Msg("Delete session: " + sid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Msgf("Device '%s' unregistered", devid)
|
||||||
|
} else {
|
||||||
|
sid := c.(*Device).DeviceID()
|
||||||
|
|
||||||
|
if _, ok := br.sessions[sid]; ok {
|
||||||
|
delete(br.sessions, sid)
|
||||||
|
|
||||||
|
if dev, ok := br.devices[devid]; ok {
|
||||||
|
dev.WriteMsg(MSG_TYPE_SESSION_DESTORY, []byte(sid))
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Msg("Delete session: " + sid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case msg := <-br.deviceMessageChan:
|
||||||
|
if msg.typ == MSG_TYPE_SESSION_CREATE {
|
||||||
|
if s, ok := br.sessions[msg.sid]; ok {
|
||||||
|
|
||||||
|
atomic.StoreUint32(&s.confirmed, 1)
|
||||||
|
|
||||||
|
// u := s.user.(*user)
|
||||||
|
// u.sid = msg.sid
|
||||||
|
|
||||||
|
// userLoginAck(loginErrorNone, s.user)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.typ == MSG_TYPE_SESSION_DATA {
|
||||||
|
if s, ok := br.sessions[msg.sid]; ok {
|
||||||
|
s.user.WriteMsg(websocket.BinaryMessage, msg.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// case msg := <-br.userMessageChan:
|
||||||
|
// if s, ok := br.sessions[msg.sid]; ok {
|
||||||
|
// if dev, ok := br.devices[s.dev.DeviceID()]; ok {
|
||||||
|
// data := msg.data
|
||||||
|
|
||||||
|
// if msg.typ == websocket.BinaryMessage {
|
||||||
|
// dev.WriteMsg(MSG_TYPE_SESSION_DATA, append([]byte(msg.sid), data[1:]...))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
265
server/client.go
Normal file
265
server/client.go
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client interface {
|
||||||
|
DeviceID() string
|
||||||
|
DeviceCategory() string
|
||||||
|
|
||||||
|
WriteMsg(typ int, data []byte)
|
||||||
|
Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
const HEART_BEAT_INTERVAL = time.Second * 5 // 心跳超时时间
|
||||||
|
|
||||||
|
const (
|
||||||
|
MSG_TYPE_UNKOWNM = iota
|
||||||
|
MSG_TYPE_HEARTBEAT
|
||||||
|
MSG_TYPE_REGISTER
|
||||||
|
MSG_TYPE_SESSION_CREATE
|
||||||
|
MSG_TYPE_SESSION_DATA
|
||||||
|
MSG_TYPE_SESSION_DESTORY
|
||||||
|
MSG_TYPE_TUNNEL_CREATE
|
||||||
|
MSG_TYPE_MAX
|
||||||
|
)
|
||||||
|
|
||||||
|
type Device struct {
|
||||||
|
br *Broker
|
||||||
|
id string
|
||||||
|
category string
|
||||||
|
desc string /* description of the device */
|
||||||
|
conn net.Conn
|
||||||
|
create_time int64 /* connection time */
|
||||||
|
active time.Time
|
||||||
|
uptime uint32
|
||||||
|
token string
|
||||||
|
registered bool
|
||||||
|
closed uint32
|
||||||
|
send chan []byte // Buffered channel of outbound messages.
|
||||||
|
}
|
||||||
|
|
||||||
|
type deviceMessage struct {
|
||||||
|
sid string
|
||||||
|
typ int
|
||||||
|
data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dev *Device) DeviceID() string {
|
||||||
|
return dev.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dev *Device) DeviceCategory() string {
|
||||||
|
return dev.category
|
||||||
|
}
|
||||||
|
|
||||||
|
// 往设备写数据
|
||||||
|
func (dev *Device) WriteMsg(typ int, data []byte) {
|
||||||
|
b := []byte{byte(typ), 0, 0}
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint16(b[1:], uint16(len(data)))
|
||||||
|
|
||||||
|
dev.send <- append(b, data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dev *Device) Close() {
|
||||||
|
if atomic.LoadUint32(&dev.closed) == 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
atomic.StoreUint32(&dev.closed, 1)
|
||||||
|
|
||||||
|
log.Debug().Msgf("Device '%s' disconnected", dev.conn.RemoteAddr())
|
||||||
|
|
||||||
|
dev.conn.Close()
|
||||||
|
|
||||||
|
close(dev.send)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析设备信息
|
||||||
|
func parseDeviceInfo(dev *Device, b []byte) bool {
|
||||||
|
|
||||||
|
fields := bytes.Split(b, []byte{0})
|
||||||
|
|
||||||
|
if len(fields) < 3 {
|
||||||
|
log.Error().Msg("msgTypeRegister: invalid")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
dev.id = string(fields[0])
|
||||||
|
dev.desc = string(fields[1])
|
||||||
|
dev.token = string(fields[2])
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析设备心跳
|
||||||
|
func parseHeartbeat(dev *Device, b []byte) {
|
||||||
|
dev.uptime = binary.BigEndian.Uint32(b[:4])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dev *Device) readLoop() {
|
||||||
|
defer func() {
|
||||||
|
dev.br.unregister <- dev
|
||||||
|
}()
|
||||||
|
|
||||||
|
reader := bufio.NewReader(dev.conn)
|
||||||
|
|
||||||
|
for {
|
||||||
|
b, err := reader.Peek(5)
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF && !strings.Contains(err.Error(), "use of closed network connection") {
|
||||||
|
log.Error().Msg(err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.Discard(5)
|
||||||
|
|
||||||
|
msg_type := b[0]
|
||||||
|
|
||||||
|
if msg_type >= MSG_TYPE_MAX {
|
||||||
|
log.Error().Msgf("invalid msg type: %d", msg_type)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msg_length := binary.BigEndian.Uint32(b[1:])
|
||||||
|
data := make([]byte, msg_length)
|
||||||
|
_, err = io.ReadFull(reader, data)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Msg(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dev.active = time.Now()
|
||||||
|
|
||||||
|
switch msg_type {
|
||||||
|
case MSG_TYPE_REGISTER:
|
||||||
|
if msg_length < 2 {
|
||||||
|
log.Error().Msg("msgTypeRegister: invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !parseDeviceInfo(dev, data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dev.br.register <- dev
|
||||||
|
|
||||||
|
case MSG_TYPE_SESSION_CREATE:
|
||||||
|
|
||||||
|
if msg_length < 33 {
|
||||||
|
log.Error().Msg("msgTypeLogin: invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dev.br.deviceMessageChan <- deviceMessage{string(data[:32]), MSG_TYPE_SESSION_CREATE, data[32:]}
|
||||||
|
|
||||||
|
case MSG_TYPE_SESSION_DATA:
|
||||||
|
if msg_length < 32 {
|
||||||
|
log.Error().Msg("msgTypeTermData|msgTypeFile: invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dev.br.deviceMessageChan <- deviceMessage{string(data[:32]), MSG_TYPE_SESSION_DATA, data[32:]}
|
||||||
|
|
||||||
|
case MSG_TYPE_HEARTBEAT:
|
||||||
|
parseHeartbeat(dev, b)
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Error().Msgf("invalid msg type: %d", msg_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dev *Device) writeLoop() {
|
||||||
|
ticker := time.NewTicker(time.Second)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
ticker.Stop()
|
||||||
|
dev.br.unregister <- dev
|
||||||
|
}()
|
||||||
|
|
||||||
|
ninactive := 0
|
||||||
|
lastHeartbeat := time.Now()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case msg, ok := <-dev.send:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := dev.conn.Write(msg)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Msg(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-ticker.C:
|
||||||
|
now := time.Now()
|
||||||
|
if now.Sub(dev.active) > HEART_BEAT_INTERVAL*3/2 {
|
||||||
|
if dev.id == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Error().Msgf("Inactive device in long time: %s", dev.id)
|
||||||
|
if ninactive > 3 {
|
||||||
|
log.Error().Msgf("Inactive 3 times, now kill it: %s", dev.id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ninactive = ninactive + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if now.Sub(lastHeartbeat) > HEART_BEAT_INTERVAL-1 {
|
||||||
|
lastHeartbeat = now
|
||||||
|
if len(dev.send) < 1 {
|
||||||
|
dev.WriteMsg(MSG_TYPE_HEARTBEAT, []byte{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func listenDevice(br *Broker) {
|
||||||
|
|
||||||
|
ln, err := net.Listen("tcp", br.cfg.AddrDev)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Msgf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Msgf("Listen Device on: %s", br.cfg.AddrDev)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
conn, err := ln.Accept()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Msg(err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().Msgf("Device '%s' connected", conn.RemoteAddr())
|
||||||
|
|
||||||
|
dev := &Device{
|
||||||
|
br: br,
|
||||||
|
conn: conn,
|
||||||
|
create_time: time.Now().Unix(),
|
||||||
|
send: make(chan []byte, 256),
|
||||||
|
}
|
||||||
|
|
||||||
|
go dev.readLoop()
|
||||||
|
go dev.writeLoop()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
}
|
23
server/go.mod
Normal file
23
server/go.mod
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
module main
|
||||||
|
|
||||||
|
go 1.21.5
|
||||||
|
|
||||||
|
require github.com/rs/zerolog v1.31.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||||
|
golang.org/x/crypto v0.14.0 // indirect
|
||||||
|
golang.org/x/net v0.17.0 // indirect
|
||||||
|
golang.org/x/term v0.13.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gorilla/websocket v1.5.1
|
||||||
|
github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
|
github.com/urfave/cli/v2 v2.27.1
|
||||||
|
golang.org/x/sys v0.13.0 // indirect
|
||||||
|
)
|
34
server/go.sum
Normal file
34
server/go.sum
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
|
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||||
|
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||||
|
github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM=
|
||||||
|
github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
|
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
|
||||||
|
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
|
||||||
|
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
||||||
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||||
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||||
|
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||||
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
|
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||||
|
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||||
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||||
|
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
82
server/main.go
Normal file
82
server/main.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
AddrDev string
|
||||||
|
AddrUser string
|
||||||
|
}
|
||||||
|
|
||||||
|
func runServer(c *cli.Context) {
|
||||||
|
|
||||||
|
cfg := &Config{
|
||||||
|
AddrDev: "0.0.0.0:9011",
|
||||||
|
AddrUser: "0.0.0.0:9012",
|
||||||
|
}
|
||||||
|
|
||||||
|
br := newBroker(cfg)
|
||||||
|
listenDevice(br)
|
||||||
|
|
||||||
|
br.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := &cli.App{
|
||||||
|
Name: "XZRobot Ops Server",
|
||||||
|
Usage: "The Server Side For xzrobot ops",
|
||||||
|
Version: "1.0.0",
|
||||||
|
Commands: []*cli.Command{
|
||||||
|
{
|
||||||
|
Name: "run",
|
||||||
|
Usage: "Run Server",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "log",
|
||||||
|
Value: "log.txt",
|
||||||
|
Usage: "log file path",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "conf",
|
||||||
|
Aliases: []string{"c"},
|
||||||
|
Value: "./rttys.conf",
|
||||||
|
Usage: "config file to load",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "addr-dev",
|
||||||
|
Value: ":9011",
|
||||||
|
Usage: "address to listen device",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "addr-user",
|
||||||
|
Value: ":9012",
|
||||||
|
Usage: "address to listen user",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "db",
|
||||||
|
Value: "sqlite://database.db",
|
||||||
|
Usage: "database source",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
runServer(c)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
c.App.Command("run").Run(c)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := app.Run(os.Args)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
41
server/utils.go
Normal file
41
server/utils.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/howeyc/gopass"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenUniqueID generate a unique ID
|
||||||
|
func GenUniqueID(extra string) string {
|
||||||
|
buf := make([]byte, 20)
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint32(buf, uint32(time.Now().Unix()))
|
||||||
|
io.ReadFull(rand.Reader, buf[4:])
|
||||||
|
|
||||||
|
h := md5.New()
|
||||||
|
h.Write(buf)
|
||||||
|
h.Write([]byte(extra))
|
||||||
|
|
||||||
|
return hex.EncodeToString(h.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenToken generate a token
|
||||||
|
func GenToken() {
|
||||||
|
password, err := gopass.GetPasswdPrompt("Please set a password:", true, os.Stdin, os.Stdout)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Msg(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
token := GenUniqueID(string(password))
|
||||||
|
|
||||||
|
fmt.Println("Your token is:", token)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user