├── version.go ├── clusterserver ├── clusterglobal.go ├── master.go └── clusterserver.go ├── iface ├── icommand.go ├── icommandinterpreter.go ├── idatapack.go ├── iconnectionmgr.go ├── imsghandle.go ├── iwriter.go ├── iclient.go ├── iconnection.go ├── iserver.go └── iprotocol.go ├── utils ├── uuid_test.go ├── uuidfactory.go ├── tools.go └── globalobj.go ├── .gitignore ├── sys_rpc ├── root_rpc.go ├── master_rpc.go └── child_rpc.go ├── timer ├── safetimer_test.go ├── timer.go ├── safetimer.go └── hashwheel.go ├── logger ├── logger_test.go └── logger.go ├── fnet ├── connectionmgr.go ├── datapack.go ├── tcpclient.go ├── msghandle.go ├── protocol.go └── connection.go ├── cluster ├── rpc.go ├── clusterconf.go ├── cmdinterpreter.go ├── asyncresult.go ├── child.go ├── rpcpack.go ├── telnetprotocol.go ├── rpchandle.go └── rpcprotocol.go ├── telnetcmd ├── pprofcpucommand.go └── mastercommand.go ├── xingo.go ├── README.md ├── fserver └── server.go ├── db └── mongo │ ├── dboperate_test.go │ └── dboperate.go └── LICENSE /version.go: -------------------------------------------------------------------------------- 1 | package xingo 2 | 3 | const version = "0.0.1" 4 | -------------------------------------------------------------------------------- /clusterserver/clusterglobal.go: -------------------------------------------------------------------------------- 1 | package clusterserver 2 | 3 | var GlobalMaster *Master 4 | var GlobalClusterServer *ClusterServer 5 | -------------------------------------------------------------------------------- /iface/icommand.go: -------------------------------------------------------------------------------- 1 | package iface 2 | 3 | type ICommand interface { 4 | Run([]string) string 5 | Help() string 6 | Name() string 7 | } 8 | -------------------------------------------------------------------------------- /iface/icommandinterpreter.go: -------------------------------------------------------------------------------- 1 | package iface 2 | 3 | type ICommandInterpreter interface { 4 | AddCommand(ICommand) 5 | Excute(string) string 6 | IsQuitCmd(string) bool 7 | } 8 | 9 | -------------------------------------------------------------------------------- /utils/uuid_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "testing" 4 | 5 | func Test(t *testing.T) { 6 | a := NewUUIDGenerator("hhh") 7 | for { 8 | t.Log(a.Get()) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /iface/idatapack.go: -------------------------------------------------------------------------------- 1 | package iface 2 | 3 | type Idatapack interface { 4 | GetHeadLen() int32 5 | Unpack([]byte) (interface{}, error) 6 | Pack(uint32, interface{}) ([]byte, error) 7 | } 8 | -------------------------------------------------------------------------------- /iface/iconnectionmgr.go: -------------------------------------------------------------------------------- 1 | package iface 2 | 3 | type Iconnectionmgr interface { 4 | Add(Iconnection) 5 | Remove(Iconnection) error 6 | Get(uint32) (Iconnection, error) 7 | Len() int 8 | } 9 | -------------------------------------------------------------------------------- /iface/imsghandle.go: -------------------------------------------------------------------------------- 1 | package iface 2 | 3 | type Imsghandle interface { 4 | DeliverToMsgQueue(interface{}) 5 | DoMsgFromGoRoutine(interface{}) 6 | AddRouter(interface{}) 7 | StartWorkerLoop(int) 8 | } 9 | -------------------------------------------------------------------------------- /iface/iwriter.go: -------------------------------------------------------------------------------- 1 | package iface 2 | 3 | type IWriter interface { 4 | Send([]byte) error 5 | GetProperty(string) (interface{}, error) 6 | SetProperty(string, interface{}) 7 | RemoveProperty(string) 8 | } 9 | -------------------------------------------------------------------------------- /iface/iclient.go: -------------------------------------------------------------------------------- 1 | package iface 2 | 3 | import "net" 4 | 5 | type Iclient interface { 6 | Start() 7 | Stop(bool) 8 | GetConnection() *net.TCPConn 9 | Send([]byte) error 10 | GetProperty(string) (interface{}, error) 11 | SetProperty(string, interface{}) 12 | RemoveProperty(string) 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | .idea 26 | -------------------------------------------------------------------------------- /iface/iconnection.go: -------------------------------------------------------------------------------- 1 | package iface 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | type Iconnection interface { 8 | Start() 9 | Stop() 10 | GetConnection() *net.TCPConn 11 | GetSessionId() uint32 12 | Send([]byte) error 13 | SendBuff([]byte) error 14 | RemoteAddr() net.Addr 15 | LostConnection() 16 | GetProperty(string) (interface{}, error) 17 | SetProperty(string, interface{}) 18 | RemoveProperty(string) 19 | } 20 | -------------------------------------------------------------------------------- /iface/iserver.go: -------------------------------------------------------------------------------- 1 | package iface 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type Iserver interface { 8 | Start() 9 | Stop() 10 | Serve() 11 | GetConnectionMgr() Iconnectionmgr 12 | GetConnectionQueue() chan interface{} 13 | AddRouter(router interface{}) 14 | CallLater(durations time.Duration, f func(v ...interface{}), args ...interface{}) 15 | CallWhen(ts string, f func(v ...interface{}), args ...interface{}) 16 | CallLoop(durations time.Duration, f func(v ...interface{}), args ...interface{}) 17 | } 18 | -------------------------------------------------------------------------------- /iface/iprotocol.go: -------------------------------------------------------------------------------- 1 | package iface 2 | 3 | type IClientProtocol interface { 4 | OnConnectionMade(fconn Iclient) 5 | OnConnectionLost(fconn Iclient) 6 | StartReadThread(fconn Iclient) 7 | InitWorker(int32) 8 | AddRpcRouter(interface{}) 9 | GetMsgHandle() Imsghandle 10 | GetDataPack() Idatapack 11 | } 12 | 13 | type IServerProtocol interface { 14 | OnConnectionMade(fconn Iconnection) 15 | OnConnectionLost(fconn Iconnection) 16 | StartReadThread(fconn Iconnection) 17 | InitWorker(int32) 18 | AddRpcRouter(interface{}) 19 | GetMsgHandle() Imsghandle 20 | GetDataPack() Idatapack 21 | } 22 | -------------------------------------------------------------------------------- /sys_rpc/root_rpc.go: -------------------------------------------------------------------------------- 1 | package sys_rpc 2 | 3 | import ( 4 | "github.com/viphxin/xingo/cluster" 5 | 6 | "github.com/viphxin/xingo/clusterserver" 7 | "github.com/viphxin/xingo/logger" 8 | "github.com/viphxin/xingo/utils" 9 | ) 10 | 11 | type RootRpc struct { 12 | } 13 | 14 | /* 15 | 子节点连上来的通知 16 | */ 17 | func (this *RootRpc) TakeProxy(request *cluster.RpcRequest) { 18 | name := request.Rpcdata.Args[0].(string) 19 | logger.Info("child node " + name + " connected to " + utils.GlobalObject.Name) 20 | //加到childs并且绑定链接connetion对象 21 | clusterserver.GlobalClusterServer.AddChild(name, request.Fconn) 22 | } 23 | -------------------------------------------------------------------------------- /utils/uuidfactory.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | const ( 8 | MAXUINT32 = 4294967295 9 | DEFAULT_UUID_CNT_CACHE = 512 10 | ) 11 | 12 | type UUIDGenerator struct { 13 | Prefix string 14 | idGen uint32 15 | internalChan chan uint32 16 | } 17 | 18 | func NewUUIDGenerator(prefix string) *UUIDGenerator { 19 | gen := &UUIDGenerator{ 20 | Prefix: prefix, 21 | idGen: 0, 22 | internalChan: make(chan uint32, DEFAULT_UUID_CNT_CACHE), 23 | } 24 | gen.startGen() 25 | return gen 26 | } 27 | 28 | func (this *UUIDGenerator) startGen() { 29 | go func() { 30 | for { 31 | if this.idGen+1 > MAXUINT32 { 32 | this.idGen = 1 33 | } else { 34 | this.idGen += 1 35 | } 36 | this.internalChan <- this.idGen 37 | } 38 | }() 39 | } 40 | 41 | func (this *UUIDGenerator) Get() string { 42 | idgen := <-this.internalChan 43 | return fmt.Sprintf("%s%d", this.Prefix, idgen) 44 | } 45 | 46 | func (this *UUIDGenerator) GetUint32() uint32 { 47 | return <-this.internalChan 48 | } -------------------------------------------------------------------------------- /timer/safetimer_test.go: -------------------------------------------------------------------------------- 1 | package timer 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "testing" 7 | "time" 8 | "sync/atomic" 9 | "github.com/viphxin/xingo/logger" 10 | ) 11 | 12 | func test(a ...interface{}) { 13 | fmt.Println(a[0], "============", a[1]) 14 | } 15 | 16 | var ( 17 | tt = int64(0) 18 | ) 19 | 20 | func Test(t *testing.T) { 21 | 22 | s := NewSafeTimerScheduel() 23 | go func() { 24 | for { 25 | df := <-s.GetTriggerChannel() 26 | df.Call() 27 | atomic.AddInt64(&tt, -1) 28 | } 29 | }() 30 | go func() { 31 | i := 0 32 | for i<50000{ 33 | s.CreateTimer(int64(rand.Int31n(3600*1e3)), test, []interface{}{22, 33}) 34 | atomic.AddInt64(&tt, 1) 35 | time.Sleep(1 * time.Second) 36 | i += 1 37 | } 38 | }() 39 | go func(){ 40 | ii := 0 41 | for ii < 50000{ 42 | s.CreateTimer(int64(rand.Int31n(3600*1e3)), test, []interface{}{22, 33}) 43 | atomic.AddInt64(&tt, 1) 44 | time.Sleep(1 * time.Second) 45 | ii += 1 46 | } 47 | }() 48 | 49 | for{ 50 | time.Sleep(60*time.Second) 51 | logger.Info("last timer: ", atomic.LoadInt64(&tt)) 52 | } 53 | } -------------------------------------------------------------------------------- /logger/logger_test.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "runtime" 5 | "strconv" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func runlog(i int) { 11 | Debug("Debug>>>>>>>>>>>>>>>>>>>>>>" + strconv.Itoa(i)) 12 | Info("Info>>>>>>>>>>>>>>>>>>>>>>>>>" + strconv.Itoa(i)) 13 | Warn("Warn>>>>>>>>>>>>>>>>>>>>>>>>>" + strconv.Itoa(i)) 14 | Error("Error>>>>>>>>>>>>>>>>>>>>>>>>>" + strconv.Itoa(i)) 15 | Fatal("Fatal>>>>>>>>>>>>>>>>>>>>>>>>>" + strconv.Itoa(i)) 16 | } 17 | 18 | func Test(t *testing.T) { 19 | runtime.GOMAXPROCS(runtime.NumCPU()) 20 | 21 | //指定是否控制台打印,默认为true 22 | SetConsole(true) 23 | //指定日志文件备份方式为文件大小的方式 24 | //第一个参数为日志文件存放目录 25 | //第二个参数为日志文件命名 26 | //第三个参数为备份文件最大数量 27 | //第四个参数为备份文件大小 28 | //第五个参数为文件大小的单位 29 | //logger.SetRollingFile("d:/logtest", "test.log", 10, 5, logger.KB) 30 | 31 | //指定日志文件备份方式为日期的方式 32 | //第一个参数为日志文件存放目录 33 | //第二个参数为日志文件命名 34 | SetRollingDaily("./logtest", "test.log") 35 | 36 | //指定日志级别 ALL,DEBUG,INFO,WARN,ERROR,FATAL,OFF 级别由低到高 37 | //一般习惯是测试阶段为debug,生成环境为info以上 38 | SetLevel(ERROR) 39 | 40 | for i := 10000; i > 0; i-- { 41 | go runlog(i) 42 | time.Sleep(1000 * time.Millisecond) 43 | } 44 | time.Sleep(15 * time.Second) 45 | } 46 | -------------------------------------------------------------------------------- /timer/timer.go: -------------------------------------------------------------------------------- 1 | package timer 2 | 3 | import ( 4 | "time" 5 | "reflect" 6 | "fmt" 7 | "github.com/viphxin/xingo/logger" 8 | ) 9 | 10 | type DelayCall struct { 11 | f func(v ...interface{}) 12 | args []interface{} 13 | } 14 | 15 | func (this *DelayCall)Call(){ 16 | defer func(){ 17 | if err := recover(); err != nil{ 18 | logger.Error(this.String(), "Call Error: ", err) 19 | } 20 | }() 21 | 22 | this.f(this.args...) 23 | } 24 | 25 | func (this *DelayCall)String() string{ 26 | funcType := reflect.TypeOf(this.f) 27 | return fmt.Sprintf("DelayCall function: %s. args: %v.", funcType.Name(), this.args) 28 | } 29 | 30 | type Timer struct { 31 | durations time.Duration 32 | delayCall *DelayCall 33 | } 34 | 35 | func NewTimer(durations time.Duration, f func(v ...interface{}), args []interface{}) *Timer{ 36 | return &Timer{ 37 | durations : durations, 38 | delayCall: &DelayCall{ 39 | f: f, 40 | args: args, 41 | }, 42 | } 43 | } 44 | 45 | func (this *Timer)Run(){ 46 | go func() { 47 | time.Sleep(this.durations) 48 | this.delayCall.Call() 49 | }() 50 | } 51 | 52 | func (this *Timer)GetDurations() time.Duration{ 53 | return this.durations 54 | } 55 | 56 | func (this *Timer)GetFunc() *DelayCall{ 57 | return this.delayCall 58 | } 59 | -------------------------------------------------------------------------------- /fnet/connectionmgr.go: -------------------------------------------------------------------------------- 1 | package fnet 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/viphxin/xingo/iface" 7 | "github.com/viphxin/xingo/logger" 8 | "sync" 9 | ) 10 | 11 | type ConnectionMgr struct { 12 | connections map[uint32]iface.Iconnection 13 | conMrgLock sync.RWMutex 14 | } 15 | 16 | func (this *ConnectionMgr) Add(conn iface.Iconnection) { 17 | this.conMrgLock.Lock() 18 | defer this.conMrgLock.Unlock() 19 | this.connections[conn.GetSessionId()] = conn 20 | logger.Debug(fmt.Sprintf("Total connection: %d", len(this.connections))) 21 | } 22 | 23 | func (this *ConnectionMgr) Remove(conn iface.Iconnection) error { 24 | this.conMrgLock.Lock() 25 | defer this.conMrgLock.Unlock() 26 | _, ok := this.connections[conn.GetSessionId()] 27 | if ok { 28 | delete(this.connections, conn.GetSessionId()) 29 | logger.Info(len(this.connections)) 30 | return nil 31 | } else { 32 | return errors.New("not found!!") 33 | } 34 | 35 | } 36 | 37 | func (this *ConnectionMgr) Get(sid uint32) (iface.Iconnection, error) { 38 | this.conMrgLock.Lock() 39 | defer this.conMrgLock.Unlock() 40 | v, ok := this.connections[sid] 41 | if ok { 42 | delete(this.connections, sid) 43 | return v, nil 44 | } else { 45 | return nil, errors.New("not found!!") 46 | } 47 | } 48 | 49 | func (this *ConnectionMgr) Len() int { 50 | this.conMrgLock.Lock() 51 | defer this.conMrgLock.Unlock() 52 | return len(this.connections) 53 | } 54 | 55 | func NewConnectionMgr() *ConnectionMgr { 56 | return &ConnectionMgr{ 57 | connections: make(map[uint32]iface.Iconnection), 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /utils/tools.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "github.com/viphxin/xingo/logger" 6 | "net/http" 7 | "reflect" 8 | "runtime/debug" 9 | "time" 10 | ) 11 | 12 | func HttpRequestWrap(uri string, targat func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { 13 | return func(response http.ResponseWriter, request *http.Request) { 14 | defer func() { 15 | if err := recover(); err != nil { 16 | debug.PrintStack() 17 | logger.Info("===================http server panic recover===============") 18 | } 19 | }() 20 | st := time.Now() 21 | logger.Debug("User-Agent: ", request.Header["User-Agent"]) 22 | targat(response, request) 23 | logger.Debug(fmt.Sprintf("%s cost total time: %f ms", uri, time.Now().Sub(st).Seconds()*1000)) 24 | } 25 | } 26 | 27 | func ReSettingLog() { 28 | // --------------------------------------------init log start 29 | logger.SetConsole(GlobalObject.SetToConsole) 30 | if GlobalObject.LogFileType == logger.ROLLINGFILE { 31 | logger.SetRollingFile(GlobalObject.LogPath, GlobalObject.LogName, 32 | GlobalObject.MaxLogNum, GlobalObject.MaxFileSize, GlobalObject.LogFileUnit) 33 | } else { 34 | logger.SetRollingDaily(GlobalObject.LogPath, GlobalObject.LogName) 35 | logger.SetLevel(GlobalObject.LogLevel) 36 | } 37 | // --------------------------------------------init log end 38 | } 39 | 40 | func XingoTry(f reflect.Value, args []reflect.Value, handler func(interface{})) { 41 | defer func() { 42 | if err := recover(); err != nil { 43 | logger.Info("-------------panic recover---------------") 44 | if handler != nil { 45 | handler(err) 46 | } 47 | } 48 | }() 49 | f.Call(args) 50 | } -------------------------------------------------------------------------------- /sys_rpc/master_rpc.go: -------------------------------------------------------------------------------- 1 | package sys_rpc 2 | 3 | import ( 4 | "github.com/viphxin/xingo/cluster" 5 | "github.com/viphxin/xingo/clusterserver" 6 | "github.com/viphxin/xingo/logger" 7 | ) 8 | 9 | type MasterRpc struct { 10 | } 11 | 12 | func (this *MasterRpc) TakeProxy(request *cluster.RpcRequest) (response map[string]interface{}) { 13 | response = make(map[string]interface{}, 0) 14 | name := request.Rpcdata.Args[0].(string) 15 | logger.Info("node " + name + " connected to master.") 16 | //加到childs并且绑定链接connetion对象 17 | clusterserver.GlobalMaster.AddNode(name, request.Fconn) 18 | 19 | //返回需要链接的父节点 20 | remotes, err := clusterserver.GlobalMaster.Cconf.GetRemotesByName(name) 21 | if err == nil { 22 | roots := make([]string, 0) 23 | for _, r := range remotes { 24 | if _, ok := clusterserver.GlobalMaster.OnlineNodes[r]; ok { 25 | //父节点在线 26 | roots = append(roots, r) 27 | } 28 | } 29 | response["roots"] = roots 30 | } 31 | //通知当前节点的子节点链接当前节点 32 | for _, child := range clusterserver.GlobalMaster.Childs.GetChilds() { 33 | //遍历所有子节点,观察child节点的父节点是否包含当前节点 34 | remotes, err := clusterserver.GlobalMaster.Cconf.GetRemotesByName(child.GetName()) 35 | if err == nil { 36 | for _, rname := range remotes { 37 | if rname == name { 38 | //包含,需要通知child节点连接当前节点 39 | //rpc notice 40 | child.CallChildNotForResult("RootTakeProxy", name) 41 | break 42 | } 43 | } 44 | } 45 | } 46 | return 47 | } 48 | 49 | //主动通知master 节点掉线 50 | func (this *MasterRpc) ChildOffLine(request *cluster.RpcRequest) { 51 | name := request.Rpcdata.Args[0].(string) 52 | logger.Info("node " + name + " disconnected offline.") 53 | clusterserver.GlobalMaster.CheckChildsAlive(true) 54 | } 55 | -------------------------------------------------------------------------------- /cluster/rpc.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "github.com/viphxin/xingo/iface" 5 | "github.com/viphxin/xingo/logger" 6 | "github.com/viphxin/xingo/utils" 7 | "time" 8 | ) 9 | 10 | type RpcSignal int32 11 | 12 | const ( 13 | REQUEST_NORESULT RpcSignal = iota 14 | REQUEST_FORRESULT 15 | RESPONSE 16 | ) 17 | 18 | type XingoRpc struct { 19 | conn iface.IWriter 20 | asyncResultMgr *AsyncResultMgr 21 | } 22 | 23 | func NewXingoRpc(conn iface.IWriter) *XingoRpc { 24 | return &XingoRpc{ 25 | conn: conn, 26 | asyncResultMgr: AResultGlobalObj, 27 | } 28 | } 29 | 30 | func (this *XingoRpc) CallRpcNotForResult(target string, args ...interface{}) error { 31 | rpcdata := &RpcData{ 32 | MsgType: REQUEST_NORESULT, 33 | Target: target, 34 | Args: args, 35 | } 36 | rpcpackege, err := utils.GlobalObject.RpcCProtoc.GetDataPack().Pack(0, rpcdata) 37 | 38 | if err == nil { 39 | this.conn.Send(rpcpackege) 40 | return nil 41 | } else { 42 | logger.Error(err) 43 | return err 44 | } 45 | } 46 | 47 | func (this *XingoRpc) CallRpcForResult(target string, args ...interface{}) (*RpcData, error) { 48 | asyncR := this.asyncResultMgr.Add() 49 | rpcdata := &RpcData{ 50 | MsgType: REQUEST_FORRESULT, 51 | Key: asyncR.GetKey(), 52 | Target: target, 53 | Args: args, 54 | } 55 | rpcpackege, err := utils.GlobalObject.RpcCProtoc.GetDataPack().Pack(0, rpcdata) 56 | if err == nil { 57 | this.conn.Send(rpcpackege) 58 | resp, err := asyncR.GetResult(2 * time.Second) 59 | if err == nil { 60 | return resp, nil 61 | } else { 62 | //超时了 或者其他原因结果没等到 63 | this.asyncResultMgr.Remove(asyncR.GetKey()) 64 | return nil, err 65 | } 66 | } else { 67 | logger.Error(err) 68 | return nil, err 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /cluster/clusterconf.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "io/ioutil" 7 | "github.com/viphxin/xingo/logger" 8 | ) 9 | 10 | var cfgpath string 11 | 12 | type ClusterServerConf struct { 13 | Name string 14 | Host string 15 | RootPort int 16 | Http []interface{} //[port, staticfile_path] 17 | Https []interface{} //[port, certFile, keyFile, staticfile_path] 18 | NetPort int 19 | DebugPort int //telnet port 20 | Remotes []string 21 | Module string 22 | Log string 23 | } 24 | 25 | type ClusterConf struct { 26 | Master *ClusterServerConf 27 | Servers map[string]*ClusterServerConf 28 | } 29 | 30 | func NewClusterConf(path string) (*ClusterConf, error) { 31 | cconf := &ClusterConf{} 32 | //集群服务器配置信息 33 | data, err := ioutil.ReadFile(path) 34 | if err != nil { 35 | panic(err) 36 | } 37 | err = json.Unmarshal(data, cconf) 38 | if err != nil { 39 | panic(err) 40 | } 41 | cfgpath = path 42 | return cconf, nil 43 | } 44 | 45 | /* 46 | 获取当前节点的父节点 47 | */ 48 | func (this *ClusterConf) GetRemotesByName(name string) ([]string, error) { 49 | server, ok := this.Servers[name] 50 | if ok { 51 | return server.Remotes, nil 52 | } else { 53 | return nil, errors.New("no server found!!!") 54 | } 55 | } 56 | 57 | /* 58 | 获取当前节点的子节点 59 | */ 60 | func (this *ClusterConf) GetChildsByName(name string) []string { 61 | names := make([]string, 0) 62 | for sername, ser := range this.Servers { 63 | for _, rname := range ser.Remotes { 64 | if rname == name { 65 | names = append(names, sername) 66 | break 67 | } 68 | } 69 | } 70 | return names 71 | } 72 | 73 | func (this *ClusterConf)Reload(){ 74 | //集群服务器配置信息 75 | data, err := ioutil.ReadFile(cfgpath) 76 | if err != nil { 77 | logger.Error(err) 78 | } 79 | err = json.Unmarshal(data, this) 80 | if err != nil { 81 | logger.Error(err) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /telnetcmd/pprofcpucommand.go: -------------------------------------------------------------------------------- 1 | package telnetcmd 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "runtime/pprof" 7 | ) 8 | 9 | type PprofCpuCommand struct { 10 | buffer *bytes.Buffer 11 | profilingBuffer *bytes.Buffer 12 | } 13 | 14 | func NewPprofCpuCommand() *PprofCpuCommand{ 15 | return &PprofCpuCommand{new(bytes.Buffer), new(bytes.Buffer)} 16 | } 17 | func (this *PprofCpuCommand)Name()string{ 18 | return "pprofcpu" 19 | } 20 | 21 | func (this *PprofCpuCommand)Help()string{ 22 | return fmt.Sprintf("pprofcpu:\r\n" + 23 | "----------- start: 开始收集服务cpu占用信息\r\n" + 24 | "----------- stop: 结束数据收集\r\n" + 25 | "----------- profiling: 分析(goroutine, heap, thread, block)") 26 | } 27 | 28 | func (this *PprofCpuCommand)Run(args []string) string{ 29 | if len(args) == 0{ 30 | return this.Help() 31 | }else{ 32 | switch args[0] { 33 | case "start": 34 | pprof.StopCPUProfile() 35 | this.buffer.Reset() 36 | err := pprof.StartCPUProfile(this.buffer) 37 | if err != nil{ 38 | return fmt.Sprintf("pprofcpu start error: %s.", err) 39 | }else{ 40 | return "pprofcpu start successful. please wait." 41 | } 42 | case "stop": 43 | pprof.StopCPUProfile() 44 | return this.buffer.String() 45 | case "profiling": 46 | var p *pprof.Profile 47 | switch args[1] { 48 | case "goroutine": 49 | p = pprof.Lookup("goroutine") 50 | case "heap": 51 | p = pprof.Lookup("heap") 52 | case "thread": 53 | p = pprof.Lookup("threadcreate") 54 | case "block": 55 | p = pprof.Lookup("block") 56 | default: 57 | return this.Help() 58 | } 59 | this.profilingBuffer.Reset() 60 | err := p.WriteTo(this.profilingBuffer, 1) 61 | if err != nil { 62 | return fmt.Sprintf("pprofcpu profiling error: %s.", err) 63 | }else{ 64 | return this.profilingBuffer.String() + "\r\n" 65 | } 66 | default: 67 | return "not found command." 68 | 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /fnet/datapack.go: -------------------------------------------------------------------------------- 1 | package fnet 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "github.com/golang/protobuf/proto" 8 | "github.com/viphxin/xingo/logger" 9 | "github.com/viphxin/xingo/utils" 10 | ) 11 | 12 | type PkgData struct { 13 | Len uint32 14 | MsgId uint32 15 | Data []byte 16 | } 17 | 18 | type PBDataPack struct{} 19 | 20 | func NewPBDataPack() *PBDataPack { 21 | return &PBDataPack{} 22 | } 23 | 24 | func (this *PBDataPack) GetHeadLen() int32 { 25 | return 8 26 | } 27 | 28 | func (this *PBDataPack) Unpack(headdata []byte) (interface{}, error) { 29 | headbuf := bytes.NewReader(headdata) 30 | 31 | head := &PkgData{} 32 | 33 | // 读取Len 34 | if err := binary.Read(headbuf, binary.LittleEndian, &head.Len); err != nil { 35 | return nil, err 36 | } 37 | 38 | // 读取MsgId 39 | if err := binary.Read(headbuf, binary.LittleEndian, &head.MsgId); err != nil { 40 | return nil, err 41 | } 42 | 43 | // 封包太大 44 | if (utils.GlobalObject.MaxPacketSize > 0 && head.Len > utils.GlobalObject.MaxPacketSize) || 45 | (utils.GlobalObject.MaxPacketSize == 0 && head.Len > MaxPacketSize) { 46 | return nil, packageTooBig 47 | } 48 | 49 | return head, nil 50 | } 51 | 52 | func (this *PBDataPack) Pack(msgId uint32, data interface{}) (out []byte, err error) { 53 | outbuff := bytes.NewBuffer([]byte{}) 54 | // 进行编码 55 | dataBytes := []byte{} 56 | if data != nil { 57 | dataBytes, err = proto.Marshal(data.(proto.Message)) 58 | } 59 | 60 | if err != nil { 61 | logger.Error(fmt.Sprintf("marshaling error: %s", err)) 62 | } 63 | // 写Len 64 | if err = binary.Write(outbuff, binary.LittleEndian, uint32(len(dataBytes))); err != nil { 65 | return 66 | } 67 | // 写MsgId 68 | if err = binary.Write(outbuff, binary.LittleEndian, msgId); err != nil { 69 | return 70 | } 71 | 72 | //all pkg data 73 | if err = binary.Write(outbuff, binary.LittleEndian, dataBytes); err != nil { 74 | return 75 | } 76 | 77 | out = outbuff.Bytes() 78 | return 79 | 80 | } 81 | -------------------------------------------------------------------------------- /sys_rpc/child_rpc.go: -------------------------------------------------------------------------------- 1 | package sys_rpc 2 | 3 | import ( 4 | "fmt" 5 | "github.com/viphxin/xingo/cluster" 6 | "github.com/viphxin/xingo/clusterserver" 7 | "github.com/viphxin/xingo/logger" 8 | "time" 9 | "github.com/viphxin/xingo/utils" 10 | "os" 11 | ) 12 | 13 | type ChildRpc struct { 14 | } 15 | 16 | /* 17 | master 通知父节点上线, 收到通知的子节点需要链接对应父节点 18 | */ 19 | func (this *ChildRpc) RootTakeProxy(request *cluster.RpcRequest) { 20 | rname := request.Rpcdata.Args[0].(string) 21 | logger.Info(fmt.Sprintf("root node %s online. connecting...", rname)) 22 | clusterserver.GlobalClusterServer.ConnectToRemote(rname) 23 | } 24 | 25 | /* 26 | 关闭节点信号 27 | */ 28 | func (this *ChildRpc) CloseServer(request *cluster.RpcRequest){ 29 | delay := request.Rpcdata.Args[0].(int) 30 | logger.Warn("server close kickdown.", delay, "second...") 31 | time.Sleep(time.Duration(delay)*time.Second) 32 | utils.GlobalObject.ProcessSignalChan <- os.Kill 33 | } 34 | 35 | /* 36 | 重新加载配置文件 37 | */ 38 | func (this *ChildRpc) ReloadConfig(request *cluster.RpcRequest){ 39 | delay := request.Rpcdata.Args[0].(int) 40 | logger.Warn("server ReloadConfig kickdown.", delay, "second...") 41 | time.Sleep(time.Duration(delay)*time.Second) 42 | clusterserver.GlobalClusterServer.Cconf.Reload() 43 | utils.GlobalObject.Reload() 44 | logger.Info("reload config.") 45 | } 46 | 47 | 48 | /* 49 | 检查节点是否下线 50 | */ 51 | func (this *ChildRpc) CheckAlive(request *cluster.RpcRequest)(response map[string]interface{}){ 52 | logger.Debug("CheckAlive!") 53 | response = make(map[string]interface{}) 54 | response["name"] = clusterserver.GlobalClusterServer.Name 55 | return 56 | } 57 | 58 | /* 59 | 通知节点掉线(父节点或子节点) 60 | */ 61 | func (this *ChildRpc)NodeDownNtf(request *cluster.RpcRequest) { 62 | isChild := request.Rpcdata.Args[0].(bool) 63 | nodeName := request.Rpcdata.Args[1].(string) 64 | logger.Debug(fmt.Sprintf("node %s down ntf.", nodeName)) 65 | if isChild { 66 | clusterserver.GlobalClusterServer.RemoveChild(nodeName) 67 | }else{ 68 | clusterserver.GlobalClusterServer.RemoveRemote(nodeName) 69 | } 70 | } -------------------------------------------------------------------------------- /cluster/cmdinterpreter.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "github.com/viphxin/xingo/iface" 5 | "github.com/viphxin/xingo/logger" 6 | "strings" 7 | "fmt" 8 | ) 9 | 10 | var ( 11 | QUIT_CMD = [3]string{"quit", "q", "exit"} 12 | ) 13 | 14 | type CommandInterpreter struct { 15 | commands map[string]iface.ICommand 16 | } 17 | 18 | func NewCommandInterpreter() *CommandInterpreter{ 19 | interpreter := &CommandInterpreter{make(map[string]iface.ICommand)} 20 | return interpreter 21 | } 22 | 23 | func (this *CommandInterpreter)AddCommand(cmd iface.ICommand){ 24 | this.commands[cmd.Name()] = cmd 25 | logger.Debug("add command ", cmd.Name()) 26 | } 27 | 28 | func (this *CommandInterpreter)preExcute(rawCmdExp string) string{ 29 | return strings.ToLower(strings.TrimSpace(rawCmdExp)) 30 | } 31 | 32 | func (this *CommandInterpreter)IsQuitCmd(rawCmdExp string) bool{ 33 | cmdExp := this.preExcute(rawCmdExp) 34 | for _, cmd := range QUIT_CMD{ 35 | if cmd == cmdExp{ 36 | return true 37 | } 38 | } 39 | return false 40 | } 41 | 42 | func (this *CommandInterpreter)help() string{ 43 | helpStr := "有关某个命令的详细信息,请键入 help 命令名" 44 | for _, v := range this.commands{ 45 | helpStr = fmt.Sprintf("%s\r\n%s", helpStr, v.Help()) 46 | } 47 | return helpStr 48 | } 49 | 50 | func (this *CommandInterpreter)Excute(rawCmdExp string) string{ 51 | defer func()string{ 52 | if err := recover(); err != nil{ 53 | logger.Error("invalid rawCmdExp: ", rawCmdExp) 54 | return "invalid rawCmdExp: " + rawCmdExp 55 | } 56 | return "Unkown ERROR!!!" 57 | }() 58 | if rawCmdExp == ""{ 59 | return "" 60 | } 61 | rawCmdExps := strings.Split(rawCmdExp, " ") 62 | if len(rawCmdExps) == 0{ 63 | return "" 64 | } 65 | cmdExps := make([]string, 0) 66 | for _, cmd := range rawCmdExps{ 67 | cmdExps = append(cmdExps, this.preExcute(cmd)) 68 | } 69 | 70 | if command, ok := this.commands[cmdExps[0]]; ok{ 71 | return command.Run(cmdExps[1:]) 72 | }else{ 73 | if cmdExps[0] == "help"{ 74 | return this.help() 75 | }else{ 76 | return "command not found." 77 | } 78 | } 79 | } 80 | 81 | -------------------------------------------------------------------------------- /xingo.go: -------------------------------------------------------------------------------- 1 | package xingo 2 | 3 | import ( 4 | _ "github.com/viphxin/xingo/fnet" 5 | _ "github.com/viphxin/xingo/timer" 6 | "github.com/viphxin/xingo/telnetcmd" 7 | "github.com/viphxin/xingo/clusterserver" 8 | "github.com/viphxin/xingo/sys_rpc" 9 | "github.com/viphxin/xingo/utils" 10 | "github.com/viphxin/xingo/fserver" 11 | "github.com/viphxin/xingo/cluster" 12 | "github.com/viphxin/xingo/logger" 13 | "fmt" 14 | "github.com/viphxin/xingo/iface" 15 | ) 16 | 17 | func NewXingoTcpServer() iface.Iserver{ 18 | //do something 19 | //debugport 是否开放 20 | if utils.GlobalObject.DebugPort > 0{ 21 | if utils.GlobalObject.Host != ""{ 22 | fserver.NewTcpServer("telnet_server", "tcp4", utils.GlobalObject.Host, 23 | utils.GlobalObject.DebugPort, 100, cluster.NewTelnetProtocol()).Start() 24 | }else{ 25 | fserver.NewTcpServer("telnet_server", "tcp4", "127.0.0.1", 26 | utils.GlobalObject.DebugPort, 100, cluster.NewTelnetProtocol()).Start() 27 | } 28 | logger.Debug(fmt.Sprintf("telnet tool start: %s:%d.", utils.GlobalObject.Host, utils.GlobalObject.DebugPort)) 29 | 30 | } 31 | 32 | //add command 33 | if utils.GlobalObject.CmdInterpreter != nil{ 34 | utils.GlobalObject.CmdInterpreter.AddCommand(telnetcmd.NewPprofCpuCommand()) 35 | } 36 | 37 | s := fserver.NewServer() 38 | return s 39 | } 40 | 41 | func NewXingoMaster(cfg string) *clusterserver.Master{ 42 | s := clusterserver.NewMaster(cfg) 43 | //add rpc 44 | s.AddRpcRouter(&sys_rpc.MasterRpc{}) 45 | //add command 46 | if utils.GlobalObject.CmdInterpreter != nil{ 47 | utils.GlobalObject.CmdInterpreter.AddCommand(telnetcmd.NewPprofCpuCommand()) 48 | utils.GlobalObject.CmdInterpreter.AddCommand(telnetcmd.NewCloseServerCommand()) 49 | utils.GlobalObject.CmdInterpreter.AddCommand(telnetcmd.NewReloadCfgCommand()) 50 | } 51 | return s 52 | } 53 | 54 | func NewXingoCluterServer(nodename, cfg string) *clusterserver.ClusterServer{ 55 | s := clusterserver.NewClusterServer(nodename,cfg) 56 | //add rpc 57 | s.AddRpcRouter(&sys_rpc.ChildRpc{}) 58 | s.AddRpcRouter(&sys_rpc.RootRpc{}) 59 | //add cmd 60 | if utils.GlobalObject.CmdInterpreter != nil{ 61 | utils.GlobalObject.CmdInterpreter.AddCommand(telnetcmd.NewPprofCpuCommand()) 62 | } 63 | return s 64 | } 65 | -------------------------------------------------------------------------------- /cluster/asyncresult.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/viphxin/xingo/logger" 7 | "github.com/viphxin/xingo/utils" 8 | _ "os" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | type AsyncResult struct { 14 | key string 15 | result chan *RpcData 16 | } 17 | 18 | //func GenUUID() string{ 19 | // f, _ := os.OpenFile("/dev/urandom", os.O_RDONLY, 0) 20 | // b := make([]byte, 16) 21 | // f.Read(b) 22 | // f.Close() 23 | // return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) 24 | //} 25 | 26 | var AResultGlobalObj *AsyncResultMgr = NewAsyncResultMgr() 27 | 28 | func NewAsyncResult(key string) *AsyncResult { 29 | return &AsyncResult{ 30 | key: key, 31 | result: make(chan *RpcData, 1), 32 | } 33 | } 34 | 35 | func (this *AsyncResult) GetKey() string { 36 | return this.key 37 | } 38 | 39 | func (this *AsyncResult) SetResult(data *RpcData) { 40 | this.result <- data 41 | } 42 | 43 | func (this *AsyncResult) GetResult(timeout time.Duration) (*RpcData, error) { 44 | select { 45 | case <-time.After(timeout): 46 | logger.Error(fmt.Sprintf("GetResult AsyncResult: timeout %s", this.key)) 47 | close(this.result) 48 | return &RpcData{}, errors.New(fmt.Sprintf("GetResult AsyncResult: timeout %s", this.key)) 49 | case result := <-this.result: 50 | return result, nil 51 | } 52 | return &RpcData{}, errors.New("GetResult AsyncResult error. reason: no") 53 | } 54 | 55 | type AsyncResultMgr struct { 56 | idGen *utils.UUIDGenerator 57 | results map[string]*AsyncResult 58 | sync.RWMutex 59 | } 60 | 61 | func NewAsyncResultMgr() *AsyncResultMgr { 62 | return &AsyncResultMgr{ 63 | results: make(map[string]*AsyncResult, 0), 64 | idGen: utils.NewUUIDGenerator("async_result_"), 65 | } 66 | } 67 | 68 | func (this *AsyncResultMgr) Add() *AsyncResult { 69 | this.Lock() 70 | defer this.Unlock() 71 | 72 | r := NewAsyncResult(this.idGen.Get()) 73 | this.results[r.GetKey()] = r 74 | return r 75 | } 76 | 77 | func (this *AsyncResultMgr) Remove(key string) { 78 | this.Lock() 79 | defer this.Unlock() 80 | 81 | delete(this.results, key) 82 | } 83 | 84 | func (this *AsyncResultMgr) GetAsyncResult(key string) (*AsyncResult, error) { 85 | this.RLock() 86 | defer this.RUnlock() 87 | 88 | r, ok := this.results[key] 89 | if ok { 90 | return r, nil 91 | } else { 92 | return nil, errors.New("not found AsyncResult") 93 | } 94 | } 95 | 96 | func (this *AsyncResultMgr) FillAsyncResult(key string, data *RpcData) error { 97 | r, err := this.GetAsyncResult(key) 98 | if err == nil { 99 | this.Remove(key) 100 | r.SetResult(data) 101 | return nil 102 | } else { 103 | return err 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /cluster/child.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/viphxin/xingo/iface" 7 | "github.com/viphxin/xingo/logger" 8 | "math/rand" 9 | "strings" 10 | "sync" 11 | ) 12 | 13 | type Child struct { 14 | name string 15 | rpc *XingoRpc 16 | } 17 | 18 | func NewChild(name string, conn iface.IWriter) *Child { 19 | return &Child{ 20 | name: name, 21 | rpc: NewXingoRpc(conn), 22 | } 23 | } 24 | 25 | func (this *Child) GetName() string { 26 | return this.name 27 | } 28 | 29 | func (this *Child) CallChildNotForResult(target string, args ...interface{}) error { 30 | return this.rpc.CallRpcNotForResult(target, args...) 31 | } 32 | 33 | func (this *Child) CallChildForResult(target string, args ...interface{}) (*RpcData, error) { 34 | return this.rpc.CallRpcForResult(target, args...) 35 | } 36 | 37 | type ChildMgr struct { 38 | childs map[string]*Child 39 | sync.RWMutex 40 | } 41 | 42 | func NewChildMgr() *ChildMgr { 43 | return &ChildMgr{ 44 | childs: make(map[string]*Child, 0), 45 | } 46 | } 47 | 48 | func (this *ChildMgr) AddChild(name string, conn iface.IWriter) { 49 | this.Lock() 50 | defer this.Unlock() 51 | 52 | this.childs[name] = NewChild(name, conn) 53 | logger.Debug(fmt.Sprintf("child %s connected.", name)) 54 | } 55 | 56 | func (this *ChildMgr) RemoveChild(name string) { 57 | this.Lock() 58 | defer this.Unlock() 59 | 60 | delete(this.childs, name) 61 | logger.Debug(fmt.Sprintf("child %s lostconnection.", name)) 62 | } 63 | 64 | func (this *ChildMgr) GetChild(name string) (*Child, error) { 65 | this.RLock() 66 | defer this.RUnlock() 67 | 68 | child, ok := this.childs[name] 69 | if ok { 70 | return child, nil 71 | } else { 72 | return nil, errors.New(fmt.Sprintf("no child named %s", name)) 73 | } 74 | } 75 | 76 | func (this *ChildMgr) GetChildsByPrefix(namePrefix string) []*Child { 77 | this.RLock() 78 | defer this.RUnlock() 79 | 80 | childs := make([]*Child, 0) 81 | for k, v := range this.childs { 82 | if strings.HasPrefix(k, namePrefix) { 83 | childs = append(childs, v) 84 | } 85 | } 86 | return childs 87 | } 88 | 89 | func (this *ChildMgr) GetChilds() []*Child { 90 | this.RLock() 91 | defer this.RUnlock() 92 | 93 | childs := make([]*Child, 0) 94 | for _, v := range this.childs { 95 | childs = append(childs, v) 96 | } 97 | return childs 98 | } 99 | 100 | func (this *ChildMgr) GetRandomChild(namesuffix string) *Child { 101 | childs := make([]*Child, 0) 102 | if namesuffix != "" { 103 | //一类 104 | childs = this.GetChildsByPrefix(namesuffix) 105 | } else { 106 | //所有 107 | childs = this.GetChilds() 108 | } 109 | if len(childs) > 0 { 110 | pos := rand.Intn(len(childs)) 111 | return childs[pos] 112 | } 113 | return nil 114 | } 115 | -------------------------------------------------------------------------------- /cluster/rpcpack.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | "github.com/viphxin/xingo/fnet" 9 | "github.com/viphxin/xingo/iface" 10 | "encoding/gob" 11 | "github.com/viphxin/xingo/logger" 12 | ) 13 | 14 | type RpcData struct { 15 | MsgType RpcSignal `json:"msgtype"` 16 | Key string `json:"key,omitempty"` 17 | Target string `json:"target,omitempty"` 18 | Args []interface{} `json:"args,omitempty"` 19 | Result map[string]interface{} `json:"result,omitempty"` 20 | } 21 | 22 | type RpcPackege struct { 23 | Len int32 24 | Data []byte 25 | } 26 | 27 | type RpcRequest struct { 28 | Fconn iface.IWriter 29 | Rpcdata *RpcData 30 | } 31 | 32 | type RpcDataPack struct{} 33 | 34 | func NewRpcDataPack() *RpcDataPack { 35 | return &RpcDataPack{} 36 | } 37 | 38 | func (this *RpcDataPack) GetHeadLen() int32 { 39 | return 4 40 | } 41 | 42 | func (this *RpcDataPack) Unpack(headdata []byte) (interface{}, error) { 43 | headbuf := bytes.NewReader(headdata) 44 | 45 | rp := &RpcPackege{} 46 | 47 | // 读取Len 48 | if err := binary.Read(headbuf, binary.LittleEndian, &rp.Len); err != nil { 49 | return nil, err 50 | } 51 | 52 | // 封包太大 53 | if rp.Len > fnet.MaxPacketSize { 54 | return nil, errors.New("rpc packege too big!!!") 55 | } 56 | 57 | return rp, nil 58 | } 59 | 60 | //func (this *RpcDataPack) Pack(msgId uint32, pkg interface{}) (out []byte, err error) { 61 | // outbuff := bytes.NewBuffer([]byte{}) 62 | // // 进行编码 63 | // dataBytes := []byte{} 64 | // data := pkg.(*RpcData) 65 | // if data != nil { 66 | // dataBytes, err = json.Marshal(data) 67 | // } 68 | // 69 | // if err != nil { 70 | // fmt.Println(fmt.Sprintf("json marshaling error: %s", err)) 71 | // } 72 | // // 写Len 73 | // if err = binary.Write(outbuff, binary.LittleEndian, uint32(len(dataBytes))); err != nil { 74 | // return 75 | // } 76 | // 77 | // //all pkg data 78 | // if err = binary.Write(outbuff, binary.LittleEndian, dataBytes); err != nil { 79 | // return 80 | // } 81 | // 82 | // out = outbuff.Bytes() 83 | // return 84 | // 85 | //} 86 | 87 | func (this *RpcDataPack) Pack(msgId uint32, pkg interface{}) (out []byte, err error) { 88 | outbuff := bytes.NewBuffer([]byte{}) 89 | // 进行编码 90 | databuff := bytes.NewBuffer([]byte{}) 91 | data := pkg.(*RpcData) 92 | if data != nil { 93 | enc := gob.NewEncoder(databuff) 94 | err = enc.Encode(data) 95 | } 96 | 97 | if err != nil { 98 | logger.Error(fmt.Sprintf("rpcpack gob marshaling error: %s", err)) 99 | return 100 | } 101 | // 写Len 102 | if err = binary.Write(outbuff, binary.LittleEndian, uint32(databuff.Len())); err != nil { 103 | return 104 | } 105 | 106 | //all pkg data 107 | if err = binary.Write(outbuff, binary.LittleEndian, databuff.Bytes()); err != nil { 108 | return 109 | } 110 | 111 | out = outbuff.Bytes() 112 | return 113 | 114 | } -------------------------------------------------------------------------------- /timer/safetimer.go: -------------------------------------------------------------------------------- 1 | package timer 2 | 3 | import ( 4 | "github.com/viphxin/xingo/logger" 5 | "sync" 6 | "time" 7 | "math" 8 | ) 9 | 10 | /* 11 | 协程安全的timer 12 | */ 13 | const ( 14 | //默认安全时间调度器的容量 15 | TIMERLEN = 2048 16 | //默认最大误差值100毫秒 17 | ERRORMAX = 100 18 | //默认最大触发队列缓冲大小 19 | TRIGGERMAX = 2048 20 | //默认hashwheel分级 21 | LEVEL = 12 22 | ) 23 | 24 | func UnixTS() int64 { 25 | return time.Now().UnixNano() / 1e6 26 | } 27 | 28 | type ParamNull struct {} 29 | 30 | type SafeTimer struct { 31 | //延迟调用的函数 32 | delayCall *DelayCall 33 | //调用的时间:单位毫秒 34 | unixts int64 35 | } 36 | 37 | func NewSafeTimer(delay int64, delayCall *DelayCall) *SafeTimer { 38 | unixts := UnixTS() 39 | if delay > 0 { 40 | unixts += delay 41 | } 42 | return &SafeTimer{ 43 | delayCall: delayCall, 44 | unixts: unixts, 45 | } 46 | } 47 | 48 | type SafeTimerScheduel struct { 49 | hashwheel *HashWheel 50 | idGen uint32 51 | triggerChan chan *DelayCall 52 | sync.RWMutex 53 | } 54 | 55 | func NewSafeTimerScheduel() *SafeTimerScheduel { 56 | scheduel := &SafeTimerScheduel{ 57 | hashwheel: NewHashWheel("wheel_hours", LEVEL, 3600*1e3, TIMERLEN), 58 | idGen: 0, 59 | triggerChan: make(chan *DelayCall, TRIGGERMAX), 60 | } 61 | 62 | //minute wheel 63 | minuteWheel := NewHashWheel("wheel_minutes", LEVEL, 60*1e3, TIMERLEN) 64 | //second wheel 65 | secondWheel := NewHashWheel("wheel_seconds", LEVEL, 1*1e3, TIMERLEN) 66 | minuteWheel.AddNext(secondWheel) 67 | scheduel.hashwheel.AddNext(minuteWheel) 68 | 69 | go scheduel.StartScheduelLoop() 70 | return scheduel 71 | } 72 | 73 | func (this *SafeTimerScheduel) GetTriggerChannel() chan *DelayCall { 74 | return this.triggerChan 75 | } 76 | 77 | func (this *SafeTimerScheduel) CreateTimer(delay int64, f func(v ...interface{}), args []interface{}) (uint32, error) { 78 | this.Lock() 79 | defer this.Unlock() 80 | 81 | this.idGen += 1 82 | err := this.hashwheel.Add2WheelChain(this.idGen, 83 | NewSafeTimer(delay, &DelayCall{ 84 | f: f, 85 | args: args, 86 | })) 87 | if err != nil{ 88 | return 0, err 89 | }else{ 90 | return this.idGen, nil 91 | } 92 | } 93 | 94 | func (this *SafeTimerScheduel) CancelTimer(timerId uint32) { 95 | this.hashwheel.RemoveFromWheelChain(timerId) 96 | } 97 | 98 | func (this *SafeTimerScheduel) StartScheduelLoop() { 99 | logger.Info("xingo safe timer scheduelloop runing.") 100 | for { 101 | triggerList := this.hashwheel.GetTriggerWithIn(ERRORMAX) 102 | //trigger 103 | for _, v := range triggerList { 104 | //logger.Debug("want call: ", v.unixts, ".real call: ", UnixTS(), ".ErrorMS: ", UnixTS()-v.unixts) 105 | if math.Abs(float64(UnixTS()-v.unixts)) > float64(ERRORMAX){ 106 | logger.Error("want call: ", v.unixts, ".real call: ", UnixTS(), ".ErrorMS: ", UnixTS()-v.unixts) 107 | } 108 | this.triggerChan <- v.delayCall 109 | } 110 | 111 | //wait for next loop 112 | time.Sleep(ERRORMAX/2*time.Millisecond) 113 | } 114 | } -------------------------------------------------------------------------------- /cluster/telnetprotocol.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "github.com/viphxin/xingo/iface" 5 | "github.com/viphxin/xingo/logger" 6 | "fmt" 7 | "time" 8 | "bufio" 9 | "strings" 10 | "github.com/viphxin/xingo/utils" 11 | ) 12 | /* 13 | debug tool protocol 14 | */ 15 | 16 | type TelnetProtocol struct {} 17 | 18 | func NewTelnetProtocol() *TelnetProtocol { 19 | if utils.GlobalObject.CmdInterpreter == nil{ 20 | utils.GlobalObject.CmdInterpreter = NewCommandInterpreter() 21 | } 22 | return &TelnetProtocol{} 23 | } 24 | 25 | func (this *TelnetProtocol) GetMsgHandle() iface.Imsghandle { 26 | return nil 27 | } 28 | func (this *TelnetProtocol) GetDataPack() iface.Idatapack { 29 | return nil 30 | } 31 | 32 | func (this *TelnetProtocol) AddRpcRouter(router interface{}) { 33 | 34 | } 35 | 36 | func (this *TelnetProtocol) InitWorker(poolsize int32) { 37 | 38 | } 39 | 40 | func (this *TelnetProtocol)isWriteListIP(ip string) bool{ 41 | for _, wip := range utils.GlobalObject.WriteList{ 42 | if strings.EqualFold(ip, wip){ 43 | return true 44 | } 45 | } 46 | return false 47 | } 48 | 49 | func (this *TelnetProtocol)getConnectionAddr(fconn iface.Iconnection)[]string{ 50 | return strings.Split(fconn.RemoteAddr().String(), ":") 51 | } 52 | 53 | func (this *TelnetProtocol) OnConnectionMade(fconn iface.Iconnection) { 54 | logger.Info(fmt.Sprintf("client ID: %d connected. IP Address: %s", fconn.GetSessionId(), fconn.RemoteAddr())) 55 | addr := this.getConnectionAddr(fconn) 56 | if !this.isWriteListIP(addr[0]){ 57 | logger.Error("invald IP: ", addr[0]) 58 | fconn.LostConnection() 59 | } 60 | } 61 | 62 | func (this *TelnetProtocol) OnConnectionLost(fconn iface.Iconnection) { 63 | logger.Info(fmt.Sprintf("client ID: %d disconnected. IP Address: %s", fconn.GetSessionId(), fconn.RemoteAddr())) 64 | } 65 | 66 | func (this *TelnetProtocol) StartReadThread(fconn iface.Iconnection) { 67 | logger.Info("start receive data from telnet socket...") 68 | fconn.GetConnection().Write([]byte(fmt.Sprintf("-------welcome to xingo telnet tool(node: %s)---------\r\n", utils.GlobalObject.Name))) 69 | for { 70 | if err := fconn.GetConnection().SetReadDeadline(time.Now().Add(time.Minute*3)); err != nil { 71 | logger.Error("telnet connection SetReadDeadline error: ", err) 72 | fconn.LostConnection() 73 | break 74 | } 75 | line, err := bufio.NewReader(fconn.GetConnection()).ReadString('\n') 76 | if err != nil { 77 | logger.Error("telnet connection read line error: ", err) 78 | fconn.Stop() 79 | break 80 | } 81 | line = strings.TrimSuffix(line[:len(line)-1], "\r") 82 | logger.Info(fmt.Sprintf("xingo telnet tool received: %s. ip: %s", line, this.getConnectionAddr(fconn)[0])) 83 | if utils.GlobalObject.CmdInterpreter.IsQuitCmd(line){ 84 | logger.Error("telnet exit ") 85 | fconn.LostConnection() 86 | break 87 | }else{ 88 | ack := utils.GlobalObject.CmdInterpreter.Excute(line) 89 | fconn.GetConnection().Write([]byte(ack)) 90 | } 91 | if err := fconn.GetConnection().SetReadDeadline(time.Time{}); err != nil { 92 | logger.Error("telnet connection SetReadDeadline error: ", err) 93 | fconn.LostConnection() 94 | break 95 | } 96 | 97 | } 98 | } 99 | 100 | -------------------------------------------------------------------------------- /telnetcmd/mastercommand.go: -------------------------------------------------------------------------------- 1 | package telnetcmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/viphxin/xingo/clusterserver" 6 | "strconv" 7 | ) 8 | 9 | type CloseServerCommand struct { 10 | } 11 | 12 | func NewCloseServerCommand() *CloseServerCommand{ 13 | return &CloseServerCommand{} 14 | } 15 | func (this *CloseServerCommand)Name()string{ 16 | return "closeserver" 17 | } 18 | 19 | func (this *CloseServerCommand)Help()string{ 20 | return fmt.Sprintf("closeserver:\r\n" + 21 | "----------- all delay: 延迟delay秒时间关闭所有子节点\r\n" + 22 | "----------- node name delay: 延迟delay秒时间关闭指定节点\r\n") 23 | } 24 | 25 | func (this *CloseServerCommand)Run(args []string) string{ 26 | if len(args) == 0{ 27 | return this.Help() 28 | }else{ 29 | switch args[0] { 30 | case "all": 31 | for _, child := range clusterserver.GlobalMaster.Childs.GetChilds() { 32 | if len(args) > 1{ 33 | if v, err := strconv.ParseInt(args[1], 10, 64); err == nil{ 34 | child.CallChildNotForResult("CloseServer", int(v)) 35 | }else{ 36 | child.CallChildNotForResult("CloseServer", int(0)) 37 | } 38 | }else{ 39 | child.CallChildNotForResult("CloseServer", int(0)) 40 | } 41 | } 42 | default: 43 | child, err := clusterserver.GlobalMaster.Childs.GetChild(args[0]) 44 | if err != nil{ 45 | return fmt.Sprintf("no sush node: %s.", args[0]) 46 | }else{ 47 | if len(args) > 1{ 48 | if v, err := strconv.ParseInt(args[1], 10, 64); err == nil{ 49 | child.CallChildNotForResult("CloseServer", int(v)) 50 | }else{ 51 | child.CallChildNotForResult("CloseServer", int(0)) 52 | } 53 | }else{ 54 | child.CallChildNotForResult("CloseServer", int(0)) 55 | } 56 | } 57 | } 58 | } 59 | return "OK" 60 | } 61 | 62 | type ReloadCfgCommand struct { 63 | } 64 | 65 | func NewReloadCfgCommand() *ReloadCfgCommand{ 66 | return &ReloadCfgCommand{} 67 | } 68 | func (this *ReloadCfgCommand)Name()string{ 69 | return "reloadcfg" 70 | } 71 | 72 | func (this *ReloadCfgCommand)Help()string{ 73 | return fmt.Sprintf("reloadcfg:\r\n" + 74 | "----------- all delay: 延迟delay秒时间重新加载所有节点的配置文件\r\n" + 75 | "----------- node name delay: 延迟delay秒时间重新加载指定节点\r\n") 76 | } 77 | 78 | func (this *ReloadCfgCommand)Run(args []string) string{ 79 | if len(args) == 0{ 80 | return this.Help() 81 | }else{ 82 | switch args[0] { 83 | case "all": 84 | for _, child := range clusterserver.GlobalMaster.Childs.GetChilds() { 85 | if len(args) > 1{ 86 | if v, err := strconv.ParseInt(args[1], 10, 64); err == nil{ 87 | child.CallChildNotForResult("ReloadConfig", int(v)) 88 | }else{ 89 | child.CallChildNotForResult("ReloadConfig", int(0)) 90 | } 91 | }else{ 92 | child.CallChildNotForResult("ReloadConfig", int(0)) 93 | } 94 | } 95 | default: 96 | child, err := clusterserver.GlobalMaster.Childs.GetChild(args[0]) 97 | if err != nil{ 98 | return fmt.Sprintf("no sush node: %s.", args[0]) 99 | }else{ 100 | if len(args) > 1{ 101 | if v, err := strconv.ParseInt(args[1], 10, 64); err == nil{ 102 | child.CallChildNotForResult("ReloadConfig", int(v)) 103 | }else{ 104 | child.CallChildNotForResult("ReloadConfig", int(0)) 105 | } 106 | }else{ 107 | child.CallChildNotForResult("ReloadConfig", int(0)) 108 | } 109 | } 110 | } 111 | } 112 | return "OK" 113 | } 114 | -------------------------------------------------------------------------------- /fnet/tcpclient.go: -------------------------------------------------------------------------------- 1 | package fnet 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/viphxin/xingo/iface" 7 | "github.com/viphxin/xingo/logger" 8 | "net" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | const ( 14 | MAX_RETRY = 1024 //父节点掉线最大重连次数 15 | RETRY_INTERVAL = 60 //重连间隔60s 16 | ) 17 | 18 | type TcpClient struct { 19 | conn *net.TCPConn 20 | addr *net.TCPAddr 21 | protoc iface.IClientProtocol 22 | PropertyBag map[string]interface{} 23 | reconnCB func(iface.Iclient) 24 | maxRetry int 25 | retryInterval int 26 | sendtagGuard sync.RWMutex 27 | propertyLock sync.RWMutex 28 | } 29 | 30 | func NewReConnTcpClient(ip string, port int, protoc iface.IClientProtocol, maxRetry int, 31 | retryInterval int, reconnCB func(iface.Iclient)) *TcpClient { 32 | client := NewTcpClient(ip, port, protoc) 33 | client.maxRetry = maxRetry 34 | client.retryInterval = retryInterval 35 | client.reconnCB = reconnCB 36 | return client 37 | } 38 | 39 | func NewTcpClient(ip string, port int, protoc iface.IClientProtocol) *TcpClient { 40 | addr := &net.TCPAddr{ 41 | IP: net.ParseIP(ip), 42 | Port: port, 43 | Zone: "", 44 | } 45 | conn, err := net.DialTCP("tcp", nil, addr) 46 | if err == nil { 47 | client := &TcpClient{ 48 | conn: conn, 49 | addr: addr, 50 | protoc: protoc, 51 | PropertyBag: make(map[string]interface{}, 0), 52 | } 53 | go client.protoc.OnConnectionMade(client) 54 | return client 55 | } else { 56 | panic(err) 57 | } 58 | 59 | } 60 | 61 | func (this *TcpClient) Start() { 62 | go this.protoc.StartReadThread(this) 63 | } 64 | 65 | func (this *TcpClient) Stop(isforce bool) { 66 | if this.maxRetry == 0 || isforce { 67 | this.protoc.OnConnectionLost(this) 68 | } else { 69 | //retry 70 | if this.ReConnection() { 71 | //顺序很重要,先把读数据用的goroutine开启 72 | this.Start() 73 | if this.reconnCB != nil { 74 | this.reconnCB(this) 75 | } 76 | } 77 | } 78 | } 79 | 80 | func (this *TcpClient) ReConnection() bool { 81 | logger.Info("reconnection ...") 82 | for i := 1; i <= this.maxRetry; i++ { 83 | logger.Info("retry time ", i) 84 | conn, err := net.DialTCP("tcp", nil, this.addr) 85 | if err == nil { 86 | this.conn = conn 87 | return true 88 | } else { 89 | d, err := time.ParseDuration(fmt.Sprintf("%ds", this.retryInterval)) 90 | if err != nil { 91 | time.Sleep(RETRY_INTERVAL * time.Second) 92 | } else { 93 | time.Sleep(d) 94 | } 95 | } 96 | } 97 | return false 98 | } 99 | 100 | func (this *TcpClient) Send(data []byte) error { 101 | this.sendtagGuard.Lock() 102 | defer this.sendtagGuard.Unlock() 103 | 104 | if _, err := this.conn.Write(data); err != nil { 105 | logger.Error(fmt.Sprintf("rpc client send data error.reason: %s", err)) 106 | return err 107 | } 108 | return nil 109 | } 110 | 111 | func (this *TcpClient) GetConnection() *net.TCPConn { 112 | return this.conn 113 | } 114 | 115 | func (this *TcpClient) GetProperty(key string) (interface{}, error) { 116 | this.propertyLock.RLock() 117 | defer this.propertyLock.RUnlock() 118 | 119 | value, ok := this.PropertyBag[key] 120 | if ok { 121 | return value, nil 122 | } else { 123 | return nil, errors.New("no property in connection") 124 | } 125 | } 126 | 127 | func (this *TcpClient) SetProperty(key string, value interface{}) { 128 | this.propertyLock.Lock() 129 | defer this.propertyLock.Unlock() 130 | 131 | this.PropertyBag[key] = value 132 | } 133 | 134 | func (this *TcpClient) RemoveProperty(key string) { 135 | this.propertyLock.Lock() 136 | defer this.propertyLock.Unlock() 137 | 138 | delete(this.PropertyBag, key) 139 | } 140 | -------------------------------------------------------------------------------- /cluster/rpchandle.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | /* 4 | regest rpc 5 | */ 6 | import ( 7 | "fmt" 8 | "github.com/viphxin/xingo/logger" 9 | "github.com/viphxin/xingo/utils" 10 | "math/rand" 11 | "reflect" 12 | "time" 13 | ) 14 | 15 | type RpcMsgHandle struct { 16 | PoolSize int32 17 | TaskQueue []chan *RpcRequest 18 | Apis map[string]reflect.Value 19 | } 20 | 21 | func NewRpcMsgHandle() *RpcMsgHandle { 22 | return &RpcMsgHandle{ 23 | PoolSize: utils.GlobalObject.PoolSize, 24 | TaskQueue: make([]chan *RpcRequest, utils.GlobalObject.PoolSize), 25 | Apis: make(map[string]reflect.Value), 26 | } 27 | } 28 | 29 | /* 30 | 处理rpc消息 31 | */ 32 | func (this *RpcMsgHandle) DoMsg(request *RpcRequest) { 33 | if request.Rpcdata.MsgType == RESPONSE && request.Rpcdata.Key != "" { 34 | //放回异步结果 35 | AResultGlobalObj.FillAsyncResult(request.Rpcdata.Key, request.Rpcdata) 36 | return 37 | } else { 38 | //rpc 请求 39 | if f, ok := this.Apis[request.Rpcdata.Target]; ok { 40 | //存在 41 | st := time.Now() 42 | if request.Rpcdata.MsgType == REQUEST_FORRESULT { 43 | ret := f.Call([]reflect.Value{reflect.ValueOf(request)}) 44 | if len(ret) == 0 { 45 | return 46 | } 47 | packdata, err := utils.GlobalObject.RpcCProtoc.GetDataPack().Pack(0, &RpcData{ 48 | MsgType: RESPONSE, 49 | Result: ret[0].Interface().(map[string]interface{}), 50 | Key: request.Rpcdata.Key, 51 | }) 52 | if err == nil { 53 | request.Fconn.Send(packdata) 54 | } else { 55 | logger.Error(err) 56 | } 57 | } else if request.Rpcdata.MsgType == REQUEST_NORESULT { 58 | f.Call([]reflect.Value{reflect.ValueOf(request)}) 59 | } 60 | 61 | logger.Debug(fmt.Sprintf("rpc %s cost total time: %f ms", request.Rpcdata.Target, time.Now().Sub(st).Seconds()*1000)) 62 | } else { 63 | logger.Error(fmt.Sprintf("not found rpc: %s", request.Rpcdata.Target)) 64 | } 65 | } 66 | } 67 | 68 | func (this *RpcMsgHandle) DeliverToMsgQueue(pkg interface{}) { 69 | request := pkg.(*RpcRequest) 70 | //add to worker pool 71 | index := rand.Int31n(utils.GlobalObject.PoolSize) 72 | taskQueue := this.TaskQueue[index] 73 | logger.Debug(fmt.Sprintf("add to rpc pool : %d", index)) 74 | taskQueue <- request 75 | } 76 | 77 | func (this *RpcMsgHandle) DoMsgFromGoRoutine(pkg interface{}) { 78 | request := pkg.(*RpcRequest) 79 | go this.DoMsg(request) 80 | } 81 | 82 | func (this *RpcMsgHandle) AddRouter(router interface{}) { 83 | value := reflect.ValueOf(router) 84 | tp := value.Type() 85 | for i := 0; i < value.NumMethod(); i += 1 { 86 | name := tp.Method(i).Name 87 | 88 | if _, ok := this.Apis[name]; ok { 89 | //存在 90 | panic("repeated rpc " + name) 91 | } 92 | this.Apis[name] = value.Method(i) 93 | logger.Info("add rpc " + name) 94 | } 95 | } 96 | 97 | func (this *RpcMsgHandle) StartWorkerLoop(poolSize int) { 98 | if utils.GlobalObject.IsThreadSafeMode(){ 99 | this.TaskQueue[0] = make(chan *RpcRequest, utils.GlobalObject.MaxWorkerLen) 100 | go func(){ 101 | for{ 102 | select { 103 | case rpcRequest := <- this.TaskQueue[0]: 104 | this.DoMsg(rpcRequest) 105 | case delayCall := <- utils.GlobalObject.GetSafeTimer().GetTriggerChannel(): 106 | delayCall.Call() 107 | } 108 | } 109 | }() 110 | }else{ 111 | for i := 0; i < poolSize; i += 1 { 112 | c := make(chan *RpcRequest, utils.GlobalObject.MaxWorkerLen) 113 | this.TaskQueue[i] = c 114 | go func(index int, taskQueue chan *RpcRequest) { 115 | logger.Info(fmt.Sprintf("init rpc thread pool %d.", index)) 116 | for { 117 | request := <-taskQueue 118 | this.DoMsg(request) 119 | } 120 | 121 | }(i, c) 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xingo_cluster 2 | 3 | xingo golang游戏开发交流群:535378240
4 | 文档地址: http://www.runingman.net/ 5 | ```text 6 | xingo是免费、开源、可定制、可扩展、节点支持“热更新”的高性能分布式服务器开发框架,采用golang语言开发,天生携带 7 | 高并发场景的处理基因,继承了golang语言本身的各种优点,开发简单易上手并且功能强大。它主要实现了高性能的异步网络库, 8 | 分布式节点间的高性能rpc通信,日志管理,数据库支持(暂时只支持mongodb),goroutine安全的定时器,telnet在线服务器调试 9 | 工具等,可用的开发场景包括但不局限于IM即时通讯服务器,游戏服务器(已有多款公司级别的项目选择了xingo)等可以节省大量 10 | 游戏开发时间,让游戏开发人员可以将主要精力放到游戏玩法和游戏逻辑上。真正实现了修改配置文件就可以搭建自定义的分布式服 11 | 务器架构。 12 | 13 | 优势特点: 14 | 1) 开发效率高 15 | 2) 支持自定义的分布式架构,方便横向扩展节点,理论上只要有足够的物理机器,没有承载上限 16 | 3) 支持自定义通信协议 17 | 4) 分布式节点自动发现,自动重连 18 | 5) worker pool工作线程池 19 | 6) telnet在线服务调试工具(使用方便扩展简单) 20 | 7) 内置mongodb数据库支持 21 | 8)goroutine安全的定时器实现 22 | ``` 23 | 示例配置:
24 | ```json 25 | { 26 | "master":{"host": "192.168.2.225","rootport":9999}, 27 | "servers":{ 28 | "gate2":{"host": "192.168.2.225", "rootport":10000,"name":"gate2", "module": "gate", "log": "gate2.log"}, 29 | "gate1":{"host": "192.168.2.225", "rootport":10001,"name":"gate1", "module": "gate", "log": "gate1.log"}, 30 | "net1":{"host": "192.168.2.225", "netport":11009,"name":"net1","remotes":["gate2", "gate1"], 31 | "module": "net", "log": "net.log"}, 32 | "net2":{"host": "192.168.2.225", "netport":11010,"name":"net2","remotes":["gate2", "gate1"], 33 | "module": "net", "log": "net.log"}, 34 | "net3":{"host": "192.168.2.225", "netport":11011,"name":"net3","remotes":["gate2", "gate1"], 35 | "module": "net", "log": "net.log"}, 36 | "net4":{"host": "192.168.2.225", "netport":11012,"name":"net4","remotes":["gate2", "gate1"], 37 | "module": "net", "log": "net.log"}, 38 | "admin":{"host": "192.168.2.225", "remotes":["gate2", "gate1"], "name":"admin", "module": "admin", 39 | "http": [8888, "/static"]}, 40 | "game1":{"host": "192.168.2.225", "remotes":["gate2", "gate1"], "name":"game1", "module": "game"} 41 | } 42 | } 43 | ``` 44 | 示例架构图: 45 | ![alt text](https://git.oschina.net/viphxin/xingo_cluster/raw/master/conf/xingo_cluster_架构.png) 46 | 47 | 48 | 默认通信协议如下(支持自定义协议处理部分代码,支持灵活的重载协议部分代码):
49 | 50 | Len uint32 数据Data部分长度
51 | MsgId uint32 消息号
52 | Data []byte 数据
53 | 消息默认通过google 的protobuf进行序列化
54 | 55 | 服务器全局配置对象为GlobalObject,支持的配置选项及默认值如下:
56 | TcpPort: 8109,//服务器监听端口
57 | MaxConn: 12000,//支持最大链接数
58 | LogPath: "./log",//日志文件路径
59 | LogName: "server.log",//日志文件名
60 | MaxLogNum: 10,//最大日志数
61 | MaxFileSize: 100,//per日志文件大小
62 | LogFileUnit: logger.KB,//日志文件大小对应单位
63 | LogLevel: logger.ERROR,//日志级别
64 | SetToConsole: true,//是否输出到console
65 | LogFileType: 1,//日志切割方式1 按天切割 2按文件大小切割 66 | PoolSize: 10,//api接口工作线程数量
67 | MaxWorkerLen: 1024 * 2,//任务缓冲池大小
68 | MaxSendChanLen: 1024,//发送队列从缓冲池
69 | FrameSpeed: 30,//未使用
70 | MaxPacketSize: 1024,//协议数据包最大包体大小
71 | FrequencyControl: 100/s,// 100/h(每小时一百个包), 100/m(每分钟一百个包), 100/s(每秒一百个包)
72 | OnConnectioned: func(fconn iface.Iconnection) {},//链接建立事件回调
73 | OnClosed: func(fconn iface.Iconnection) {},//链接断开事件回调
74 | OnServerStop: func(), //服务器停服回调
75 | Protoc: iface.IServerProtocol//socket数据pack和unpack的实现,可以通过设置该值重载服务器协议
76 | 77 | 如何使用?
78 | 只需要一步,添加消息路由:
79 | s := fserver.NewServer()
80 | //add api ---------------start
81 | FightingRouterObj := &api.FightingRouter{}
82 | s.AddRouter(FightingRouterObj)
83 | //add api ---------------end
84 | xingo会自动注册FightingRouter中的方法处理对应消息
85 | 例如:msgId =1 则会寻找FightingRouter中的Func_1的方法从进行处理
86 | 具体使用请参考项目:
87 | 帧同步服务器: https://github.com/viphxin/fighting
88 | mmo demo: https://git.oschina.net/viphxin/xingo_demo
89 | xingo_cluster demo: https://github.com/viphxin/xingo_cluster 90 | 91 | -------------------------------------------------------------------------------- /utils/globalobj.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/viphxin/xingo/iface" 6 | "github.com/viphxin/xingo/logger" 7 | "io/ioutil" 8 | "strconv" 9 | "strings" 10 | "os" 11 | "github.com/viphxin/xingo/timer" 12 | ) 13 | 14 | type GlobalObj struct { 15 | TcpServers map[string]iface.Iserver 16 | TcpServer iface.Iserver 17 | OnConnectioned func(fconn iface.Iconnection) 18 | OnClosed func(fconn iface.Iconnection) 19 | OnClusterConnectioned func(fconn iface.Iconnection) //集群rpc root节点回调 20 | OnClusterClosed func(fconn iface.Iconnection) 21 | OnClusterCConnectioned func(fconn iface.Iclient) //集群rpc 子节点回调 22 | OnClusterCClosed func(fconn iface.Iclient) 23 | OnServerStop func() //服务器停服回调 24 | Protoc iface.IServerProtocol 25 | RpcSProtoc iface.IServerProtocol 26 | RpcCProtoc iface.IClientProtocol 27 | Host string 28 | DebugPort int //telnet port 用于单机模式 29 | WriteList []string //telnet ip list 30 | TcpPort int 31 | MaxConn int 32 | IntraMaxConn int //内部服务器最大连接数 33 | //log 34 | LogPath string 35 | LogName string 36 | MaxLogNum int32 37 | MaxFileSize int64 38 | LogFileUnit logger.UNIT 39 | LogLevel logger.LEVEL 40 | SetToConsole bool 41 | LogFileType int32 42 | PoolSize int32 43 | MaxWorkerLen int32 44 | MaxSendChanLen int32 45 | FrameSpeed uint8 46 | Name string 47 | MaxPacketSize uint32 48 | FrequencyControl string // 100/h, 100/m, 100/s 49 | CmdInterpreter iface.ICommandInterpreter //xingo debug tool Interpreter 50 | ProcessSignalChan chan os.Signal 51 | safeTimerScheduel *timer.SafeTimerScheduel 52 | } 53 | 54 | func (this *GlobalObj) GetFrequency() (int, string) { 55 | fc := strings.Split(this.FrequencyControl, "/") 56 | if len(fc) != 2 { 57 | return 0, "" 58 | } else { 59 | fc0_int, err := strconv.Atoi(fc[0]) 60 | if err == nil { 61 | return fc0_int, fc[1] 62 | } else { 63 | logger.Error("FrequencyControl params error: ", this.FrequencyControl) 64 | return 0, "" 65 | } 66 | } 67 | } 68 | 69 | func (this *GlobalObj)IsThreadSafeMode()bool{ 70 | if this.PoolSize == 1{ 71 | return true 72 | }else{ 73 | return false 74 | } 75 | } 76 | 77 | func (this *GlobalObj)GetSafeTimer() *timer.SafeTimerScheduel{ 78 | return this.safeTimerScheduel 79 | } 80 | 81 | func (this *GlobalObj)Reload(){ 82 | //读取用户自定义配置 83 | data, err := ioutil.ReadFile("conf/server.json") 84 | if err != nil { 85 | panic(err) 86 | } 87 | err = json.Unmarshal(data, this) 88 | if err != nil { 89 | panic(err) 90 | }else{ 91 | ReSettingLog() 92 | //init safetimer 93 | if GlobalObject.safeTimerScheduel == nil && GlobalObject.IsThreadSafeMode(){ 94 | GlobalObject.safeTimerScheduel = timer.NewSafeTimerScheduel() 95 | } 96 | } 97 | } 98 | 99 | var GlobalObject *GlobalObj 100 | 101 | func init() { 102 | GlobalObject = &GlobalObj{ 103 | TcpServers: make(map[string]iface.Iserver), 104 | Host: "0.0.0.0", 105 | TcpPort: 8109, 106 | MaxConn: 12000, 107 | IntraMaxConn: 100, 108 | LogPath: "./log", 109 | LogName: "server.log", 110 | MaxLogNum: 10, 111 | MaxFileSize: 100, 112 | LogFileUnit: logger.KB, 113 | LogLevel: logger.DEBUG, 114 | SetToConsole: true, 115 | LogFileType: 1, 116 | PoolSize: 10, 117 | MaxWorkerLen: 1024 * 2, 118 | MaxSendChanLen: 1024, 119 | FrameSpeed: 30, 120 | OnConnectioned: func(fconn iface.Iconnection) {}, 121 | OnClosed: func(fconn iface.Iconnection) {}, 122 | OnClusterConnectioned: func(fconn iface.Iconnection) {}, 123 | OnClusterClosed: func(fconn iface.Iconnection) {}, 124 | OnClusterCConnectioned: func(fconn iface.Iclient) {}, 125 | OnClusterCClosed: func(fconn iface.Iclient) {}, 126 | ProcessSignalChan: make(chan os.Signal, 1), 127 | } 128 | GlobalObject.Reload() 129 | } 130 | -------------------------------------------------------------------------------- /fnet/msghandle.go: -------------------------------------------------------------------------------- 1 | package fnet 2 | 3 | /* 4 | find msg api 5 | */ 6 | import ( 7 | "fmt" 8 | "github.com/viphxin/xingo/logger" 9 | "github.com/viphxin/xingo/utils" 10 | "reflect" 11 | "strconv" 12 | "strings" 13 | "time" 14 | "runtime/debug" 15 | ) 16 | 17 | type MsgHandle struct { 18 | PoolSize int32 19 | TaskQueue []chan *PkgAll 20 | Apis map[uint32]reflect.Value 21 | } 22 | 23 | func NewMsgHandle() *MsgHandle { 24 | return &MsgHandle{ 25 | PoolSize: utils.GlobalObject.PoolSize, 26 | TaskQueue: make([]chan *PkgAll, utils.GlobalObject.PoolSize), 27 | Apis: make(map[uint32]reflect.Value), 28 | } 29 | } 30 | 31 | //一致性路由,保证同一连接的数据转发给相同的goroutine 32 | func (this *MsgHandle) DeliverToMsgQueue(pkg interface{}) { 33 | data := pkg.(*PkgAll) 34 | //index := rand.Int31n(utils.GlobalObject.PoolSize) 35 | index := data.Fconn.GetSessionId() % uint32(utils.GlobalObject.PoolSize) 36 | taskQueue := this.TaskQueue[index] 37 | logger.Debug(fmt.Sprintf("add to pool : %d", index)) 38 | taskQueue <- data 39 | } 40 | 41 | func (this *MsgHandle) DoMsgFromGoRoutine(pkg interface{}) { 42 | data := pkg.(*PkgAll) 43 | go func() { 44 | if f, ok := this.Apis[data.Pdata.MsgId]; ok { 45 | //存在 46 | st := time.Now() 47 | f.Call([]reflect.Value{reflect.ValueOf(data)}) 48 | logger.Debug(fmt.Sprintf("Api_%d cost total time: %f ms", data.Pdata.MsgId, time.Now().Sub(st).Seconds()*1000)) 49 | } else { 50 | logger.Error(fmt.Sprintf("not found api: %d", data.Pdata.MsgId)) 51 | } 52 | }() 53 | } 54 | 55 | func (this *MsgHandle) AddRouter(router interface{}) { 56 | value := reflect.ValueOf(router) 57 | tp := value.Type() 58 | for i := 0; i < value.NumMethod(); i += 1 { 59 | name := tp.Method(i).Name 60 | k := strings.Split(name, "_") 61 | index, err := strconv.Atoi(k[1]) 62 | if err != nil { 63 | panic("error api: " + name) 64 | } 65 | if _, ok := this.Apis[uint32(index)]; ok { 66 | //存在 67 | panic("repeated api " + string(index)) 68 | } 69 | this.Apis[uint32(index)] = value.Method(i) 70 | logger.Info("add api " + name) 71 | } 72 | 73 | //exec test 74 | // for i := 0; i < 100; i += 1 { 75 | // Apis[1].Call([]reflect.Value{reflect.ValueOf("huangxin"), reflect.ValueOf(router)}) 76 | // Apis[2].Call([]reflect.Value{}) 77 | // } 78 | // fmt.Println(this.Apis) 79 | // this.Apis[2].Call([]reflect.Value{reflect.ValueOf(&PkgAll{})}) 80 | } 81 | 82 | func (this *MsgHandle)HandleError(err interface{}){ 83 | if err != nil{ 84 | debug.PrintStack() 85 | } 86 | } 87 | 88 | func (this *MsgHandle) StartWorkerLoop(poolSize int) { 89 | if utils.GlobalObject.IsThreadSafeMode(){ 90 | //线程安全模式所有的逻辑都在一个goroutine处理, 这样可以实现无锁化服务 91 | this.TaskQueue[0] = make(chan *PkgAll, utils.GlobalObject.MaxWorkerLen) 92 | go func(){ 93 | logger.Info("init thread mode workpool.") 94 | for{ 95 | select { 96 | case data := <- this.TaskQueue[0]: 97 | if f, ok := this.Apis[data.Pdata.MsgId]; ok { 98 | //存在 99 | st := time.Now() 100 | //f.Call([]reflect.Value{reflect.ValueOf(data)}) 101 | utils.XingoTry(f, []reflect.Value{reflect.ValueOf(data)}, this.HandleError) 102 | logger.Debug(fmt.Sprintf("Api_%d cost total time: %f ms", data.Pdata.MsgId, time.Now().Sub(st).Seconds()*1000)) 103 | } else { 104 | logger.Error(fmt.Sprintf("not found api: %d", data.Pdata.MsgId)) 105 | } 106 | case delaytask := <- utils.GlobalObject.GetSafeTimer().GetTriggerChannel(): 107 | delaytask.Call() 108 | } 109 | } 110 | }() 111 | }else{ 112 | for i := 0; i < poolSize; i += 1 { 113 | c := make(chan *PkgAll, utils.GlobalObject.MaxWorkerLen) 114 | this.TaskQueue[i] = c 115 | go func(index int, taskQueue chan *PkgAll) { 116 | logger.Info(fmt.Sprintf("init thread pool %d.", index)) 117 | for { 118 | data := <-taskQueue 119 | //can goroutine? 120 | if f, ok := this.Apis[data.Pdata.MsgId]; ok { 121 | //存在 122 | st := time.Now() 123 | //f.Call([]reflect.Value{reflect.ValueOf(data)}) 124 | utils.XingoTry(f, []reflect.Value{reflect.ValueOf(data)}, this.HandleError) 125 | logger.Debug(fmt.Sprintf("Api_%d cost total time: %f ms", data.Pdata.MsgId, time.Now().Sub(st).Seconds()*1000)) 126 | } else { 127 | logger.Error(fmt.Sprintf("not found api: %d", data.Pdata.MsgId)) 128 | } 129 | } 130 | }(i, c) 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /timer/hashwheel.go: -------------------------------------------------------------------------------- 1 | package timer 2 | 3 | import ( 4 | "sync" 5 | "github.com/viphxin/xingo/logger" 6 | "fmt" 7 | "time" 8 | "errors" 9 | ) 10 | /* 11 | 分级时间轮 12 | */ 13 | 14 | const ( 15 | DEFAULT_LEVEL = 12 16 | ) 17 | 18 | type HashWheel struct { 19 | title string //时间轮唯一标识 20 | index int //时间轮当前指针 21 | level int //多少级 22 | levelInterval int64 //分级间隔 (ms) 23 | maxCap uint32 //每一级最大容量 24 | timerQueue map[int]map[uint32]*SafeTimer//存储所有timer 25 | nextHashWheel *HashWheel //下级时间轮 26 | sync.RWMutex 27 | } 28 | 29 | func NewHashWheel(title string, level int, linterval int64, maxCap uint32) *HashWheel{ 30 | wheel := &HashWheel{ 31 | title: title, 32 | index: 0, 33 | level: level, 34 | levelInterval: linterval, 35 | maxCap: maxCap, 36 | timerQueue: make(map[int]map[uint32]*SafeTimer, level), 37 | } 38 | for i := 0; i < wheel.level; i++{ 39 | wheel.timerQueue[i] = make(map[uint32]*SafeTimer, maxCap) 40 | } 41 | go wheel.RunWheel() 42 | return wheel 43 | } 44 | 45 | func (this *HashWheel)AddNext(next *HashWheel){ 46 | this.nextHashWheel = next 47 | } 48 | 49 | func (this *HashWheel)Count() int{ 50 | this.RLock() 51 | defer this.RUnlock() 52 | 53 | c := 0 54 | for i := 0; i < this.level; i++{ 55 | c += len(this.timerQueue[i]) 56 | } 57 | return c 58 | } 59 | 60 | func (this *HashWheel)_add2WheelChain(tid uint32, t *SafeTimer, forceNext bool) error{ 61 | defer func() error{ 62 | if err := recover(); err != nil{ 63 | logger.Error(fmt.Sprintf("add safetimer to hashwheel err: %s.", err)) 64 | return errors.New(fmt.Sprintf("add safetimer to hashwheel err: %s.", err)) 65 | }else{ 66 | return nil 67 | } 68 | }() 69 | 70 | now := UnixTS() 71 | if t.unixts - now >= this.levelInterval || this.nextHashWheel == nil{ 72 | saved := false 73 | for i := this.level - 1; i >= 0; i-- { 74 | if t.unixts - now >= int64(i)*this.levelInterval{ 75 | if (i + this.index)%this.level == this.index && forceNext{ 76 | this.timerQueue[(i + this.index + 1)%this.level][tid] = t 77 | }else{ 78 | this.timerQueue[(i + this.index)%this.level][tid] = t 79 | } 80 | saved = true 81 | break 82 | } 83 | } 84 | if !saved { 85 | if forceNext { 86 | this.timerQueue[(this.index+1)%this.level][tid] = t 87 | }else{ 88 | this.timerQueue[this.index][tid] = t 89 | } 90 | } 91 | return nil 92 | }else{ 93 | //应该放到下级 94 | return this.nextHashWheel.Add2WheelChain(tid, t) 95 | 96 | } 97 | } 98 | 99 | func (this *HashWheel)Add2WheelChain(tid uint32, t *SafeTimer) error{ 100 | this.Lock() 101 | defer this.Unlock() 102 | 103 | return this._add2WheelChain(tid, t, false) 104 | } 105 | 106 | func (this *HashWheel)RemoveFromWheelChain(tid uint32){ 107 | this.Lock() 108 | defer this.Unlock() 109 | 110 | for i := 0; i < this.level; i++{ 111 | if _, ok := this.timerQueue[i][tid]; ok{ 112 | delete(this.timerQueue[i], tid) 113 | return 114 | } 115 | } 116 | //去下级wheel找 117 | if this.nextHashWheel != nil{ 118 | this.nextHashWheel.RemoveFromWheelChain(tid) 119 | } 120 | } 121 | 122 | func (this *HashWheel)GetTriggerWithIn(ms int64) map[uint32]*SafeTimer{ 123 | leafWheel := this 124 | for leafWheel.nextHashWheel != nil{ 125 | leafWheel = leafWheel.nextHashWheel 126 | } 127 | 128 | leafWheel.Lock() 129 | defer leafWheel.Unlock() 130 | 131 | triggerList := make(map[uint32]*SafeTimer) 132 | now := UnixTS() 133 | for k, v := range leafWheel.timerQueue[leafWheel.index]{ 134 | if v.unixts - now <= ms { 135 | triggerList[k] = v 136 | } 137 | } 138 | 139 | for k, _ := range triggerList{ 140 | delete(leafWheel.timerQueue[leafWheel.index], k) 141 | } 142 | return triggerList 143 | 144 | } 145 | 146 | //时间轮跑起来 147 | func (this *HashWheel)RunWheel() { 148 | for{ 149 | time.Sleep(time.Duration(this.levelInterval) * time.Millisecond) 150 | //loop 151 | this.Lock() 152 | CurtriggerList := this.timerQueue[this.index] 153 | this.timerQueue[this.index] = make(map[uint32]*SafeTimer, this.maxCap) 154 | for k, v := range CurtriggerList{ 155 | this._add2WheelChain(k, v, true) 156 | } 157 | 158 | NextriggerList := this.timerQueue[(this.index + 1) % this.level] 159 | this.timerQueue[(this.index + 1) % this.level] = make(map[uint32]*SafeTimer, this.maxCap) 160 | for k, v := range NextriggerList{ 161 | this._add2WheelChain(k, v, true) 162 | } 163 | //下一格 164 | this.index = (this.index + 1) % this.level 165 | this.Unlock() 166 | } 167 | } 168 | 169 | 170 | -------------------------------------------------------------------------------- /fnet/protocol.go: -------------------------------------------------------------------------------- 1 | package fnet 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/viphxin/xingo/iface" 7 | "github.com/viphxin/xingo/logger" 8 | "github.com/viphxin/xingo/utils" 9 | "io" 10 | "time" 11 | ) 12 | 13 | const ( 14 | MaxPacketSize = 1024 * 1024 15 | ) 16 | 17 | var ( 18 | packageTooBig = errors.New("Too many data to received!!") 19 | ) 20 | 21 | type PkgAll struct { 22 | Pdata *PkgData 23 | Fconn iface.Iconnection 24 | } 25 | 26 | type Protocol struct { 27 | msghandle *MsgHandle 28 | pbdatapack *PBDataPack 29 | } 30 | 31 | func NewProtocol() *Protocol { 32 | return &Protocol{ 33 | msghandle: NewMsgHandle(), 34 | pbdatapack: NewPBDataPack(), 35 | } 36 | } 37 | 38 | func (this *Protocol) GetMsgHandle() iface.Imsghandle { 39 | return this.msghandle 40 | } 41 | func (this *Protocol) GetDataPack() iface.Idatapack { 42 | return this.pbdatapack 43 | } 44 | 45 | func (this *Protocol) AddRpcRouter(router interface{}) { 46 | this.msghandle.AddRouter(router) 47 | } 48 | 49 | func (this *Protocol) InitWorker(poolsize int32) { 50 | this.msghandle.StartWorkerLoop(int(poolsize)) 51 | } 52 | 53 | func (this *Protocol) OnConnectionMade(fconn iface.Iconnection) { 54 | logger.Info(fmt.Sprintf("client ID: %d connected. IP Address: %s", fconn.GetSessionId(), fconn.RemoteAddr())) 55 | utils.GlobalObject.OnConnectioned(fconn) 56 | //加频率控制 57 | this.SetFrequencyControl(fconn) 58 | } 59 | 60 | func (this *Protocol) SetFrequencyControl(fconn iface.Iconnection) { 61 | fc0, fc1 := utils.GlobalObject.GetFrequency() 62 | if fc1 == "h" { 63 | fconn.SetProperty("xingo_fc", 0) 64 | fconn.SetProperty("xingo_fc0", fc0) 65 | fconn.SetProperty("xingo_fc1", time.Now().UnixNano()*1e6+int64(3600*1e3)) 66 | } else if fc1 == "m" { 67 | fconn.SetProperty("xingo_fc", 0) 68 | fconn.SetProperty("xingo_fc0", fc0) 69 | fconn.SetProperty("xingo_fc1", time.Now().UnixNano()*1e6+int64(60*1e3)) 70 | } else if fc1 == "s" { 71 | fconn.SetProperty("xingo_fc", 0) 72 | fconn.SetProperty("xingo_fc0", fc0) 73 | fconn.SetProperty("xingo_fc1", time.Now().UnixNano()*1e6+int64(1e3)) 74 | } 75 | } 76 | 77 | func (this *Protocol) DoFrequencyControl(fconn iface.Iconnection) error { 78 | xingo_fc1, err := fconn.GetProperty("xingo_fc1") 79 | if err != nil { 80 | //没有频率控制 81 | return nil 82 | } else { 83 | if time.Now().UnixNano()*1e6 >= xingo_fc1.(int64) { 84 | //init 85 | this.SetFrequencyControl(fconn) 86 | } else { 87 | xingo_fc, _ := fconn.GetProperty("xingo_fc") 88 | xingo_fc0, _ := fconn.GetProperty("xingo_fc0") 89 | xingo_fc_int := xingo_fc.(int) + 1 90 | xingo_fc0_int := xingo_fc0.(int) 91 | if xingo_fc_int >= xingo_fc0_int { 92 | //trigger 93 | return errors.New(fmt.Sprintf("received package exceed limit: %s", utils.GlobalObject.FrequencyControl)) 94 | } else { 95 | fconn.SetProperty("xingo_fc", xingo_fc_int) 96 | } 97 | } 98 | return nil 99 | } 100 | } 101 | 102 | func (this *Protocol) OnConnectionLost(fconn iface.Iconnection) { 103 | logger.Info(fmt.Sprintf("client ID: %d disconnected. IP Address: %s", fconn.GetSessionId(), fconn.RemoteAddr())) 104 | utils.GlobalObject.OnClosed(fconn) 105 | } 106 | 107 | func (this *Protocol) StartReadThread(fconn iface.Iconnection) { 108 | logger.Info("start receive data from socket...") 109 | for { 110 | //频率控制 111 | err := this.DoFrequencyControl(fconn) 112 | if err != nil { 113 | logger.Error(err) 114 | fconn.Stop() 115 | return 116 | } 117 | //read per head data 118 | headdata := make([]byte, this.pbdatapack.GetHeadLen()) 119 | 120 | if _, err := io.ReadFull(fconn.GetConnection(), headdata); err != nil { 121 | logger.Error(err) 122 | fconn.Stop() 123 | return 124 | } 125 | pkgHead, err := this.pbdatapack.Unpack(headdata) 126 | if err != nil { 127 | logger.Error(err) 128 | fconn.Stop() 129 | return 130 | } 131 | //data 132 | pkg := pkgHead.(*PkgData) 133 | if pkg.Len > 0 { 134 | pkg.Data = make([]byte, pkg.Len) 135 | if _, err := io.ReadFull(fconn.GetConnection(), pkg.Data); err != nil { 136 | logger.Error(err) 137 | fconn.Stop() 138 | return 139 | } 140 | } 141 | 142 | logger.Debug(fmt.Sprintf("msg id :%d, data len: %d", pkg.MsgId, pkg.Len)) 143 | if utils.GlobalObject.PoolSize > 0 { 144 | this.msghandle.DeliverToMsgQueue(&PkgAll{ 145 | Pdata: pkg, 146 | Fconn: fconn, 147 | }) 148 | } else { 149 | this.msghandle.DoMsgFromGoRoutine(&PkgAll{ 150 | Pdata: pkg, 151 | Fconn: fconn, 152 | }) 153 | } 154 | 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /clusterserver/master.go: -------------------------------------------------------------------------------- 1 | package clusterserver 2 | 3 | import ( 4 | "fmt" 5 | "github.com/viphxin/xingo/cluster" 6 | "github.com/viphxin/xingo/fserver" 7 | "github.com/viphxin/xingo/iface" 8 | "github.com/viphxin/xingo/logger" 9 | "github.com/viphxin/xingo/utils" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | const ( 15 | KEEP_ALIVED_DURATION = 30 //s 16 | ) 17 | 18 | type Master struct { 19 | OnlineNodes map[string]bool 20 | Cconf *cluster.ClusterConf 21 | Childs *cluster.ChildMgr 22 | TelnetServer iface.Iserver 23 | sync.RWMutex 24 | } 25 | 26 | func NewMaster(path string) *Master { 27 | logger.SetPrefix(fmt.Sprintf("[%s]", "MASTER")) 28 | cconf, err := cluster.NewClusterConf(path) 29 | if err != nil { 30 | panic("cluster conf error!!!") 31 | } 32 | GlobalMaster = &Master{ 33 | OnlineNodes: make(map[string]bool), 34 | Cconf: cconf, 35 | Childs: cluster.NewChildMgr(), 36 | } 37 | //regest callback 38 | utils.GlobalObject.TcpPort = GlobalMaster.Cconf.Master.RootPort 39 | utils.GlobalObject.Protoc = cluster.NewRpcServerProtocol() 40 | utils.GlobalObject.RpcCProtoc = cluster.NewRpcClientProtocol() 41 | utils.GlobalObject.OnClusterConnectioned = DoConnectionMade 42 | utils.GlobalObject.OnClusterClosed = DoConnectionLost 43 | utils.GlobalObject.Name = "master" 44 | if cconf.Master.Log != "" { 45 | utils.GlobalObject.LogName = cconf.Master.Log 46 | utils.ReSettingLog() 47 | } 48 | 49 | //telnet debug tool 50 | if GlobalMaster.Cconf.Master.DebugPort > 0{ 51 | if GlobalMaster.Cconf.Master.Host != ""{ 52 | GlobalMaster.TelnetServer = fserver.NewTcpServer("telnet_server", "tcp4", GlobalMaster.Cconf.Master.Host, 53 | GlobalMaster.Cconf.Master.DebugPort, 100, cluster.NewTelnetProtocol()) 54 | }else{ 55 | GlobalMaster.TelnetServer = fserver.NewTcpServer("telnet_server", "tcp4", "127.0.0.1", 56 | GlobalMaster.Cconf.Master.DebugPort, 100, cluster.NewTelnetProtocol()) 57 | } 58 | logger.Info(fmt.Sprintf("telnet tool start: %s:%d.", 59 | GlobalMaster.Cconf.Master.Host, GlobalMaster.Cconf.Master.DebugPort)) 60 | } 61 | return GlobalMaster 62 | } 63 | 64 | func DoConnectionMade(fconn iface.Iconnection) { 65 | logger.Info("node connected to master!!!") 66 | } 67 | 68 | func DoConnectionLost(fconn iface.Iconnection) { 69 | logger.Info("node disconnected from master!!!") 70 | nodename, err := fconn.GetProperty("child") 71 | if err == nil { 72 | GlobalMaster.RemoveNode(nodename.(string)) 73 | } 74 | } 75 | 76 | func (this *Master) StartMaster() { 77 | s := fserver.NewServer() 78 | if GlobalMaster.TelnetServer != nil{ 79 | this.TelnetServer.Start() 80 | } 81 | //check node alive tick 82 | s.CallLoop(KEEP_ALIVED_DURATION*time.Second, this.CheckChildsAlive, true) 83 | s.Serve() 84 | } 85 | 86 | func (this *Master) AddRpcRouter(router interface{}) { 87 | //add rpc ---------------start 88 | utils.GlobalObject.Protoc.AddRpcRouter(router) 89 | //add rpc ---------------end 90 | } 91 | 92 | func (this *Master) AddNode(name string, writer iface.IWriter) { 93 | this.Lock() 94 | defer this.Unlock() 95 | 96 | this.Childs.AddChild(name, writer) 97 | writer.SetProperty("child", name) 98 | this.OnlineNodes[name] = true 99 | } 100 | 101 | func (this *Master) RemoveNode(name string) { 102 | this.Lock() 103 | defer this.Unlock() 104 | 105 | this.Childs.RemoveChild(name) 106 | delete(this.OnlineNodes, name) 107 | 108 | } 109 | 110 | func (this *Master)CheckChildsAlive(params ...interface{}) { 111 | childs := this.Childs.GetChilds() 112 | for _, child := range childs { 113 | _, err := child.CallChildForResult("CheckAlive") 114 | if err == nil { 115 | continue 116 | } 117 | //节点掉线通知child节点的父节点 118 | remotes, err := GlobalMaster.Cconf.GetRemotesByName(child.GetName()) 119 | if err == nil && len(remotes) > 0 { 120 | for _, remote := range remotes { 121 | remoteProxy, err := GlobalMaster.Childs.GetChild(remote) 122 | if err == nil { 123 | //child是子节点 true 124 | remoteProxy.CallChildNotForResult("NodeDownNtf", true, child.GetName()) 125 | } 126 | } 127 | } 128 | //节点掉线通知child节点的子节点 129 | curChilds := GlobalMaster.Cconf.GetChildsByName(child.GetName()) 130 | if len(curChilds) > 0 { 131 | for _, curChild := range curChilds { 132 | curChildProxy, err := GlobalMaster.Childs.GetChild(curChild) 133 | if err == nil { 134 | //child是父节点 false 135 | curChildProxy.CallChildNotForResult("NodeDownNtf", false, child.GetName()) 136 | } 137 | } 138 | } 139 | this.Childs.RemoveChild(child.GetName()) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /fnet/connection.go: -------------------------------------------------------------------------------- 1 | package fnet 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/viphxin/xingo/iface" 7 | "github.com/viphxin/xingo/logger" 8 | "github.com/viphxin/xingo/utils" 9 | "net" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | const ( 15 | XINGO_CONN_PROPERTY_CTIME = "xingo_ctime" 16 | XINGO_CONN_PROPERTY_NAME = "xingo_tcpserver_name" 17 | ) 18 | 19 | type Connection struct { 20 | Conn *net.TCPConn 21 | isClosed bool 22 | SessionId uint32 23 | Protoc iface.IServerProtocol 24 | PropertyBag map[string]interface{} 25 | sendtagGuard sync.RWMutex 26 | propertyLock sync.RWMutex 27 | 28 | SendBuffChan chan []byte 29 | ExtSendChan chan bool 30 | } 31 | 32 | func NewConnection(conn *net.TCPConn, sessionId uint32, protoc iface.IServerProtocol) *Connection { 33 | fconn := &Connection{ 34 | Conn: conn, 35 | isClosed: false, 36 | SessionId: sessionId, 37 | Protoc: protoc, 38 | PropertyBag: make(map[string]interface{}), 39 | SendBuffChan: make(chan []byte, utils.GlobalObject.MaxSendChanLen), 40 | ExtSendChan: make(chan bool, 1), 41 | } 42 | //set connection time 43 | fconn.SetProperty(XINGO_CONN_PROPERTY_CTIME, time.Since(time.Now())) 44 | return fconn 45 | } 46 | 47 | func (this *Connection) Start() { 48 | //add to connectionmsg 49 | serverName, err := this.GetProperty(XINGO_CONN_PROPERTY_NAME) 50 | if err != nil{ 51 | logger.Error("not find server name in GlobalObject.") 52 | return 53 | }else{ 54 | serverNameStr := serverName.(string) 55 | utils.GlobalObject.TcpServers[serverNameStr].GetConnectionMgr().Add(this) 56 | } 57 | 58 | this.Protoc.OnConnectionMade(this) 59 | this.StartWriteThread() 60 | this.Protoc.StartReadThread(this) 61 | } 62 | 63 | func (this *Connection) Stop() { 64 | // 防止将Send放在go内造成的多线程冲突问题 65 | this.sendtagGuard.Lock() 66 | defer this.sendtagGuard.Unlock() 67 | 68 | if this.isClosed{ 69 | return 70 | } 71 | 72 | this.Conn.Close() 73 | this.ExtSendChan <- true 74 | this.isClosed = true 75 | //掉线回调放到go内防止,掉线回调处理出线死锁 76 | go this.Protoc.OnConnectionLost(this) 77 | //remove to connectionmsg 78 | serverName, err := this.GetProperty(XINGO_CONN_PROPERTY_NAME) 79 | if err != nil{ 80 | logger.Error("not find server name in GlobalObject.") 81 | return 82 | }else{ 83 | serverNameStr := serverName.(string) 84 | utils.GlobalObject.TcpServers[serverNameStr].GetConnectionMgr().Remove(this) 85 | } 86 | close(this.ExtSendChan) 87 | close(this.SendBuffChan) 88 | } 89 | 90 | func (this *Connection) GetConnection() *net.TCPConn { 91 | return this.Conn 92 | } 93 | 94 | func (this *Connection) GetSessionId() uint32 { 95 | return this.SessionId 96 | } 97 | 98 | func (this *Connection) GetProtoc() iface.IServerProtocol { 99 | return this.Protoc 100 | } 101 | 102 | func (this *Connection) GetProperty(key string) (interface{}, error) { 103 | this.propertyLock.RLock() 104 | defer this.propertyLock.RUnlock() 105 | 106 | value, ok := this.PropertyBag[key] 107 | if ok { 108 | return value, nil 109 | } else { 110 | return nil, errors.New("no property in connection") 111 | } 112 | } 113 | 114 | func (this *Connection) SetProperty(key string, value interface{}) { 115 | this.propertyLock.Lock() 116 | defer this.propertyLock.Unlock() 117 | 118 | this.PropertyBag[key] = value 119 | } 120 | 121 | func (this *Connection) RemoveProperty(key string) { 122 | this.propertyLock.Lock() 123 | defer this.propertyLock.Unlock() 124 | 125 | delete(this.PropertyBag, key) 126 | } 127 | 128 | func (this *Connection) Send(data []byte) error { 129 | // 防止将Send放在go内造成的多线程冲突问题 130 | this.sendtagGuard.Lock() 131 | defer this.sendtagGuard.Unlock() 132 | 133 | if !this.isClosed { 134 | if _, err := this.Conn.Write(data); err != nil { 135 | logger.Error(fmt.Sprintf("send data error.reason: %s", err)) 136 | return err 137 | } 138 | return nil 139 | } else { 140 | return errors.New("connection closed") 141 | } 142 | } 143 | 144 | func (this *Connection) SendBuff(data []byte) error { 145 | // 防止将Send放在go内造成的多线程冲突问题 146 | this.sendtagGuard.Lock() 147 | defer this.sendtagGuard.Unlock() 148 | 149 | if !this.isClosed { 150 | 151 | // 发送超时 152 | select { 153 | case <-time.After(time.Second * 2): 154 | logger.Error("send error: timeout.") 155 | return errors.New("send error: timeout.") 156 | case this.SendBuffChan <- data: 157 | return nil 158 | } 159 | } else { 160 | return errors.New("connection closed") 161 | } 162 | 163 | } 164 | 165 | func (this *Connection) RemoteAddr() net.Addr { 166 | return (*this.Conn).RemoteAddr() 167 | } 168 | 169 | func (this *Connection) LostConnection() { 170 | this.Conn.Close() 171 | logger.Info("LostConnection session: ", this.SessionId) 172 | } 173 | 174 | func (this *Connection) StartWriteThread() { 175 | go func() { 176 | logger.Debug("start send data from channel...") 177 | for { 178 | select { 179 | case <-this.ExtSendChan: 180 | logger.Info("send thread exit successful!!!!") 181 | return 182 | case data := <-this.SendBuffChan: 183 | //send 184 | if _, err := this.Conn.Write(data); err != nil { 185 | logger.Info("send data error exit...") 186 | return 187 | } 188 | } 189 | } 190 | }() 191 | } 192 | -------------------------------------------------------------------------------- /fserver/server.go: -------------------------------------------------------------------------------- 1 | package fserver 2 | 3 | import ( 4 | "fmt" 5 | "github.com/viphxin/xingo/fnet" 6 | "github.com/viphxin/xingo/iface" 7 | "github.com/viphxin/xingo/logger" 8 | "github.com/viphxin/xingo/timer" 9 | "github.com/viphxin/xingo/utils" 10 | "net" 11 | "os" 12 | "os/signal" 13 | "time" 14 | "syscall" 15 | ) 16 | 17 | func init() { 18 | utils.GlobalObject.Protoc = fnet.NewProtocol() 19 | // --------------------------------------------init log start 20 | utils.ReSettingLog() 21 | // --------------------------------------------init log end 22 | } 23 | 24 | type Server struct { 25 | Name string 26 | IPVersion string 27 | IP string 28 | Port int 29 | MaxConn int 30 | GenNum *utils.UUIDGenerator 31 | connectionMgr iface.Iconnectionmgr 32 | Protoc iface.IServerProtocol 33 | } 34 | 35 | func NewServer() iface.Iserver { 36 | s := &Server{ 37 | Name: utils.GlobalObject.Name, 38 | IPVersion: "tcp4", 39 | IP: "0.0.0.0", 40 | Port: utils.GlobalObject.TcpPort, 41 | MaxConn: utils.GlobalObject.MaxConn, 42 | connectionMgr: fnet.NewConnectionMgr(), 43 | Protoc: utils.GlobalObject.Protoc, 44 | GenNum: utils.NewUUIDGenerator(""), 45 | } 46 | utils.GlobalObject.TcpServer = s 47 | 48 | return s 49 | } 50 | 51 | func NewTcpServer(name string, version string, ip string, port int, maxConn int, protoc iface.IServerProtocol) iface.Iserver { 52 | s := &Server{ 53 | Name: name, 54 | IPVersion: version, 55 | IP: ip, 56 | Port: port, 57 | MaxConn: maxConn, 58 | connectionMgr: fnet.NewConnectionMgr(), 59 | Protoc: protoc, 60 | GenNum: utils.NewUUIDGenerator(""), 61 | } 62 | utils.GlobalObject.TcpServer = s 63 | 64 | return s 65 | } 66 | 67 | func (this *Server) handleConnection(conn *net.TCPConn) { 68 | conn.SetNoDelay(true) 69 | conn.SetKeepAlive(true) 70 | // conn.SetDeadline(time.Now().Add(time.Minute * 2)) 71 | var fconn *fnet.Connection 72 | if this.Protoc == nil{ 73 | fconn = fnet.NewConnection(conn, this.GenNum.GetUint32(), utils.GlobalObject.Protoc) 74 | }else{ 75 | fconn = fnet.NewConnection(conn, this.GenNum.GetUint32(), this.Protoc) 76 | } 77 | fconn.SetProperty(fnet.XINGO_CONN_PROPERTY_NAME, this.Name) 78 | fconn.Start() 79 | } 80 | 81 | func (this *Server) Start() { 82 | utils.GlobalObject.TcpServers[this.Name] = this 83 | go func() { 84 | this.Protoc.InitWorker(utils.GlobalObject.PoolSize) 85 | tcpAddr, err := net.ResolveTCPAddr(this.IPVersion, fmt.Sprintf("%s:%d", this.IP, this.Port)) 86 | if err != nil{ 87 | logger.Fatal("ResolveTCPAddr err: ", err) 88 | return 89 | } 90 | ln, err := net.ListenTCP("tcp", tcpAddr) 91 | if err != nil { 92 | logger.Error(err) 93 | } 94 | logger.Info(fmt.Sprintf("start xingo server %s...", this.Name)) 95 | for { 96 | conn, err := ln.AcceptTCP() 97 | if err != nil { 98 | logger.Error(err) 99 | continue 100 | } 101 | //max client exceed 102 | if this.connectionMgr.Len() >= utils.GlobalObject.MaxConn { 103 | conn.Close() 104 | } else { 105 | go this.handleConnection(conn) 106 | } 107 | } 108 | }() 109 | } 110 | 111 | func (this *Server) GetConnectionMgr() iface.Iconnectionmgr { 112 | return this.connectionMgr 113 | } 114 | 115 | func (this *Server) GetConnectionQueue() chan interface{} { 116 | return nil 117 | } 118 | 119 | func (this *Server) Stop() { 120 | logger.Info("stop xingo server ", this.Name) 121 | if utils.GlobalObject.OnServerStop != nil { 122 | utils.GlobalObject.OnServerStop() 123 | } 124 | } 125 | 126 | func (this *Server) AddRouter(router interface{}) { 127 | logger.Info("AddRouter") 128 | utils.GlobalObject.Protoc.GetMsgHandle().AddRouter(router) 129 | } 130 | 131 | func (this *Server) CallLater(durations time.Duration, f func(v ...interface{}), args ...interface{}) { 132 | delayTask := timer.NewTimer(durations, f, args) 133 | delayTask.Run() 134 | } 135 | 136 | func (this *Server) CallWhen(ts string, f func(v ...interface{}), args ...interface{}) { 137 | loc, err_loc := time.LoadLocation("Local") 138 | if err_loc != nil { 139 | logger.Error(err_loc) 140 | return 141 | } 142 | t, err := time.ParseInLocation("2006-01-02 15:04:05", ts, loc) 143 | now := time.Now() 144 | if err == nil { 145 | if now.Before(t) { 146 | this.CallLater(t.Sub(now), f, args...) 147 | } else { 148 | logger.Error("CallWhen time before now") 149 | } 150 | } else { 151 | logger.Error(err) 152 | } 153 | } 154 | 155 | func (this *Server) CallLoop(durations time.Duration, f func(v ...interface{}), args ...interface{}) { 156 | go func() { 157 | delayTask := timer.NewTimer(durations, f, args) 158 | for { 159 | time.Sleep(delayTask.GetDurations()) 160 | delayTask.GetFunc().Call() 161 | } 162 | }() 163 | } 164 | 165 | func (this *Server) WaitSignal() { 166 | signal.Notify(utils.GlobalObject.ProcessSignalChan, os.Kill, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGINT) 167 | sig := <-utils.GlobalObject.ProcessSignalChan 168 | logger.Info(fmt.Sprintf("server exit. signal: [%s]", sig)) 169 | this.Stop() 170 | } 171 | 172 | func (this *Server) Serve() { 173 | this.Start() 174 | this.WaitSignal() 175 | } 176 | -------------------------------------------------------------------------------- /cluster/rpcprotocol.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "fmt" 5 | "github.com/viphxin/xingo/iface" 6 | "github.com/viphxin/xingo/logger" 7 | "github.com/viphxin/xingo/utils" 8 | "io" 9 | "encoding/gob" 10 | "bytes" 11 | ) 12 | 13 | type RpcServerProtocol struct { 14 | rpcMsgHandle *RpcMsgHandle 15 | rpcDatapack *RpcDataPack 16 | } 17 | 18 | func NewRpcServerProtocol() *RpcServerProtocol { 19 | return &RpcServerProtocol{ 20 | rpcMsgHandle: NewRpcMsgHandle(), 21 | rpcDatapack: NewRpcDataPack(), 22 | } 23 | } 24 | 25 | func (this *RpcServerProtocol) GetMsgHandle() iface.Imsghandle { 26 | return this.rpcMsgHandle 27 | } 28 | 29 | func (this *RpcServerProtocol) GetDataPack() iface.Idatapack { 30 | return this.rpcDatapack 31 | } 32 | 33 | func (this *RpcServerProtocol) AddRpcRouter(router interface{}) { 34 | this.rpcMsgHandle.AddRouter(router) 35 | } 36 | 37 | func (this *RpcServerProtocol) InitWorker(poolsize int32) { 38 | this.rpcMsgHandle.StartWorkerLoop(int(poolsize)) 39 | } 40 | 41 | func (this *RpcServerProtocol) OnConnectionMade(fconn iface.Iconnection) { 42 | logger.Info(fmt.Sprintf("node ID: %d connected. IP Address: %s", fconn.GetSessionId(), fconn.RemoteAddr())) 43 | utils.GlobalObject.OnClusterConnectioned(fconn) 44 | } 45 | 46 | func (this *RpcServerProtocol) OnConnectionLost(fconn iface.Iconnection) { 47 | logger.Info(fmt.Sprintf("node ID: %d disconnected. IP Address: %s", fconn.GetSessionId(), fconn.RemoteAddr())) 48 | utils.GlobalObject.OnClusterClosed(fconn) 49 | } 50 | 51 | func (this *RpcServerProtocol) StartReadThread(fconn iface.Iconnection) { 52 | logger.Debug("start receive rpc data from socket...") 53 | for { 54 | //read per head data 55 | headdata := make([]byte, this.rpcDatapack.GetHeadLen()) 56 | 57 | if _, err := io.ReadFull(fconn.GetConnection(), headdata); err != nil { 58 | logger.Error(err) 59 | fconn.Stop() 60 | return 61 | } 62 | pkgHead, err := this.rpcDatapack.Unpack(headdata) 63 | if err != nil { 64 | fconn.Stop() 65 | return 66 | } 67 | //data 68 | pkg := pkgHead.(*RpcPackege) 69 | if pkg.Len > 0 { 70 | pkg.Data = make([]byte, pkg.Len) 71 | if _, err := io.ReadFull(fconn.GetConnection(), pkg.Data); err != nil { 72 | fconn.Stop() 73 | return 74 | } else { 75 | rpcRequest := &RpcRequest{ 76 | Fconn: fconn, 77 | Rpcdata: &RpcData{}, 78 | } 79 | 80 | //err = json.Unmarshal(pkg.Data, rpcRequest.Rpcdata) 81 | //replace json to gob 82 | dec := gob.NewDecoder(bytes.NewReader(pkg.Data)) 83 | err = dec.Decode(rpcRequest.Rpcdata) 84 | 85 | if err != nil { 86 | logger.Error(err) 87 | fconn.Stop() 88 | return 89 | } 90 | 91 | logger.Debug(fmt.Sprintf("rpc call. data len: %d. MsgType: %d", pkg.Len, int(rpcRequest.Rpcdata.MsgType))) 92 | if utils.GlobalObject.PoolSize > 0 && rpcRequest.Rpcdata.MsgType != RESPONSE { 93 | this.rpcMsgHandle.DeliverToMsgQueue(rpcRequest) 94 | } else { 95 | this.rpcMsgHandle.DoMsgFromGoRoutine(rpcRequest) 96 | } 97 | } 98 | } 99 | } 100 | } 101 | 102 | type RpcClientProtocol struct { 103 | rpcMsgHandle *RpcMsgHandle 104 | rpcDatapack *RpcDataPack 105 | } 106 | 107 | func NewRpcClientProtocol() *RpcClientProtocol { 108 | return &RpcClientProtocol{ 109 | rpcMsgHandle: NewRpcMsgHandle(), 110 | rpcDatapack: NewRpcDataPack(), 111 | } 112 | } 113 | 114 | func (this *RpcClientProtocol) GetMsgHandle() iface.Imsghandle { 115 | return this.rpcMsgHandle 116 | } 117 | 118 | func (this *RpcClientProtocol) GetDataPack() iface.Idatapack { 119 | return this.rpcDatapack 120 | } 121 | func (this *RpcClientProtocol) AddRpcRouter(router interface{}) { 122 | this.rpcMsgHandle.AddRouter(router) 123 | } 124 | 125 | func (this *RpcClientProtocol) InitWorker(poolsize int32) { 126 | this.rpcMsgHandle.StartWorkerLoop(int(poolsize)) 127 | } 128 | 129 | func (this *RpcClientProtocol) OnConnectionMade(fconn iface.Iclient) { 130 | utils.GlobalObject.OnClusterCConnectioned(fconn) 131 | } 132 | 133 | func (this *RpcClientProtocol) OnConnectionLost(fconn iface.Iclient) { 134 | utils.GlobalObject.OnClusterCClosed(fconn) 135 | } 136 | 137 | func (this *RpcClientProtocol) StartReadThread(fconn iface.Iclient) { 138 | logger.Debug("start receive rpc data from socket...") 139 | for { 140 | //read per head data 141 | headdata := make([]byte, this.rpcDatapack.GetHeadLen()) 142 | 143 | if _, err := io.ReadFull(fconn.GetConnection(), headdata); err != nil { 144 | logger.Error(err) 145 | fconn.Stop(false) 146 | return 147 | } 148 | pkgHead, err := this.rpcDatapack.Unpack(headdata) 149 | if err != nil { 150 | fconn.Stop(false) 151 | return 152 | } 153 | //data 154 | pkg := pkgHead.(*RpcPackege) 155 | if pkg.Len > 0 { 156 | pkg.Data = make([]byte, pkg.Len) 157 | if _, err := io.ReadFull(fconn.GetConnection(), pkg.Data); err != nil { 158 | fconn.Stop(false) 159 | return 160 | } else { 161 | rpcRequest := &RpcRequest{ 162 | Fconn: fconn, 163 | Rpcdata: &RpcData{}, 164 | } 165 | //err = json.Unmarshal(pkg.Data, rpcRequest.Rpcdata) 166 | //replace json to gob 167 | dec := gob.NewDecoder(bytes.NewReader(pkg.Data)) 168 | err = dec.Decode(rpcRequest.Rpcdata) 169 | if err != nil { 170 | logger.Error("json.Unmarshal error!!!") 171 | fconn.Stop(false) 172 | return 173 | } 174 | 175 | logger.Debug(fmt.Sprintf("rpc call. data len: %d. MsgType: %d", pkg.Len, rpcRequest.Rpcdata.MsgType)) 176 | if utils.GlobalObject.PoolSize > 0 && rpcRequest.Rpcdata.MsgType != RESPONSE { 177 | this.rpcMsgHandle.DeliverToMsgQueue(rpcRequest) 178 | } else { 179 | this.rpcMsgHandle.DoMsgFromGoRoutine(rpcRequest) 180 | } 181 | } 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /db/mongo/dboperate_test.go: -------------------------------------------------------------------------------- 1 | package mongo 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | "gopkg.in/mgo.v2/bson" 7 | "errors" 8 | "gopkg.in/mgo.v2" 9 | "fmt" 10 | "encoding/gob" 11 | "bytes" 12 | ) 13 | 14 | func Test_DailDB(t *testing.T){ 15 | dbcfg := NewDbCfg("127.0.0.1", 27017, "xingodb", "", "") 16 | //dbcfg := NewDbCfg("127.0.0.1", 27017, "xingodb", "admin", "admin") 17 | t.Log("url: ", dbcfg.String()) 18 | dbo := NewDbOperate(dbcfg, 5*time.Second) 19 | dbo.OpenDB(nil) 20 | dbo.CloseDB() 21 | } 22 | //go test -v github.com\viphxin\xingo\db\mongo -run ^Test_CommonOperate$ 23 | func Test_CommonOperate(t *testing.T){ 24 | dbcfg := NewDbCfg("127.0.0.1", 27017, "xingodb", "", "") 25 | //dbcfg := NewDbCfg("127.0.0.1", 27017, "xingodb", "admin", "admin") 26 | t.Log("url: ", dbcfg.String()) 27 | dbo := NewDbOperate(dbcfg, 5*time.Second) 28 | dbo.OpenDB(func(ms *mgo.Session){ 29 | ms.DB("").C("test").EnsureIndex(mgo.Index{ 30 | Key: []string{"username"}, 31 | Unique: true, 32 | }) 33 | }) 34 | 35 | _, err := dbo.DeleteAll("test", bson.M{"pass": "pass1111"}) 36 | if err != nil{ 37 | //not do anything 38 | } 39 | //------------------------------------------------------------------------ 40 | err = dbo.Insert("test", bson.M{"username": "xingo", "pass": "pass1111"}) 41 | if err != nil{ 42 | dbo.CloseDB() 43 | t.Fatal(err) 44 | return 45 | } 46 | 47 | err = dbo.Insert("test", bson.M{"username": "xingo_0", "pass": "pass1111"}) 48 | if err != nil{ 49 | dbo.CloseDB() 50 | t.Fatal(err) 51 | return 52 | } 53 | 54 | err = dbo.DBFindOne("test", bson.M{"username": "xingo"}, func(a bson.M)error{ 55 | if a != nil{ 56 | t.Log(a) 57 | return nil 58 | }else{ 59 | dbo.CloseDB() 60 | t.Fatal("DBFindOne error") 61 | return errors.New("DBFindOne error") 62 | } 63 | 64 | }) 65 | if err != nil{ 66 | dbo.CloseDB() 67 | t.Fatal(err) 68 | return 69 | } 70 | _, err = dbo.DeleteAll("test", bson.M{"pass": "pass1111"}) 71 | if err != nil{ 72 | dbo.CloseDB() 73 | t.Fatal(err) 74 | return 75 | } 76 | //------------------------------------------------------------------------- 77 | //bulk 78 | docs := make([]bson.M, 0) 79 | for i := 0; i < 500; i++{ 80 | docs = append(docs, bson.M{"username": fmt.Sprintf("xingo_%d", i), "pass": "pass1111"}) 81 | } 82 | 83 | err = dbo.BulkInsert("test", docs) 84 | if err != nil{ 85 | dbo.CloseDB() 86 | t.Fatal(err) 87 | return 88 | } 89 | 90 | _, err = dbo.DeleteAll("test", bson.M{"pass": "pass1111"}) 91 | if err != nil{ 92 | dbo.CloseDB() 93 | t.Fatal(err) 94 | return 95 | } 96 | 97 | dbo.CloseDB() 98 | } 99 | 100 | //go test -v github.com\viphxin\xingo\db\mongo -bench ^Benchmark_CommonOperate$ 101 | func Benchmark_CommonOperate(b *testing.B){ 102 | dbcfg := NewDbCfg("127.0.0.1", 27017, "xingodb", "", "") 103 | //dbcfg := NewDbCfg("127.0.0.1", 27017, "xingodb", "admin", "admin") 104 | b.Log("url: ", dbcfg.String()) 105 | dbo := NewDbOperate(dbcfg, 5*time.Second) 106 | dbo.OpenDB(func(ms *mgo.Session){ 107 | ms.DB("").C("test").EnsureIndex(mgo.Index{ 108 | Key: []string{"username"}, 109 | Unique: true, 110 | }) 111 | }) 112 | 113 | for i := 0; i < b.N; i++ { 114 | _, err := dbo.DeleteAll("test", bson.M{"pass": "pass1111"}) 115 | if err != nil { 116 | //not do anything 117 | } 118 | //------------------------------------------------------------------------ 119 | err = dbo.Insert("test", bson.M{"username": "xingo", "pass": "pass1111"}) 120 | if err != nil { 121 | dbo.CloseDB() 122 | b.Fatal(err) 123 | return 124 | } 125 | 126 | err = dbo.Insert("test", bson.M{"username": "xingo_0", "pass": "pass1111"}) 127 | if err != nil { 128 | dbo.CloseDB() 129 | b.Fatal(err) 130 | return 131 | } 132 | 133 | err = dbo.DBFindOne("test", bson.M{"username": "xingo"}, func(a bson.M) error { 134 | if a != nil { 135 | b.Log(a) 136 | return nil 137 | } else { 138 | dbo.CloseDB() 139 | b.Fatal("DBFindOne error") 140 | return errors.New("DBFindOne error") 141 | } 142 | 143 | }) 144 | if err != nil { 145 | dbo.CloseDB() 146 | b.Fatal(err) 147 | return 148 | } 149 | _, err = dbo.DeleteAll("test", bson.M{"pass": "pass1111"}) 150 | if err != nil { 151 | dbo.CloseDB() 152 | b.Fatal(err) 153 | return 154 | } 155 | //------------------------------------------------------------------------- 156 | //bulk 157 | docs := make([]bson.M, 0) 158 | for i := 0; i < 500; i++ { 159 | docs = append(docs, bson.M{"username": fmt.Sprintf("xingo_%d", i), "pass": "pass1111"}) 160 | } 161 | 162 | err = dbo.BulkInsert("test", docs) 163 | if err != nil { 164 | dbo.CloseDB() 165 | b.Fatal(err) 166 | return 167 | } 168 | 169 | _, err = dbo.DeleteAll("test", bson.M{"pass": "pass1111"}) 170 | if err != nil { 171 | dbo.CloseDB() 172 | b.Fatal(err) 173 | return 174 | } 175 | } 176 | dbo.CloseDB() 177 | } 178 | 179 | //go test -v github.com\viphxin\xingo\db\mongo -bench ^Benchmark_CommonOperatePP$ 180 | func Benchmark_CommonOperatePP(b *testing.B){ 181 | dbcfg := NewDbCfg("127.0.0.1", 27017, "xingodb", "", "") 182 | //dbcfg := NewDbCfg("127.0.0.1", 27017, "xingodb", "admin", "admin") 183 | b.Log("url: ", dbcfg.String()) 184 | dbo := NewDbOperate(dbcfg, 5*time.Second) 185 | dbo.OpenDB(func(ms *mgo.Session){ 186 | ms.DB("").C("test").DropIndex("username") 187 | }) 188 | _, err := dbo.DeleteAll("test", bson.M{"pass": "pass1111"}) 189 | if err != nil { 190 | dbo.CloseDB() 191 | b.Fatal(err) 192 | return 193 | } 194 | b.RunParallel(func(pb *testing.PB) { 195 | for pb.Next() { 196 | //------------------------------------------------------------------------ 197 | err := dbo.Insert("test", bson.M{"username": "xingo", "pass": "pass1111"}) 198 | if err != nil { 199 | dbo.CloseDB() 200 | b.Fatal(err) 201 | return 202 | } 203 | 204 | err = dbo.DBFindOne("test", bson.M{"username": "xingo"}, func(a bson.M) error { 205 | if a != nil { 206 | b.Log(a) 207 | return nil 208 | } else { 209 | dbo.CloseDB() 210 | b.Fatal("DBFindOne error") 211 | return errors.New("DBFindOne error") 212 | } 213 | 214 | }) 215 | if err != nil { 216 | dbo.CloseDB() 217 | b.Fatal(err) 218 | return 219 | } 220 | //------------------------------------------------------------------------- 221 | //bulk 222 | docs := make([]bson.M, 0) 223 | for i := 0; i < 500; i++ { 224 | docs = append(docs, bson.M{"username": fmt.Sprintf("xingo_%d", i), "pass": "pass1111"}) 225 | } 226 | 227 | err = dbo.BulkInsert("test", docs) 228 | if err != nil { 229 | dbo.CloseDB() 230 | b.Fatal(err) 231 | return 232 | } 233 | } 234 | }) 235 | dbo.CloseDB() 236 | } 237 | 238 | func Test_GrdiFS(t *testing.T){ 239 | dbcfg := NewDbCfg("127.0.0.1", 27017, "xingodb", "", "") 240 | //dbcfg := NewDbCfg("127.0.0.1", 27017, "xingodb", "admin", "admin") 241 | t.Log("url: ", dbcfg.String()) 242 | dbo := NewDbOperate(dbcfg, 5*time.Second) 243 | dbo.OpenDB(func(ms *mgo.Session){ 244 | ms.DB("").C("test.gfs").EnsureIndexKey("filename") 245 | }) 246 | _, err := dbo.RemoveGridFile("test.gfs", "mmomap.db") 247 | if err != nil { 248 | dbo.CloseDB() 249 | t.Fatal(err) 250 | return 251 | } 252 | 253 | type player struct { 254 | UserId int64 255 | Daomond int64 256 | } 257 | type playerQueue struct { 258 | Q []player 259 | } 260 | q := &playerQueue{Q: make([]player, 0)} 261 | for i := int64(0); i < 1000; i++{ 262 | q.Q = append(q.Q, player{i, 1000}) 263 | } 264 | //create gridfs 265 | buff := new(bytes.Buffer) 266 | enc := gob.NewEncoder(buff) 267 | enc.Encode(q) 268 | err = dbo.WriteGridFile("test.gfs", "mmomap.db", buff.Bytes()) 269 | if err != nil{ 270 | dbo.CloseDB() 271 | t.Fatal(err) 272 | return 273 | } 274 | //read gridfs 275 | data, err := dbo.OpenGridFile("test.gfs", "mmomap.db") 276 | if err != nil{ 277 | dbo.CloseDB() 278 | t.Fatal(err) 279 | return 280 | } 281 | buff.Reset() 282 | buff.Write(data) 283 | dec := gob.NewDecoder(buff) 284 | qq := &playerQueue{Q: make([]player, 0)} 285 | err = dec.Decode(qq) 286 | if err != nil{ 287 | dbo.CloseDB() 288 | t.Fatal(err) 289 | return 290 | }else{ 291 | t.Log("success!!!!!!!!!!!!, Q len: ", len(qq.Q)) 292 | } 293 | dbo.CloseDB() 294 | } -------------------------------------------------------------------------------- /logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | /* 4 | https://github.com/donnie4w 5 | */ 6 | import ( 7 | "fmt" 8 | "log" 9 | "os" 10 | "runtime" 11 | "strconv" 12 | "sync" 13 | "time" 14 | ) 15 | 16 | const ( 17 | _VER string = "1.0.2" 18 | ) 19 | 20 | type LEVEL int32 21 | 22 | var logLevel LEVEL = 1 23 | var maxFileSize int64 24 | var maxFileCount int32 25 | var dailyRolling bool = true 26 | var consoleAppender bool = true 27 | var RollingFile bool = false 28 | var logObj *_FILE 29 | 30 | const DATEFORMAT = "2006-01-02" 31 | 32 | type UNIT int64 33 | 34 | const ( 35 | _ = iota 36 | KB UNIT = 1 << (iota * 10) 37 | MB 38 | GB 39 | TB 40 | ) 41 | 42 | const ( 43 | ALL LEVEL = iota 44 | DEBUG 45 | INFO 46 | WARN 47 | ERROR 48 | FATAL 49 | OFF 50 | ) 51 | 52 | var ( 53 | debug_str = "debug" 54 | info_str = "info" 55 | warn_str = "\033[036;1mwarn\033[036;0m" 56 | error_str = "\033[031;1merror\033[031;0m" 57 | fatal_str = "\033[031;1mfatal\033[031;0m" 58 | ) 59 | 60 | const ( 61 | _ = iota 62 | ROLLINGDAILY 63 | ROLLINGFILE 64 | ) 65 | 66 | func init(){ 67 | if runtime.GOOS == "windows"{ 68 | warn_str = "warn" 69 | error_str = "error" 70 | fatal_str = "fatal" 71 | } 72 | } 73 | 74 | type _FILE struct { 75 | dir string 76 | filename string 77 | _suffix int 78 | isCover bool 79 | _date *time.Time 80 | mu *sync.RWMutex 81 | logfile *os.File 82 | lg *log.Logger 83 | } 84 | 85 | func SetPrefix(title string) { 86 | log.SetPrefix(title) 87 | } 88 | 89 | func SetConsole(isConsole bool) { 90 | consoleAppender = isConsole 91 | } 92 | 93 | func SetLevel(_level LEVEL) { 94 | logLevel = _level 95 | } 96 | 97 | func SetRollingFile(fileDir, fileName string, maxNumber int32, maxSize int64, _unit UNIT) { 98 | maxFileCount = maxNumber 99 | maxFileSize = maxSize * int64(_unit) 100 | RollingFile = true 101 | dailyRolling = false 102 | mkdirlog(fileDir) 103 | logObj = &_FILE{dir: fileDir, filename: fileName, isCover: false, mu: new(sync.RWMutex)} 104 | logObj.mu.Lock() 105 | defer logObj.mu.Unlock() 106 | for i := 1; i <= int(maxNumber); i++ { 107 | if isExist(fileDir + "/" + fileName + "." + strconv.Itoa(i)) { 108 | logObj._suffix = i 109 | } else { 110 | break 111 | } 112 | } 113 | if !logObj.isMustRename() { 114 | logObj.logfile, _ = os.OpenFile(fileDir+"/"+fileName, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666) 115 | logObj.lg = log.New(logObj.logfile, "", log.Ldate|log.Ltime|log.Lshortfile) 116 | } else { 117 | logObj.rename() 118 | } 119 | go fileMonitor() 120 | } 121 | 122 | func SetRollingDaily(fileDir, fileName string) { 123 | RollingFile = false 124 | dailyRolling = true 125 | t, _ := time.Parse(DATEFORMAT, time.Now().Format(DATEFORMAT)) 126 | mkdirlog(fileDir) 127 | logObj = &_FILE{dir: fileDir, filename: fileName, _date: &t, isCover: false, mu: new(sync.RWMutex)} 128 | logObj.mu.Lock() 129 | defer logObj.mu.Unlock() 130 | 131 | if !logObj.isMustRename() { 132 | logObj.logfile, _ = os.OpenFile(fileDir+"/"+fileName, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666) 133 | logObj.lg = log.New(logObj.logfile, "", log.Ldate|log.Ltime|log.Lshortfile) 134 | } else { 135 | logObj.rename() 136 | } 137 | } 138 | 139 | func mkdirlog(dir string) (e error) { 140 | _, er := os.Stat(dir) 141 | b := er == nil || os.IsExist(er) 142 | if !b { 143 | if err := os.MkdirAll(dir, 0666); err != nil { 144 | if os.IsPermission(err) { 145 | fmt.Println("create dir error:", err.Error()) 146 | e = err 147 | } 148 | } 149 | } 150 | return 151 | } 152 | 153 | func console(s ...interface{}) { 154 | if consoleAppender { 155 | _, file, line, _ := runtime.Caller(2) 156 | short := file 157 | for i := len(file) - 1; i > 0; i-- { 158 | if file[i] == '/' { 159 | short = file[i+1:] 160 | break 161 | } 162 | } 163 | file = short 164 | log.Println(file, strconv.Itoa(line), s) 165 | } 166 | } 167 | 168 | func catchError() { 169 | if err := recover(); err != nil { 170 | log.Println("err", err) 171 | } 172 | } 173 | 174 | func Debug(v ...interface{}) { 175 | if dailyRolling { 176 | fileCheck() 177 | } 178 | defer catchError() 179 | if logObj != nil { 180 | logObj.mu.RLock() 181 | defer logObj.mu.RUnlock() 182 | } 183 | 184 | if logLevel <= DEBUG { 185 | if logObj != nil { 186 | logObj.lg.Output(2, fmt.Sprintln(debug_str, v)) 187 | } 188 | console(debug_str, v) 189 | } 190 | } 191 | func Info(v ...interface{}) { 192 | if dailyRolling { 193 | fileCheck() 194 | } 195 | defer catchError() 196 | if logObj != nil { 197 | logObj.mu.RLock() 198 | defer logObj.mu.RUnlock() 199 | } 200 | if logLevel <= INFO { 201 | if logObj != nil { 202 | logObj.lg.Output(2, fmt.Sprintln(info_str, v)) 203 | } 204 | console(info_str, v) 205 | } 206 | } 207 | func Warn(v ...interface{}) { 208 | if dailyRolling { 209 | fileCheck() 210 | } 211 | defer catchError() 212 | if logObj != nil { 213 | logObj.mu.RLock() 214 | defer logObj.mu.RUnlock() 215 | } 216 | 217 | if logLevel <= WARN { 218 | if logObj != nil { 219 | logObj.lg.Output(2, fmt.Sprintln(warn_str, v)) 220 | } 221 | console(warn_str, v) 222 | } 223 | } 224 | func Error(v ...interface{}) { 225 | if dailyRolling { 226 | fileCheck() 227 | } 228 | defer catchError() 229 | if logObj != nil { 230 | logObj.mu.RLock() 231 | defer logObj.mu.RUnlock() 232 | } 233 | if logLevel <= ERROR { 234 | if logObj != nil { 235 | logObj.lg.Output(2, fmt.Sprintln(error_str, v)) 236 | } 237 | console(error_str, v) 238 | } 239 | } 240 | func Fatal(v ...interface{}) { 241 | if dailyRolling { 242 | fileCheck() 243 | } 244 | defer catchError() 245 | if logObj != nil { 246 | logObj.mu.RLock() 247 | defer logObj.mu.RUnlock() 248 | } 249 | if logLevel <= FATAL { 250 | if logObj != nil { 251 | logObj.lg.Output(2, fmt.Sprintln(fatal_str, v)) 252 | } 253 | console(fatal_str, v) 254 | } 255 | } 256 | 257 | func (f *_FILE) isMustRename() bool { 258 | if dailyRolling { 259 | t, _ := time.Parse(DATEFORMAT, time.Now().Format(DATEFORMAT)) 260 | if t.After(*f._date) { 261 | return true 262 | } 263 | } else { 264 | if maxFileCount > 1 { 265 | if fileSize(f.dir+"/"+f.filename) >= maxFileSize { 266 | return true 267 | } 268 | } 269 | } 270 | return false 271 | } 272 | 273 | func (f *_FILE) rename() { 274 | if dailyRolling { 275 | fn := f.dir + "/" + f.filename + "." + f._date.Format(DATEFORMAT) 276 | if !isExist(fn) && f.isMustRename() { 277 | if f.logfile != nil { 278 | f.logfile.Close() 279 | } 280 | err := os.Rename(f.dir+"/"+f.filename, fn) 281 | if err != nil { 282 | f.lg.Println("rename err", err.Error()) 283 | } 284 | t, _ := time.Parse(DATEFORMAT, time.Now().Format(DATEFORMAT)) 285 | f._date = &t 286 | f.logfile, _ = os.Create(f.dir + "/" + f.filename) 287 | f.lg = log.New(logObj.logfile, "", log.Ldate|log.Ltime|log.Lshortfile) 288 | } 289 | } else { 290 | f.coverNextOne() 291 | } 292 | } 293 | 294 | func (f *_FILE) nextSuffix() int { 295 | return int(f._suffix%int(maxFileCount) + 1) 296 | } 297 | 298 | func (f *_FILE) coverNextOne() { 299 | f._suffix = f.nextSuffix() 300 | if f.logfile != nil { 301 | f.logfile.Close() 302 | } 303 | if isExist(f.dir + "/" + f.filename + "." + strconv.Itoa(int(f._suffix))) { 304 | os.Remove(f.dir + "/" + f.filename + "." + strconv.Itoa(int(f._suffix))) 305 | } 306 | os.Rename(f.dir+"/"+f.filename, f.dir+"/"+f.filename+"."+strconv.Itoa(int(f._suffix))) 307 | f.logfile, _ = os.Create(f.dir + "/" + f.filename) 308 | f.lg = log.New(logObj.logfile, "", log.Ldate|log.Ltime|log.Lshortfile) 309 | } 310 | 311 | func fileSize(file string) int64 { 312 | fmt.Println("fileSize", file) 313 | f, e := os.Stat(file) 314 | if e != nil { 315 | fmt.Println(e.Error()) 316 | return 0 317 | } 318 | return f.Size() 319 | } 320 | 321 | func isExist(path string) bool { 322 | _, err := os.Stat(path) 323 | return err == nil || os.IsExist(err) 324 | } 325 | 326 | func fileMonitor() { 327 | timer := time.NewTicker(1 * time.Second) 328 | for { 329 | select { 330 | case <-timer.C: 331 | fileCheck() 332 | } 333 | } 334 | } 335 | 336 | func fileCheck() { 337 | defer func() { 338 | if err := recover(); err != nil { 339 | log.Println(err) 340 | } 341 | }() 342 | if logObj != nil && logObj.isMustRename() { 343 | logObj.mu.Lock() 344 | defer logObj.mu.Unlock() 345 | logObj.rename() 346 | } 347 | } 348 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /clusterserver/clusterserver.go: -------------------------------------------------------------------------------- 1 | package clusterserver 2 | 3 | import ( 4 | "fmt" 5 | "github.com/viphxin/xingo/cluster" 6 | "github.com/viphxin/xingo/fnet" 7 | "github.com/viphxin/xingo/fserver" 8 | "github.com/viphxin/xingo/iface" 9 | "github.com/viphxin/xingo/logger" 10 | "github.com/viphxin/xingo/utils" 11 | "net/http" 12 | "os" 13 | "os/signal" 14 | "reflect" 15 | "strings" 16 | "sync" 17 | "time" 18 | "syscall" 19 | ) 20 | 21 | type ClusterServer struct { 22 | Name string 23 | RemoteNodesMgr *cluster.ChildMgr //子节点有 24 | ChildsMgr *cluster.ChildMgr //root节点有 25 | MasterObj *fnet.TcpClient 26 | httpServerMux *http.ServeMux 27 | NetServer iface.Iserver 28 | RootServer iface.Iserver 29 | TelnetServer iface.Iserver 30 | Cconf *cluster.ClusterConf 31 | modules map[string][]interface{} //所有模块统一管理 32 | sync.RWMutex 33 | } 34 | 35 | func DoCSConnectionLost(fconn iface.Iconnection) { 36 | logger.Error("node disconnected from " + utils.GlobalObject.Name) 37 | //子节点掉线 38 | nodename, err := fconn.GetProperty("child") 39 | if err == nil { 40 | GlobalClusterServer.RemoveChild(nodename.(string)) 41 | } 42 | } 43 | 44 | func DoCCConnectionLost(fconn iface.Iclient) { 45 | //父节点掉线 46 | rname, err := fconn.GetProperty("remote") 47 | if err == nil { 48 | GlobalClusterServer.RemoveRemote(rname.(string)) 49 | logger.Error("remote " + rname.(string) + " disconnected from " + utils.GlobalObject.Name) 50 | } 51 | } 52 | 53 | //reconnected to master 54 | func ReConnectMasterCB(fconn iface.Iclient) { 55 | rpc := cluster.NewChild(utils.GlobalObject.Name, GlobalClusterServer.MasterObj) 56 | response, err := rpc.CallChildForResult("TakeProxy", utils.GlobalObject.Name) 57 | if err == nil { 58 | roots, ok := response.Result["roots"] 59 | if ok { 60 | for _, root := range roots.([]string) { 61 | GlobalClusterServer.ConnectToRemote(root) 62 | } 63 | } 64 | } else { 65 | panic(fmt.Sprintf("reconnected to master error: %s", err)) 66 | } 67 | } 68 | 69 | func NewClusterServer(name, path string) *ClusterServer { 70 | logger.SetPrefix(fmt.Sprintf("[%s]", strings.ToUpper(name))) 71 | cconf, err := cluster.NewClusterConf(path) 72 | if err != nil { 73 | panic("cluster conf error!!!") 74 | } 75 | 76 | GlobalClusterServer = &ClusterServer{ 77 | Name: name, 78 | Cconf: cconf, 79 | RemoteNodesMgr: cluster.NewChildMgr(), 80 | ChildsMgr: cluster.NewChildMgr(), 81 | modules: make(map[string][]interface{}, 0), 82 | httpServerMux: http.NewServeMux(), 83 | } 84 | 85 | serverconf, ok := GlobalClusterServer.Cconf.Servers[name] 86 | if !ok { 87 | panic(fmt.Sprintf("no server %s in clusterconf!!!", name)) 88 | } 89 | 90 | utils.GlobalObject.Name = name 91 | utils.GlobalObject.OnClusterClosed = DoCSConnectionLost 92 | utils.GlobalObject.OnClusterCClosed = DoCCConnectionLost 93 | utils.GlobalObject.RpcCProtoc = cluster.NewRpcClientProtocol() 94 | 95 | if utils.GlobalObject.PoolSize > 0 { 96 | //init rpc worker pool 97 | utils.GlobalObject.RpcCProtoc.InitWorker(int32(utils.GlobalObject.PoolSize)) 98 | } 99 | if serverconf.NetPort > 0 { 100 | utils.GlobalObject.Protoc = fnet.NewProtocol() 101 | 102 | } 103 | if serverconf.RootPort > 0 { 104 | utils.GlobalObject.RpcSProtoc = cluster.NewRpcServerProtocol() 105 | } 106 | 107 | if serverconf.Log != "" { 108 | utils.GlobalObject.LogName = serverconf.Log 109 | utils.ReSettingLog() 110 | } 111 | 112 | //telnet debug tool 113 | if serverconf.DebugPort > 0{ 114 | if serverconf.Host != ""{ 115 | GlobalClusterServer.TelnetServer = fserver.NewTcpServer("telnet_server", "tcp4", serverconf.Host, serverconf.DebugPort, 100, cluster.NewTelnetProtocol()) 116 | }else{ 117 | GlobalClusterServer.TelnetServer = fserver.NewTcpServer("telnet_server", "tcp4", "127.0.0.1", serverconf.DebugPort, 100, cluster.NewTelnetProtocol()) 118 | } 119 | } 120 | return GlobalClusterServer 121 | } 122 | 123 | func (this *ClusterServer) StartClusterServer() { 124 | serverconf, ok := this.Cconf.Servers[utils.GlobalObject.Name] 125 | if !ok { 126 | panic("no server in clusterconf!!!") 127 | } 128 | //自动发现注册modules api 129 | modules, ok := this.modules[serverconf.Module] 130 | if ok { 131 | //api 132 | if serverconf.NetPort > 0 { 133 | for _, m := range modules[0].([]interface{}){ 134 | if m != nil{ 135 | this.AddRouter(m) 136 | } 137 | } 138 | } 139 | //http 140 | if len(serverconf.Http) > 0 || len(serverconf.Https) > 0{ 141 | for _, m := range modules[1].([]interface{}){ 142 | if m != nil{ 143 | this.AddHttpRouter(m) 144 | } 145 | } 146 | } 147 | //rpc 148 | for _, m := range modules[2].([]interface{}){ 149 | if m != nil{ 150 | this.AddRpcRouter(m) 151 | } 152 | } 153 | } 154 | 155 | //http server 156 | if len(serverconf.Http) > 0 { 157 | //staticfile handel 158 | if len(serverconf.Http) == 2 { 159 | this.httpServerMux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(serverconf.Http[1].(string))))) 160 | } 161 | httpserver := &http.Server{ 162 | Addr: fmt.Sprintf(":%d", int(serverconf.Http[0].(float64))), 163 | Handler: this.httpServerMux, 164 | ReadTimeout: 5 * time.Second, 165 | WriteTimeout: 5 * time.Second, 166 | MaxHeaderBytes: 1 << 20, //1M 167 | } 168 | httpserver.SetKeepAlivesEnabled(true) 169 | go httpserver.ListenAndServe() 170 | logger.Info(fmt.Sprintf("http://%s:%d start", serverconf.Host, int(serverconf.Http[0].(float64)))) 171 | } else if len(serverconf.Https) > 2 { 172 | //staticfile handel 173 | if len(serverconf.Https) == 4 { 174 | this.httpServerMux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(serverconf.Https[3].(string))))) 175 | } 176 | httpserver := &http.Server{ 177 | Addr: fmt.Sprintf(":%d", int(serverconf.Https[0].(float64))), 178 | Handler: this.httpServerMux, 179 | ReadTimeout: 5 * time.Second, 180 | WriteTimeout: 5 * time.Second, 181 | MaxHeaderBytes: 1 << 20, //1M 182 | } 183 | httpserver.SetKeepAlivesEnabled(true) 184 | go httpserver.ListenAndServeTLS(serverconf.Https[1].(string), serverconf.Https[2].(string)) 185 | logger.Info(fmt.Sprintf("http://%s:%d start", serverconf.Host, int(serverconf.Https[0].(float64)))) 186 | } 187 | //tcp server 188 | if serverconf.NetPort > 0 { 189 | utils.GlobalObject.TcpPort = serverconf.NetPort 190 | if serverconf.Host != ""{ 191 | this.NetServer = fserver.NewTcpServer("xingocluster_net_server", "tcp4", serverconf.Host, serverconf.NetPort, 192 | utils.GlobalObject.MaxConn, utils.GlobalObject.Protoc) 193 | }else{ 194 | this.NetServer = fserver.NewTcpServer("xingocluster_net_server", "tcp4", serverconf.Host, serverconf.NetPort, 195 | utils.GlobalObject.MaxConn, utils.GlobalObject.Protoc) 196 | } 197 | this.NetServer.Start() 198 | } 199 | if serverconf.RootPort > 0 { 200 | if serverconf.Host != ""{ 201 | this.RootServer = fserver.NewTcpServer("xingocluster_root_server", "tcp4", serverconf.Host, serverconf.RootPort, 202 | utils.GlobalObject.IntraMaxConn, utils.GlobalObject.RpcSProtoc) 203 | }else{ 204 | this.RootServer = fserver.NewTcpServer("xingocluster_root_server", "tcp4", serverconf.Host, serverconf.RootPort, 205 | utils.GlobalObject.IntraMaxConn, utils.GlobalObject.RpcSProtoc) 206 | } 207 | this.RootServer.Start() 208 | } 209 | //telnet 210 | if this.TelnetServer != nil{ 211 | logger.Info(fmt.Sprintf("telnet tool start: %s:%d.", serverconf.Host, serverconf.DebugPort)) 212 | this.TelnetServer.Start() 213 | } 214 | 215 | //master 216 | this.ConnectToMaster() 217 | 218 | logger.Info("xingo cluster start success.") 219 | // close 220 | this.WaitSignal() 221 | this.MasterObj.Stop(true) 222 | if this.RootServer != nil { 223 | this.RootServer.Stop() 224 | } 225 | 226 | if this.NetServer != nil { 227 | this.NetServer.Stop() 228 | } 229 | 230 | if this.TelnetServer != nil{ 231 | this.TelnetServer.Stop() 232 | } 233 | logger.Info("xingo cluster stoped.") 234 | } 235 | 236 | func (this *ClusterServer) WaitSignal() { 237 | signal.Notify(utils.GlobalObject.ProcessSignalChan, os.Kill, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGINT) 238 | sig := <-utils.GlobalObject.ProcessSignalChan 239 | //尝试主动通知master checkalive 240 | rpc := cluster.NewChild(utils.GlobalObject.Name, this.MasterObj) 241 | rpc.CallChildNotForResult("ChildOffLine", utils.GlobalObject.Name) 242 | 243 | logger.Info(fmt.Sprintf("server exit. signal: [%s]", sig)) 244 | } 245 | 246 | func (this *ClusterServer) ConnectToMaster() { 247 | master := fnet.NewReConnTcpClient(this.Cconf.Master.Host, this.Cconf.Master.RootPort, utils.GlobalObject.RpcCProtoc, 1024, 60, ReConnectMasterCB) 248 | this.MasterObj = master 249 | master.Start() 250 | //注册到master 251 | rpc := cluster.NewChild(utils.GlobalObject.Name, this.MasterObj) 252 | response, err := rpc.CallChildForResult("TakeProxy", utils.GlobalObject.Name) 253 | if err == nil { 254 | roots, ok := response.Result["roots"] 255 | if ok { 256 | for _, root := range roots.([]string) { 257 | this.ConnectToRemote(root) 258 | } 259 | } 260 | } else { 261 | panic(fmt.Sprintf("connected to master error: %s", err)) 262 | } 263 | } 264 | 265 | func (this *ClusterServer) ConnectToRemote(rname string) { 266 | rserverconf, ok := this.Cconf.Servers[rname] 267 | if ok { 268 | //处理master掉线,重新通知的情况 269 | if _, err := this.GetRemote(rname); err != nil { 270 | rserver := fnet.NewTcpClient(rserverconf.Host, rserverconf.RootPort, utils.GlobalObject.RpcCProtoc) 271 | this.RemoteNodesMgr.AddChild(rname, rserver) 272 | rserver.Start() 273 | rserver.SetProperty("remote", rname) 274 | //takeproxy 275 | child, err := this.RemoteNodesMgr.GetChild(rname) 276 | if err == nil { 277 | child.CallChildNotForResult("TakeProxy", utils.GlobalObject.Name) 278 | } 279 | } else { 280 | logger.Info("Remote connection already exist!") 281 | } 282 | } else { 283 | //未找到节点 284 | logger.Error("ConnectToRemote error. " + rname + " node can`t found!!!") 285 | } 286 | } 287 | 288 | func (this *ClusterServer) AddRouter(router interface{}) { 289 | if utils.GlobalObject.Protoc != nil{ 290 | //add api ---------------start 291 | utils.GlobalObject.Protoc.AddRpcRouter(router) 292 | //add api ---------------end 293 | } 294 | } 295 | 296 | func (this *ClusterServer) AddRpcRouter(router interface{}) { 297 | //add api ---------------start 298 | utils.GlobalObject.RpcCProtoc.AddRpcRouter(router) 299 | if utils.GlobalObject.RpcSProtoc != nil { 300 | utils.GlobalObject.RpcSProtoc.AddRpcRouter(router) 301 | } 302 | //add api ---------------end 303 | } 304 | 305 | /* 306 | 子节点连上来回调 307 | */ 308 | func (this *ClusterServer) AddChild(name string, writer iface.IWriter) { 309 | this.Lock() 310 | defer this.Unlock() 311 | 312 | this.ChildsMgr.AddChild(name, writer) 313 | writer.SetProperty("child", name) 314 | } 315 | 316 | /* 317 | 子节点断开回调 318 | */ 319 | func (this *ClusterServer) RemoveChild(name string) { 320 | this.Lock() 321 | defer this.Unlock() 322 | 323 | this.ChildsMgr.RemoveChild(name) 324 | } 325 | 326 | func (this *ClusterServer) RemoveRemote(name string) { 327 | this.Lock() 328 | defer this.Unlock() 329 | 330 | this.RemoteNodesMgr.RemoveChild(name) 331 | } 332 | 333 | func (this *ClusterServer) GetRemote(name string) (*cluster.Child, error) { 334 | this.RLock() 335 | defer this.RUnlock() 336 | 337 | return this.RemoteNodesMgr.GetChild(name) 338 | } 339 | 340 | /* 341 | 注册模块到分布式服务器 342 | */ 343 | func (this *ClusterServer) AddModule(mname string, apimodule interface{},httpmodule interface{}, rpcmodule interface{}) { 344 | //this.modules[mname] = []interface{}{module, rpcmodule} 345 | if _,ok := this.modules[mname]; ok{ 346 | this.modules[mname][0] = append(this.modules[mname][0].([]interface{}), apimodule) 347 | this.modules[mname][1] = append(this.modules[mname][1].([]interface{}), httpmodule) 348 | this.modules[mname][2] = append(this.modules[mname][2].([]interface{}), rpcmodule) 349 | }else{ 350 | this.modules[mname] = []interface{}{[]interface{}{apimodule}, []interface{}{httpmodule}, []interface{}{rpcmodule}} 351 | } 352 | } 353 | 354 | /* 355 | 注册http的api到分布式服务器 356 | */ 357 | func (this *ClusterServer) AddHttpRouter(router interface{}) { 358 | value := reflect.ValueOf(router) 359 | tp := value.Type() 360 | for i := 0; i < value.NumMethod(); i += 1 { 361 | name := tp.Method(i).Name 362 | uri := fmt.Sprintf("/%s", strings.ToLower(strings.Replace(name, "Handle", "", 1))) 363 | this.httpServerMux.HandleFunc(uri, 364 | utils.HttpRequestWrap(uri, value.Method(i).Interface().(func(http.ResponseWriter, *http.Request)))) 365 | logger.Info("add http url: " + uri) 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /db/mongo/dboperate.go: -------------------------------------------------------------------------------- 1 | package mongo 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "gopkg.in/mgo.v2" 7 | "gopkg.in/mgo.v2/bson" 8 | "time" 9 | "github.com/viphxin/xingo/logger" 10 | ) 11 | 12 | const ( 13 | Strong = 1 14 | Monotonic = 2 15 | ) 16 | 17 | var ( 18 | XINGO_MONGODB_SESSION_NIL_ERR = errors.New("DbOperate session nil.") 19 | XINGO_MONGODB_NOTFOUND_ERR = errors.New("not found!") 20 | XINGO_MONGODB_DBFINDALL_ERR = errors.New("DBFindAll failed,q is nil!") 21 | XINGO_MONGODB_OPENGRIDFILE_ERR = errors.New("OpenGridFile failed!") 22 | XINGO_MONGODB_READGRIDFILE_ERR = errors.New("ReadGridFile failed!") 23 | XINGO_MONGODB_CREATEGRIDFILE_ERR = errors.New("CreateGridFile is nil") 24 | ) 25 | 26 | type DbCfg struct { 27 | DbHost string 28 | DbPort int 29 | DbName string 30 | DbUser string 31 | DbPass string 32 | } 33 | 34 | func NewDbCfg(host string, port int, name , user, pass string) *DbCfg{ 35 | return &DbCfg{ 36 | DbHost: host, 37 | DbPort: port, 38 | DbName: name, 39 | DbUser: user, 40 | DbPass: pass, 41 | } 42 | } 43 | 44 | func (this *DbCfg)String() string{ 45 | url := fmt.Sprintf("mongodb://%s:%s@%s:%d/%s", 46 | this.DbUser, this.DbPass, this.DbHost, this.DbPort, this.DbName) 47 | if this.DbUser == "" || this.DbPass == "" { 48 | url = fmt.Sprintf("mongodb://%s:%d/%s", this.DbHost, this.DbPort, this.DbName) 49 | } 50 | return url 51 | } 52 | 53 | type DbOperate struct { 54 | session *mgo.Session 55 | timeout time.Duration 56 | dbcfg *DbCfg 57 | } 58 | 59 | func NewDbOperate(dbcfg *DbCfg, timeout time.Duration) *DbOperate{ 60 | return &DbOperate{nil, timeout, dbcfg} 61 | } 62 | 63 | func (db *DbOperate) GetDbSession() *mgo.Session { 64 | return db.session 65 | } 66 | 67 | func (this *DbOperate) SetMode(mode int, refresh bool) { 68 | status := mgo.Monotonic 69 | if mode == Strong { 70 | status = mgo.Strong 71 | } else { 72 | status = mgo.Monotonic 73 | } 74 | 75 | this.session.SetMode(status, refresh) 76 | } 77 | 78 | func (this *DbOperate) OpenDB(set_index_func func(ms *mgo.Session)) error { 79 | logger.Info(fmt.Sprintf("DbOperate mongodb connect url: %s\n", this.dbcfg.String())) 80 | 81 | var err error 82 | this.session, err = mgo.DialWithTimeout(this.dbcfg.String(), this.timeout) 83 | if err != nil { 84 | panic(err.Error()) 85 | } 86 | 87 | this.session.SetMode(mgo.Monotonic, true) 88 | //set index 89 | if set_index_func != nil{ 90 | set_index_func(this.session) 91 | } 92 | logger.Info(fmt.Sprintf("DbOperate Connect %v mongodb...OK", this.dbcfg.String())) 93 | return nil 94 | } 95 | 96 | func (this *DbOperate) CloseDB() { 97 | if this.session != nil { 98 | this.session.DB("").Logout() 99 | this.session.Close() 100 | this.session = nil 101 | logger.Info("Disconnect mongodb url: ", this.dbcfg.String()) 102 | } 103 | } 104 | 105 | func (this *DbOperate) RefreshSession() { 106 | this.session.Refresh() 107 | 108 | } 109 | 110 | func (this *DbOperate) Insert(collection string, doc interface{}) error { 111 | if this.session == nil { 112 | return XINGO_MONGODB_SESSION_NIL_ERR 113 | } 114 | 115 | local_session := this.session.Copy() 116 | defer local_session.Close() 117 | 118 | c := local_session.DB("").C(collection) 119 | 120 | return c.Insert(doc) 121 | } 122 | 123 | func (this *DbOperate) StrongInsert(collection string, doc interface{}) error { 124 | if this.session == nil { 125 | return XINGO_MONGODB_SESSION_NIL_ERR 126 | } 127 | 128 | local_session := this.session.Copy() 129 | defer local_session.Close() 130 | 131 | local_session.SetMode(mgo.Strong, true) 132 | 133 | c := local_session.DB("").C(collection) 134 | 135 | return c.Insert(doc) 136 | } 137 | 138 | func (this *DbOperate) Cover(collection string, cond interface{}, change interface{}) error { 139 | if this.session == nil { 140 | return XINGO_MONGODB_SESSION_NIL_ERR 141 | } 142 | 143 | local_session := this.session.Copy() 144 | defer local_session.Close() 145 | 146 | c := local_session.DB("").C(collection) 147 | 148 | return c.Update(cond, change) 149 | } 150 | 151 | func (this *DbOperate) Update(collection string, cond interface{}, change interface{}) error { 152 | if this.session == nil { 153 | return XINGO_MONGODB_SESSION_NIL_ERR 154 | } 155 | 156 | local_session := this.session.Copy() 157 | defer local_session.Close() 158 | 159 | c := local_session.DB("").C(collection) 160 | 161 | return c.Update(cond, bson.M{"$set": change}) 162 | } 163 | 164 | func (this *DbOperate) StrongUpdate(collection string, cond interface{}, change interface{}) error { 165 | if this.session == nil { 166 | return XINGO_MONGODB_SESSION_NIL_ERR 167 | } 168 | 169 | local_session := this.session.Copy() 170 | defer local_session.Close() 171 | 172 | local_session.SetMode(mgo.Strong, true) 173 | c := local_session.DB("").C(collection) 174 | 175 | return c.Update(cond, bson.M{"$set": change}) 176 | } 177 | 178 | func (this *DbOperate) UpdateInsert(collection string, cond interface{}, doc interface{}) error { 179 | if this.session == nil { 180 | return XINGO_MONGODB_SESSION_NIL_ERR 181 | } 182 | 183 | local_session := this.session.Copy() 184 | defer local_session.Close() 185 | 186 | c := local_session.DB("").C(collection) 187 | _, err := c.Upsert(cond, bson.M{"$set": doc}) 188 | if err != nil { 189 | logger.Error(fmt.Sprintf("UpdateInsert failed collection is:%s. cond is:%v", collection, cond)) 190 | } 191 | 192 | return err 193 | } 194 | 195 | func (this *DbOperate) StrongUpdateInsert(collection string, cond interface{}, doc interface{}) error { 196 | if this.session == nil { 197 | return XINGO_MONGODB_SESSION_NIL_ERR 198 | } 199 | 200 | local_session := this.session.Copy() 201 | defer local_session.Close() 202 | 203 | local_session.SetMode(mgo.Strong, true) 204 | 205 | c := local_session.DB("").C(collection) 206 | _, err := c.Upsert(cond, bson.M{"$set": doc}) 207 | if err != nil { 208 | logger.Error(fmt.Sprintf("UpdateInsert failed collection is:%s. cond is:%v", collection, cond)) 209 | } 210 | 211 | return err 212 | } 213 | 214 | func (this *DbOperate) RemoveOne(collection string, cond_name string, cond_value int64) error { 215 | if this.session == nil { 216 | return XINGO_MONGODB_SESSION_NIL_ERR 217 | } 218 | 219 | local_session := this.session.Copy() 220 | defer local_session.Close() 221 | 222 | c := local_session.DB("").C(collection) 223 | err := c.Remove(bson.M{cond_name: cond_value}) 224 | if err != nil && err != mgo.ErrNotFound { 225 | logger.Error(fmt.Sprintf("remove failed from collection:%s. name:%s-value:%d", collection, cond_name, cond_value)) 226 | } 227 | 228 | return err 229 | 230 | } 231 | 232 | func (this *DbOperate) RemoveOneByCond(collection string, cond interface{}) error { 233 | if this.session == nil { 234 | return XINGO_MONGODB_SESSION_NIL_ERR 235 | } 236 | 237 | local_session := this.session.Copy() 238 | defer local_session.Close() 239 | 240 | c := local_session.DB("").C(collection) 241 | err := c.Remove(cond) 242 | 243 | if err != nil && err != mgo.ErrNotFound { 244 | logger.Error(fmt.Sprintf("remove failed from collection:%s. cond :%v, err: %v.", collection, cond, err)) 245 | } 246 | 247 | return err 248 | 249 | } 250 | 251 | func (this *DbOperate) RemoveAll(collection string, cond interface{}) error { 252 | if this.session == nil { 253 | return XINGO_MONGODB_SESSION_NIL_ERR 254 | } 255 | 256 | local_session := this.session.Copy() 257 | defer local_session.Close() 258 | 259 | c := local_session.DB("").C(collection) 260 | change, err := c.RemoveAll(cond) 261 | if err != nil && err != mgo.ErrNotFound { 262 | logger.Error(fmt.Sprintf("DbOperate.RemoveAll failed : %s, %v", collection, cond)) 263 | return err 264 | } 265 | logger.Debug(fmt.Sprintf("DbOperate.RemoveAll: %v, %v", change.Updated, change.Removed)) 266 | return nil 267 | } 268 | 269 | func (this *DbOperate) DBFindOne(collection string, cond interface{}, resHandler func(bson.M) error) error { 270 | if this.session == nil { 271 | return XINGO_MONGODB_SESSION_NIL_ERR 272 | } 273 | 274 | local_session := this.session.Copy() 275 | defer local_session.Close() 276 | 277 | c := local_session.DB("").C(collection) 278 | q := c.Find(cond) 279 | 280 | m := make(bson.M) 281 | if err := q.One(m); err != nil { 282 | if mgo.ErrNotFound != err { 283 | logger.Error(fmt.Sprintf("DBFindOne query falied,return error: %v; name: %v.", err, collection)) 284 | } 285 | return err 286 | } 287 | 288 | if nil != resHandler { 289 | return resHandler(m) 290 | } 291 | 292 | return nil 293 | 294 | } 295 | 296 | func (this *DbOperate) StrongDBFindOne(collection string, cond interface{}, resHandler func(bson.M) error) error { 297 | if this.session == nil { 298 | return XINGO_MONGODB_SESSION_NIL_ERR 299 | } 300 | 301 | local_session := this.session.Copy() 302 | defer local_session.Close() 303 | 304 | local_session.SetMode(mgo.Strong, true) 305 | 306 | c := local_session.DB("").C(collection) 307 | q := c.Find(cond) 308 | 309 | m := make(bson.M) 310 | if err := q.One(m); err != nil { 311 | if mgo.ErrNotFound != err { 312 | logger.Error(fmt.Sprintf("DBFindOne query falied, return error: %v; name: %v.", err, collection)) 313 | } 314 | return err 315 | } 316 | 317 | if nil != resHandler { 318 | return resHandler(m) 319 | } 320 | 321 | return nil 322 | 323 | } 324 | 325 | func (this *DbOperate) DBFindAll(collection string, cond interface{}, resHandler func(bson.M) error) error { 326 | if this.session == nil { 327 | return XINGO_MONGODB_SESSION_NIL_ERR 328 | } 329 | 330 | local_session := this.session.Copy() 331 | defer local_session.Close() 332 | 333 | c := local_session.DB("").C(collection) 334 | q := c.Find(cond) 335 | 336 | if nil == q { 337 | return XINGO_MONGODB_DBFINDALL_ERR 338 | } 339 | 340 | iter := q.Iter() 341 | m := make(bson.M) 342 | for iter.Next(m) == true { 343 | if nil != resHandler { 344 | err := resHandler(m) 345 | if err != nil { 346 | logger.Error(fmt.Sprintf("resHandler error :%v!!!", err)) 347 | return err 348 | } 349 | } 350 | } 351 | 352 | return nil 353 | 354 | } 355 | 356 | func (this *DbOperate) StrongDBFindAll(collection string, cond interface{}, resHandler func(bson.M) error) error { 357 | if this.session == nil { 358 | return XINGO_MONGODB_SESSION_NIL_ERR 359 | } 360 | 361 | local_session := this.session.Copy() 362 | defer local_session.Close() 363 | 364 | local_session.SetMode(mgo.Strong, true) 365 | 366 | c := local_session.DB("").C(collection) 367 | q := c.Find(cond) 368 | 369 | logger.Debug(fmt.Sprintf("[DbOperate.DBFindAll] name:%s,query:%v", collection, cond)) 370 | 371 | if nil == q { 372 | return XINGO_MONGODB_DBFINDALL_ERR 373 | } 374 | iter := q.Iter() 375 | m := make(bson.M) 376 | for iter.Next(m) == true { 377 | if resHandler != nil { 378 | err := resHandler(m) 379 | if err != nil { 380 | logger.Error(fmt.Sprintf("resHandler error :%v!!!", err)) 381 | return err 382 | } 383 | } 384 | } 385 | 386 | return nil 387 | } 388 | 389 | func (this *DbOperate) DBFindAllEx(collection string, cond interface{}, resHandler func(*mgo.Query) error) error { 390 | if this.session == nil { 391 | return XINGO_MONGODB_SESSION_NIL_ERR 392 | } 393 | 394 | local_session := this.session.Copy() 395 | defer local_session.Close() 396 | 397 | c := local_session.DB("").C(collection) 398 | q := c.Find(cond) 399 | 400 | logger.Error(fmt.Sprintf("[DbOperate.DBFindAll] name:%s,query:%v", collection, cond)) 401 | 402 | if nil == q { 403 | return XINGO_MONGODB_DBFINDALL_ERR 404 | } 405 | if nil != resHandler { 406 | return resHandler(q) 407 | } 408 | return nil 409 | } 410 | 411 | func (this *DbOperate) StrongDBFindAllEx(collection string, cond interface{}, resHandler func(*mgo.Query) error) error { 412 | if this.session == nil { 413 | return XINGO_MONGODB_SESSION_NIL_ERR 414 | } 415 | 416 | local_session := this.session.Copy() 417 | defer local_session.Close() 418 | 419 | local_session.SetMode(mgo.Strong, true) 420 | 421 | c := local_session.DB("").C(collection) 422 | q := c.Find(cond) 423 | 424 | logger.Debug(fmt.Sprintf("[DbOperate.DBFindAll] name:%s,query:%v", collection, cond)) 425 | 426 | if nil == q { 427 | return XINGO_MONGODB_DBFINDALL_ERR 428 | } 429 | if nil != resHandler { 430 | return resHandler(q) 431 | } 432 | return nil 433 | } 434 | 435 | func (this *DbOperate) FindAndModify(collection string, cond interface{}, change mgo.Change, val interface{}) error { 436 | if this.session == nil { 437 | return XINGO_MONGODB_SESSION_NIL_ERR 438 | } 439 | 440 | local_session := this.session.Copy() 441 | defer local_session.Close() 442 | 443 | local_session.SetMode(mgo.Strong, true) 444 | c := local_session.DB("").C(collection) 445 | _, err := c.Find(cond).Apply(change, val) 446 | return err 447 | } 448 | 449 | func (this *DbOperate) FindAll(collection string, cond interface{}, all interface{}) error { 450 | if this.session == nil { 451 | return XINGO_MONGODB_SESSION_NIL_ERR 452 | } 453 | 454 | local_session := this.session.Copy() 455 | defer local_session.Close() 456 | 457 | local_session.SetMode(mgo.Strong, true) 458 | c := local_session.DB("").C(collection) 459 | err := c.Find(cond).All(all) 460 | return err 461 | } 462 | 463 | func (this *DbOperate) StrongBatchInsert(collection string, docs ...interface{}) error { 464 | if this.session == nil { 465 | return XINGO_MONGODB_SESSION_NIL_ERR 466 | } 467 | 468 | local_session := this.session.Copy() 469 | defer local_session.Close() 470 | 471 | local_session.SetMode(mgo.Strong, true) 472 | c := local_session.DB("").C(collection) 473 | return c.Insert(docs...) 474 | } 475 | 476 | func (this *DbOperate) FindOne(collection string, cond interface{}, value interface{}) error { 477 | if this.session == nil { 478 | return XINGO_MONGODB_SESSION_NIL_ERR 479 | } 480 | 481 | local_session := this.session.Copy() 482 | defer local_session.Close() 483 | local_session.SetMode(mgo.Strong, true) 484 | c := local_session.DB("").C(collection) 485 | return c.Find(cond).One(value) 486 | } 487 | 488 | //友情提示,如果存在多个document,会报错,请用DeleteAll 489 | func (this *DbOperate) DeleteOne(collection string, cond interface{}) error { 490 | if this.session == nil { 491 | return XINGO_MONGODB_SESSION_NIL_ERR 492 | } 493 | 494 | local_session := this.session.Copy() 495 | defer local_session.Close() 496 | local_session.SetMode(mgo.Strong, true) 497 | c := local_session.DB("").C(collection) 498 | return c.Remove(cond) 499 | } 500 | 501 | func (this *DbOperate) DeleteAll(collection string, cond interface{}) (int, error) { 502 | if this.session == nil { 503 | return 0, XINGO_MONGODB_SESSION_NIL_ERR 504 | } 505 | 506 | local_session := this.session.Copy() 507 | defer local_session.Close() 508 | local_session.SetMode(mgo.Strong, true) 509 | c := local_session.DB("").C(collection) 510 | changeInfo, err := c.RemoveAll(cond) 511 | if err != nil{ 512 | return 0, err 513 | } 514 | return changeInfo.Removed, nil 515 | } 516 | 517 | // gridfs 518 | func (this *DbOperate) OpenGridFile(collection string, filename string) ([]byte, error) { 519 | if this.session == nil { 520 | return nil, XINGO_MONGODB_SESSION_NIL_ERR 521 | } 522 | 523 | local_session := this.session.Copy() 524 | defer local_session.Close() 525 | 526 | gfs := local_session.DB("").GridFS(collection) 527 | // open file only for reading 528 | fsfile, err := gfs.Open(filename) 529 | 530 | if err != nil { 531 | return nil, err 532 | } 533 | 534 | if fsfile == nil { 535 | return nil, XINGO_MONGODB_OPENGRIDFILE_ERR 536 | } 537 | defer fsfile.Close() 538 | 539 | data := make([]byte, fsfile.Size()) 540 | _, err = fsfile.Read(data) 541 | 542 | if err != nil { 543 | return nil, XINGO_MONGODB_READGRIDFILE_ERR 544 | } 545 | return data, nil 546 | } 547 | 548 | type gfsDocId struct { 549 | Id interface{} "_id" 550 | } 551 | 552 | func (this *DbOperate) CreateGridFile(collection string, filename string, resHandler func(*mgo.GridFile) error) error { 553 | 554 | var doc gfsDocId 555 | if this.session == nil { 556 | return XINGO_MONGODB_SESSION_NIL_ERR 557 | } 558 | 559 | local_session := this.session.Copy() 560 | defer local_session.Close() 561 | 562 | gfs := local_session.DB("").GridFS(collection) 563 | 564 | file, err := gfs.Create(filename) 565 | if err != nil { 566 | return err 567 | } 568 | if file == nil { 569 | return XINGO_MONGODB_CREATEGRIDFILE_ERR 570 | } 571 | if resHandler != nil{ 572 | err = resHandler(file) 573 | if err != nil { 574 | return err 575 | } 576 | } 577 | 578 | newfileId := file.Id() 579 | file.Close() 580 | 581 | query := gfs.Files.Find(bson.M{"filename": filename, "_id": bson.M{"$ne": newfileId}}) 582 | iter := query.Iter() 583 | for iter.Next(&doc) { 584 | if e := gfs.RemoveId(doc.Id); e != nil { 585 | err = e 586 | break 587 | } 588 | } 589 | 590 | return err 591 | } 592 | 593 | func (this *DbOperate) GridFileExists(collection string, filename string) (bool, error) { 594 | if this.session == nil { 595 | return false, XINGO_MONGODB_SESSION_NIL_ERR 596 | } 597 | 598 | local_session := this.session.Copy() 599 | defer local_session.Close() 600 | 601 | gfs := local_session.DB("").GridFS(collection) 602 | 603 | query := gfs.Files.Find(bson.M{"filename": filename}) 604 | m := make(bson.M) 605 | if err := query.One(m); err != nil { 606 | if mgo.ErrNotFound != err { 607 | return false, err 608 | } 609 | return false, nil 610 | } 611 | 612 | return true, nil 613 | } 614 | 615 | func (this *DbOperate) RemoveGridFile(collection string, filename string) (bool, error) { 616 | if this.session == nil { 617 | return false, XINGO_MONGODB_SESSION_NIL_ERR 618 | } 619 | 620 | local_session := this.session.Copy() 621 | defer local_session.Close() 622 | 623 | gfs := local_session.DB("").GridFS(collection) 624 | 625 | if err := gfs.Remove(filename); err != nil { 626 | if mgo.ErrNotFound != err { 627 | return false, err 628 | } 629 | } 630 | 631 | return true, nil 632 | } 633 | 634 | func (this *DbOperate) BulkInsertDoc(collection string, docs []interface{}) error { 635 | if this.session == nil { 636 | return XINGO_MONGODB_SESSION_NIL_ERR 637 | } 638 | 639 | local_session := this.session.Copy() 640 | defer local_session.Close() 641 | 642 | c := local_session.DB("").C(collection) 643 | 644 | bulk := c.Bulk() 645 | 646 | bulk.Insert(docs...) 647 | _, err := bulk.Run() 648 | return err 649 | } 650 | 651 | func (this *DbOperate) BulkInsert(collection string, pairs []bson.M) error { 652 | if this.session == nil { 653 | return XINGO_MONGODB_SESSION_NIL_ERR 654 | } 655 | 656 | local_session := this.session.Copy() 657 | defer local_session.Close() 658 | 659 | c := local_session.DB("").C(collection) 660 | 661 | bulk := c.Bulk() 662 | 663 | for i := 0; i < len(pairs); i++ { 664 | bulk.Insert(pairs[i]) 665 | } 666 | _, err := bulk.Run() 667 | return err 668 | } 669 | func (this *DbOperate) BulkUpdate(collection string, pairs []bson.M) error { 670 | if this.session == nil { 671 | return XINGO_MONGODB_SESSION_NIL_ERR 672 | } 673 | 674 | local_session := this.session.Copy() 675 | defer local_session.Close() 676 | 677 | c := local_session.DB("").C(collection) 678 | 679 | bulk := c.Bulk() 680 | 681 | for i := 0; i < len(pairs); i += 2 { 682 | selector := pairs[i] 683 | update := pairs[i+1] 684 | bulk.Update(selector, update) 685 | } 686 | _, err := bulk.Run() 687 | return err 688 | } 689 | 690 | func (this *DbOperate) GetMaxId(collection string, field string) (int64, error) { 691 | var id int64 692 | var present bool 693 | 694 | fnc := func(mq *mgo.Query) error { 695 | q := mq.Sort("-" + field).Limit(1) 696 | m := make(bson.M) 697 | if err := q.One(m); err != nil { 698 | id = 0 699 | } 700 | id, present = m[field].(int64) 701 | if !present { 702 | id = 0 703 | } 704 | return nil 705 | } 706 | err := this.DBFindAllEx(collection, nil, fnc) 707 | if nil != err { 708 | return 0, nil 709 | } 710 | return id, nil 711 | } 712 | 713 | func (this *DbOperate) WriteGridFile(collection string, filename string, data []byte) error { 714 | logger.Debug(fmt.Sprintf("Write grid file: %v, size %v.", filename, len(data))) 715 | 716 | fnc := func(file *mgo.GridFile) error { 717 | _, err := file.Write(data) 718 | return err 719 | } 720 | 721 | err := this.CreateGridFile(collection, filename, fnc) 722 | if err != nil { 723 | logger.Error("Write grid file fail: ", err) 724 | } 725 | return err 726 | } 727 | 728 | func (this *DbOperate) BulkUpsert(collection string, pairs []bson.M) error { 729 | if this.session == nil { 730 | return XINGO_MONGODB_SESSION_NIL_ERR 731 | } 732 | 733 | local_session := this.session.Copy() 734 | defer local_session.Close() 735 | 736 | c := local_session.DB("").C(collection) 737 | 738 | bulk := c.Bulk() 739 | 740 | for i := 0; i < len(pairs); i += 2 { 741 | selector := pairs[i] 742 | update := pairs[i+1] 743 | bulk.Upsert(selector, update) 744 | } 745 | _, err := bulk.Run() 746 | return err 747 | } --------------------------------------------------------------------------------