feat: 添加更多示例
parent
357ba1be05
commit
bb8918e785
@ -1 +1,284 @@
|
|||||||
package client
|
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
|
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
|
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
|
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
|
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
|
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
|
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