feat: 添加更多示例
parent
357ba1be05
commit
bb8918e785
@ -1 +1,284 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"git.noahlan.cn/northlan/ngs/internal/codec"
|
||||
"git.noahlan.cn/northlan/ngs/internal/log"
|
||||
"git.noahlan.cn/northlan/ngs/internal/message"
|
||||
"git.noahlan.cn/northlan/ngs/internal/packet"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
hsd []byte // handshake data
|
||||
had []byte // handshake ack data
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
hsd, err = codec.Encode(packet.Handshake, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
had, err = codec.Encode(packet.HandshakeAck, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
type (
|
||||
// Callback represents the callback type which will be called
|
||||
// when the correspond events is occurred.
|
||||
Callback func(data interface{})
|
||||
|
||||
// Client is a tiny Ngs client
|
||||
Client struct {
|
||||
conn net.Conn // low-level connection
|
||||
codec *codec.Decoder // decoder
|
||||
die chan struct{} // connector close channel
|
||||
chSend chan []byte // send queue
|
||||
mid uint64 // message id
|
||||
|
||||
// events handler
|
||||
muEvents sync.RWMutex
|
||||
events map[string]Callback
|
||||
|
||||
// response handler
|
||||
muResponses sync.RWMutex
|
||||
responses map[uint64]Callback
|
||||
|
||||
connectedCallback func() // connected callback
|
||||
}
|
||||
)
|
||||
|
||||
// NewClient create a new Client
|
||||
func NewClient() *Client {
|
||||
return &Client{
|
||||
die: make(chan struct{}),
|
||||
codec: codec.NewDecoder(),
|
||||
chSend: make(chan []byte, 64),
|
||||
mid: 1,
|
||||
events: map[string]Callback{},
|
||||
responses: map[uint64]Callback{},
|
||||
}
|
||||
}
|
||||
|
||||
// Start connect to the server and send/recv between the c/s
|
||||
func (c *Client) Start(addr string) error {
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.conn = conn
|
||||
|
||||
go c.write()
|
||||
|
||||
// send handshake packet
|
||||
c.send(hsd)
|
||||
|
||||
// read and process network message
|
||||
go c.read()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnConnected set the callback which will be called when the client connected to the server
|
||||
func (c *Client) OnConnected(callback func()) {
|
||||
c.connectedCallback = callback
|
||||
}
|
||||
|
||||
// Request send a request to server and register a callback for the response
|
||||
func (c *Client) Request(route string, v proto.Message, callback Callback) error {
|
||||
data, err := serialize(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg := &message.Message{
|
||||
Type: message.Request,
|
||||
Route: route,
|
||||
ID: c.mid,
|
||||
Data: data,
|
||||
}
|
||||
|
||||
c.setResponseHandler(c.mid, callback)
|
||||
if err := c.sendMessage(msg); err != nil {
|
||||
c.setResponseHandler(c.mid, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Notify send a notification to server
|
||||
func (c *Client) Notify(route string, v proto.Message) error {
|
||||
data, err := serialize(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg := &message.Message{
|
||||
Type: message.Notify,
|
||||
Route: route,
|
||||
Data: data,
|
||||
}
|
||||
return c.sendMessage(msg)
|
||||
}
|
||||
|
||||
// On add the callback for the event
|
||||
func (c *Client) On(event string, callback Callback) {
|
||||
c.muEvents.Lock()
|
||||
defer c.muEvents.Unlock()
|
||||
|
||||
c.events[event] = callback
|
||||
}
|
||||
|
||||
// Close the connection, and shutdown the benchmark
|
||||
func (c *Client) Close() {
|
||||
c.conn.Close()
|
||||
close(c.die)
|
||||
}
|
||||
|
||||
func (c *Client) eventHandler(event string) (Callback, bool) {
|
||||
c.muEvents.RLock()
|
||||
defer c.muEvents.RUnlock()
|
||||
|
||||
cb, ok := c.events[event]
|
||||
return cb, ok
|
||||
}
|
||||
|
||||
func (c *Client) responseHandler(mid uint64) (Callback, bool) {
|
||||
c.muResponses.RLock()
|
||||
defer c.muResponses.RUnlock()
|
||||
|
||||
cb, ok := c.responses[mid]
|
||||
return cb, ok
|
||||
}
|
||||
|
||||
func (c *Client) setResponseHandler(mid uint64, cb Callback) {
|
||||
c.muResponses.Lock()
|
||||
defer c.muResponses.Unlock()
|
||||
|
||||
if cb == nil {
|
||||
delete(c.responses, mid)
|
||||
} else {
|
||||
c.responses[mid] = cb
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) sendMessage(msg *message.Message) error {
|
||||
data, err := msg.Encode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//log.Printf("%+v",msg)
|
||||
|
||||
payload, err := codec.Encode(packet.Data, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.mid++
|
||||
c.send(payload)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) write() {
|
||||
defer close(c.chSend)
|
||||
|
||||
for {
|
||||
select {
|
||||
case data := <-c.chSend:
|
||||
if _, err := c.conn.Write(data); err != nil {
|
||||
log.Println(err.Error())
|
||||
c.Close()
|
||||
}
|
||||
|
||||
case <-c.die:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) send(data []byte) {
|
||||
c.chSend <- data
|
||||
}
|
||||
|
||||
func (c *Client) read() {
|
||||
buf := make([]byte, 2048)
|
||||
|
||||
for {
|
||||
n, err := c.conn.Read(buf)
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
|
||||
packets, err := c.codec.Decode(buf[:n])
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
|
||||
for i := range packets {
|
||||
p := packets[i]
|
||||
c.processPacket(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) processPacket(p *packet.Packet) {
|
||||
switch p.Type {
|
||||
case packet.Handshake:
|
||||
c.send(had)
|
||||
c.connectedCallback()
|
||||
case packet.Data:
|
||||
msg, err := message.Decode(p.Data)
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
return
|
||||
}
|
||||
c.processMessage(msg)
|
||||
|
||||
case packet.Kick:
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) processMessage(msg *message.Message) {
|
||||
switch msg.Type {
|
||||
case message.Push:
|
||||
cb, ok := c.eventHandler(msg.Route)
|
||||
if !ok {
|
||||
log.Println("event handler not found", msg.Route)
|
||||
return
|
||||
}
|
||||
|
||||
cb(msg.Data)
|
||||
|
||||
case message.Response:
|
||||
cb, ok := c.responseHandler(msg.ID)
|
||||
if !ok {
|
||||
log.Println("response handler not found", msg.ID)
|
||||
return
|
||||
}
|
||||
|
||||
cb(msg.Data)
|
||||
c.setResponseHandler(msg.ID, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func serialize(v proto.Message) ([]byte, error) {
|
||||
data, err := proto.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
@ -1 +1,102 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"git.noahlan.cn/northlan/ngs"
|
||||
"git.noahlan.cn/northlan/ngs/benchmark/testdata"
|
||||
"git.noahlan.cn/northlan/ngs/component"
|
||||
"git.noahlan.cn/northlan/ngs/serialize/protobuf"
|
||||
"git.noahlan.cn/northlan/ngs/session"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
addr = "127.0.0.1:13250" // local address
|
||||
conc = 1000 // concurrent client count
|
||||
)
|
||||
|
||||
//
|
||||
type TestHandler struct {
|
||||
component.Base
|
||||
metrics int32
|
||||
group *ngs.Group
|
||||
}
|
||||
|
||||
func (h *TestHandler) AfterInit() {
|
||||
ticker := time.NewTicker(time.Second)
|
||||
|
||||
// metrics output ticker
|
||||
go func() {
|
||||
for range ticker.C {
|
||||
println("QPS", atomic.LoadInt32(&h.metrics))
|
||||
atomic.StoreInt32(&h.metrics, 0)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func NewTestHandler() *TestHandler {
|
||||
return &TestHandler{
|
||||
group: ngs.NewGroup("handler"),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *TestHandler) Ping(s *session.Session, data *testdata.Ping) error {
|
||||
atomic.AddInt32(&h.metrics, 1)
|
||||
return s.Push("pong", &testdata.Pong{Content: data.Content})
|
||||
}
|
||||
|
||||
func server() {
|
||||
components := &component.Components{}
|
||||
components.Register(NewTestHandler())
|
||||
|
||||
ngs.Listen(addr,
|
||||
ngs.WithDebugMode(),
|
||||
ngs.WithSerializer(protobuf.NewSerializer()),
|
||||
ngs.WithComponents(components),
|
||||
)
|
||||
}
|
||||
|
||||
func client() {
|
||||
c := NewClient()
|
||||
|
||||
chReady := make(chan struct{})
|
||||
c.OnConnected(func() {
|
||||
chReady <- struct{}{}
|
||||
})
|
||||
|
||||
if err := c.Start(addr); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.On("pong", func(data interface{}) {})
|
||||
|
||||
<-chReady
|
||||
for /*i := 0; i < 1; i++*/ {
|
||||
c.Notify("TestHandler.Ping", &testdata.Ping{})
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIO(t *testing.T) {
|
||||
go server()
|
||||
|
||||
// wait server startup
|
||||
time.Sleep(1 * time.Second)
|
||||
for i := 0; i < conc; i++ {
|
||||
go client()
|
||||
}
|
||||
|
||||
log.SetFlags(log.LstdFlags | log.Llongfile)
|
||||
|
||||
sg := make(chan os.Signal)
|
||||
signal.Notify(sg, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGKILL)
|
||||
|
||||
<-sg
|
||||
|
||||
t.Log("exit")
|
||||
}
|
||||
|
@ -1 +1,58 @@
|
||||
package chat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.noahlan.cn/northlan/ngs"
|
||||
"git.noahlan.cn/northlan/ngs/component"
|
||||
"git.noahlan.cn/northlan/ngs/examples/cluster/protocol"
|
||||
"git.noahlan.cn/northlan/ngs/internal/log"
|
||||
"git.noahlan.cn/northlan/ngs/session"
|
||||
)
|
||||
|
||||
type RoomService struct {
|
||||
component.Base
|
||||
group *ngs.Group
|
||||
}
|
||||
|
||||
func newRoomService() *RoomService {
|
||||
return &RoomService{
|
||||
group: ngs.NewGroup("all-users"),
|
||||
}
|
||||
}
|
||||
|
||||
func (rs *RoomService) JoinRoom(s *session.Session, msg *protocol.JoinRoomRequest) error {
|
||||
if err := s.Bind(msg.MasterUid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
broadcast := &protocol.NewUserBroadcast{
|
||||
Content: fmt.Sprintf("User user join: %v", msg.Nickname),
|
||||
}
|
||||
if err := rs.group.Broadcast("onNewUser", broadcast); err != nil {
|
||||
return err
|
||||
}
|
||||
return rs.group.Add(s)
|
||||
}
|
||||
|
||||
type SyncMessage struct {
|
||||
Name string `json:"name"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
func (rs *RoomService) SyncMessage(s *session.Session, msg *SyncMessage) error {
|
||||
// Send an RPC to master server to stats
|
||||
if err := s.RPC("TopicService.Stats", &protocol.MasterStats{Uid: s.UID()}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Sync message to all members in this room
|
||||
return rs.group.Broadcast("onMessage", msg)
|
||||
}
|
||||
|
||||
func (rs *RoomService) userDisconnected(s *session.Session) {
|
||||
if err := rs.group.Leave(s); err != nil {
|
||||
log.Println("Remove user from group failed", s.UID(), err)
|
||||
return
|
||||
}
|
||||
log.Println("User session disconnected", s.UID())
|
||||
}
|
||||
|
@ -1 +1,21 @@
|
||||
package chat
|
||||
|
||||
import (
|
||||
"git.noahlan.cn/northlan/ngs/component"
|
||||
"git.noahlan.cn/northlan/ngs/session"
|
||||
)
|
||||
|
||||
var (
|
||||
// Services is All services in chat server
|
||||
Services = &component.Components{}
|
||||
|
||||
roomService = newRoomService()
|
||||
)
|
||||
|
||||
func init() {
|
||||
Services.Register(roomService)
|
||||
}
|
||||
|
||||
func OnSessionClosed(s *session.Session) {
|
||||
roomService.userDisconnected(s)
|
||||
}
|
||||
|
@ -1 +1,44 @@
|
||||
package gate
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.noahlan.cn/northlan/ngs/component"
|
||||
"git.noahlan.cn/northlan/ngs/examples/cluster/protocol"
|
||||
"git.noahlan.cn/northlan/ngs/session"
|
||||
)
|
||||
|
||||
type BindService struct {
|
||||
component.Base
|
||||
nextGateUid int64
|
||||
}
|
||||
|
||||
func newBindService() *BindService {
|
||||
return &BindService{}
|
||||
}
|
||||
|
||||
type (
|
||||
LoginRequest struct {
|
||||
Nickname string `json:"nickname"`
|
||||
}
|
||||
LoginResponse struct {
|
||||
Code int `json:"code"`
|
||||
}
|
||||
)
|
||||
|
||||
func (bs *BindService) Login(s *session.Session, msg *LoginRequest) error {
|
||||
bs.nextGateUid++
|
||||
uid := bs.nextGateUid
|
||||
request := &protocol.NewUserRequest{
|
||||
Nickname: msg.Nickname,
|
||||
GateUid: uid,
|
||||
}
|
||||
if err := s.RPC("TopicService.NewUser", request); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.Response(&LoginResponse{})
|
||||
}
|
||||
|
||||
func (bs *BindService) BindChatServer(s *session.Session, msg []byte) error {
|
||||
return errors.New(fmt.Sprintf("not implement"))
|
||||
}
|
||||
|
@ -1 +1,14 @@
|
||||
package gate
|
||||
|
||||
import "git.noahlan.cn/northlan/ngs/component"
|
||||
|
||||
var (
|
||||
// Services is All services in gate server
|
||||
Services = &component.Components{}
|
||||
|
||||
bindService = newBindService()
|
||||
)
|
||||
|
||||
func init() {
|
||||
Services.Register(bindService)
|
||||
}
|
||||
|
@ -1 +1,179 @@
|
||||
package cluster
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"git.noahlan.cn/northlan/ngs"
|
||||
"git.noahlan.cn/northlan/ngs/examples/cluster/chat"
|
||||
"git.noahlan.cn/northlan/ngs/examples/cluster/gate"
|
||||
"git.noahlan.cn/northlan/ngs/examples/cluster/master"
|
||||
"git.noahlan.cn/northlan/ngs/internal/log"
|
||||
"git.noahlan.cn/northlan/ngs/serialize/json"
|
||||
"git.noahlan.cn/northlan/ngs/session"
|
||||
"github.com/urfave/cli"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "ClusterDemo"
|
||||
app.Author = "NorthLan"
|
||||
app.Email = "6995syu@163.com"
|
||||
app.Description = "cluster demo"
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "master",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "listen,l",
|
||||
Usage: "--l --listen :8888 Master service listen address",
|
||||
Value: "127.0.0.1:34567",
|
||||
},
|
||||
},
|
||||
Action: runMaster,
|
||||
},
|
||||
{
|
||||
Name: "gate",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "master",
|
||||
Usage: "master server address",
|
||||
Value: "127.0.0.1:34567",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "listen,l",
|
||||
Usage: "Gate service listen address",
|
||||
Value: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "gate-address",
|
||||
Usage: "Client connect address",
|
||||
Value: "",
|
||||
},
|
||||
},
|
||||
Action: runGate,
|
||||
},
|
||||
{
|
||||
Name: "chat",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "master",
|
||||
Usage: "master server address",
|
||||
Value: "127.0.0.1:34567",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "listen,l",
|
||||
Usage: "Chat service listen address",
|
||||
Value: "",
|
||||
},
|
||||
},
|
||||
Action: runChat,
|
||||
},
|
||||
}
|
||||
//log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
log.Fatalf("Startup server error %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func runMaster(args *cli.Context) error {
|
||||
listen := args.String("listen")
|
||||
if listen == "" {
|
||||
return errors.New("master listen address cannot empty")
|
||||
}
|
||||
|
||||
webDir := filepath.Join(srcPath(), "master", "web")
|
||||
log.Println("Ngs master server web content directory", webDir)
|
||||
log.Println("Ngs master listen address", listen)
|
||||
log.Println("Open http://127.0.0.1:12345/web/ in browser")
|
||||
|
||||
http.Handle("/web/", http.StripPrefix("/web/", http.FileServer(http.Dir(webDir))))
|
||||
|
||||
go func() {
|
||||
if err := http.ListenAndServe(":12345", nil); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Register session closed callback
|
||||
session.Lifetime.OnClosed(master.OnSessionClosed)
|
||||
|
||||
// Startup Ngs server with the specified listen address
|
||||
ngs.Listen(listen,
|
||||
ngs.WithMaster(),
|
||||
ngs.WithComponents(master.Services),
|
||||
ngs.WithSerializer(json.NewSerializer()),
|
||||
ngs.WithDebugMode(),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func srcPath() string {
|
||||
_, file, _, _ := runtime.Caller(0)
|
||||
return filepath.Dir(file)
|
||||
}
|
||||
|
||||
func runGate(args *cli.Context) error {
|
||||
listen := args.String("listen")
|
||||
if listen == "" {
|
||||
return errors.New("gate listen address cannot empty")
|
||||
}
|
||||
|
||||
masterAddr := args.String("master")
|
||||
if masterAddr == "" {
|
||||
return errors.New("master address cannot empty")
|
||||
}
|
||||
|
||||
gateAddr := args.String("gate-address")
|
||||
if gateAddr == "" {
|
||||
return errors.New("gate address cannot empty")
|
||||
}
|
||||
|
||||
log.Println("Current server listen address", listen)
|
||||
log.Println("Current gate server address", gateAddr)
|
||||
log.Println("Remote master server address", masterAddr)
|
||||
|
||||
// Startup Ngs server with the specified listen address
|
||||
ngs.Listen(listen,
|
||||
ngs.WithAdvertiseAddr(masterAddr),
|
||||
ngs.WithClientAddr(gateAddr),
|
||||
ngs.WithComponents(gate.Services),
|
||||
ngs.WithSerializer(json.NewSerializer()),
|
||||
ngs.WithIsWebsocket(true),
|
||||
ngs.WithWSPath("/ngs"),
|
||||
ngs.WithCheckOriginFunc(func(_ *http.Request) bool { return true }),
|
||||
ngs.WithDebugMode(),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runChat(args *cli.Context) error {
|
||||
listen := args.String("listen")
|
||||
if listen == "" {
|
||||
return errors.New("chat listen address cannot empty")
|
||||
}
|
||||
|
||||
masterAddr := args.String("master")
|
||||
if listen == "" {
|
||||
return errors.New("master address cannot empty")
|
||||
}
|
||||
|
||||
log.Println("Current chat server listen address", listen)
|
||||
log.Println("Remote master server address", masterAddr)
|
||||
|
||||
// Register session closed callback
|
||||
session.Lifetime.OnClosed(chat.OnSessionClosed)
|
||||
|
||||
// Startup Ngs server with the specified listen address
|
||||
ngs.Listen(listen,
|
||||
ngs.WithAdvertiseAddr(masterAddr),
|
||||
ngs.WithComponents(chat.Services),
|
||||
ngs.WithSerializer(json.NewSerializer()),
|
||||
ngs.WithDebugMode(),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -1 +1,23 @@
|
||||
package main
|
||||
package master
|
||||
|
||||
import (
|
||||
"git.noahlan.cn/northlan/ngs/component"
|
||||
"git.noahlan.cn/northlan/ngs/session"
|
||||
)
|
||||
|
||||
var (
|
||||
// Services is All services in master server
|
||||
Services = &component.Components{}
|
||||
|
||||
// topicService Topic service
|
||||
topicService = newTopicService()
|
||||
// ... other services
|
||||
)
|
||||
|
||||
func init() {
|
||||
Services.Register(topicService)
|
||||
}
|
||||
|
||||
func OnSessionClosed(s *session.Session) {
|
||||
topicService.userDisconnected(s)
|
||||
}
|
||||
|
@ -1 +1,90 @@
|
||||
package master
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.noahlan.cn/northlan/ngs/component"
|
||||
"git.noahlan.cn/northlan/ngs/examples/cluster/protocol"
|
||||
"git.noahlan.cn/northlan/ngs/internal/log"
|
||||
"git.noahlan.cn/northlan/ngs/session"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
session *session.Session
|
||||
nickname string
|
||||
gateId int64
|
||||
masterId int64
|
||||
balance int64
|
||||
message int
|
||||
}
|
||||
|
||||
type TopicService struct {
|
||||
component.Base
|
||||
nextUid int64
|
||||
users map[int64]*User
|
||||
}
|
||||
|
||||
func newTopicService() *TopicService {
|
||||
return &TopicService{
|
||||
users: map[int64]*User{},
|
||||
}
|
||||
}
|
||||
|
||||
type ExistsMembersResponse struct {
|
||||
Members string `json:"members"`
|
||||
}
|
||||
|
||||
func (ts *TopicService) NewUser(s *session.Session, msg *protocol.NewUserRequest) error {
|
||||
ts.nextUid++
|
||||
uid := ts.nextUid
|
||||
if err := s.Bind(uid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var members []string
|
||||
for _, u := range ts.users {
|
||||
members = append(members, u.nickname)
|
||||
}
|
||||
err := s.Push("onMembers", &ExistsMembersResponse{Members: strings.Join(members, ",")})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user := &User{
|
||||
session: s,
|
||||
nickname: msg.Nickname,
|
||||
gateId: msg.GateUid,
|
||||
masterId: uid,
|
||||
balance: 1000,
|
||||
}
|
||||
ts.users[uid] = user
|
||||
|
||||
chat := &protocol.JoinRoomRequest{
|
||||
Nickname: msg.Nickname,
|
||||
GateUid: msg.GateUid,
|
||||
MasterUid: uid,
|
||||
}
|
||||
return s.RPC("RoomService.JoinRoom", chat)
|
||||
}
|
||||
|
||||
type UserBalanceResponse struct {
|
||||
CurrentBalance int64 `json:"currentBalance"`
|
||||
}
|
||||
|
||||
func (ts *TopicService) Stats(s *session.Session, msg *protocol.MasterStats) error {
|
||||
// It's OK to use map without lock because of this service running in main thread
|
||||
user, found := ts.users[msg.Uid]
|
||||
if !found {
|
||||
return errors.New(fmt.Sprintf("User not found: %v", msg.Uid))
|
||||
}
|
||||
user.message++
|
||||
user.balance--
|
||||
return s.Push("onBalance", &UserBalanceResponse{user.balance})
|
||||
}
|
||||
|
||||
func (ts *TopicService) userDisconnected(s *session.Session) {
|
||||
uid := s.UID()
|
||||
delete(ts.users, uid)
|
||||
log.Println("User session disconnected", s.UID())
|
||||
}
|
||||
|
Loading…
Reference in New Issue