├── .gitignore ├── .travis.yml ├── CHANGES.md ├── LICENSE ├── README.md ├── benchmark └── main.go ├── codec.go ├── codec ├── binary │ └── binary.go ├── codecreg.go ├── gogopb │ ├── gogopb.go │ └── test │ │ ├── export.bat │ │ ├── export.sh │ │ ├── gogopb_test.go │ │ ├── msgid.go │ │ ├── pb.pb.go │ │ └── pb.proto ├── httpform │ ├── form.go │ └── mapping.go ├── httpjson │ └── json.go ├── json │ └── json.go ├── msgcodec.go └── protoplus │ └── protoplus.go ├── doc ├── architecture.png ├── chatlogger.png ├── customcodec.md ├── custompeer.md ├── customproc.md ├── dirstruct.md ├── faq.md ├── logo.png ├── pbcodec.md ├── peer.md ├── procflow.png ├── procmsg.md └── queue.md ├── err.go ├── event.go ├── examples ├── chat │ ├── client │ │ └── main.go │ ├── proto │ │ └── msg.go │ └── server │ │ └── main.go ├── echo │ ├── client_callback_async.go │ ├── client_rpc_async.go │ ├── client_rpc_sync.go │ ├── main.go │ ├── proto.go │ └── server.go ├── fileserver │ └── main.go └── websocket │ ├── index.html │ └── main.go ├── go.mod ├── go.sum ├── meta.go ├── msglog ├── blocker.go ├── listbase.go ├── logcolor.go └── proc.go ├── peer.go ├── peer ├── gorillaws │ ├── acceptor.go │ ├── connector.go │ ├── log.go │ ├── session.go │ └── syncconn.go ├── http │ ├── acceptor.go │ ├── connector.go │ ├── file.go │ ├── log.go │ ├── respond_html.go │ ├── respond_msg.go │ ├── respond_status.go │ └── session.go ├── iopanic.go ├── mysql │ ├── connector.go │ ├── log.go │ └── wrapper.go ├── peerprop.go ├── peerreg.go ├── procbundle.go ├── property.go ├── redisparam.go ├── redix │ ├── connector.go │ └── log.go ├── runningtag.go ├── sesidentify.go ├── sesmgr.go ├── socketoption.go ├── sqlparam.go ├── sysmsgreg.go ├── tcp │ ├── acceptor.go │ ├── connector.go │ ├── log.go │ ├── session.go │ └── syncconn.go └── udp │ ├── acceptor.go │ ├── connector.go │ ├── log.go │ ├── session.go │ └── trackkey.go ├── peer_db.go ├── peer_http.go ├── peer_tcp.go ├── peer_udp.go ├── peer_ws.go ├── pipe.go ├── proc ├── gorillaws │ ├── hooker.go │ ├── log.go │ ├── setup.go │ └── transmitter.go ├── http │ ├── log.go │ └── setup.go ├── msgdispatcher.go ├── procbundle.go ├── procreg.go ├── syncrecv.go ├── tcp │ ├── hooker.go │ ├── log.go │ ├── setup.go │ └── transmitter.go └── udp │ ├── log.go │ ├── recv.go │ ├── send.go │ └── setup.go ├── processor.go ├── protoc-gen-msg ├── file.go └── main.go ├── queue.go ├── relay ├── Make.sh ├── broadcast.go ├── event.go ├── log.go ├── msg.proto ├── msg_gen.go ├── proc.go └── relay.go ├── rpc ├── Make.sh ├── event.go ├── log.go ├── msg.go ├── msg.proto ├── msg_gen.go ├── proc.go ├── req.go ├── req_async.go ├── req_sync.go ├── req_type.go └── util.go ├── session.go ├── sysmsg.go ├── tests ├── Run.bat ├── Run.sh ├── db │ ├── redis_test.go │ └── sql_test.go ├── echo_test.go ├── gracefulexit_test.go ├── http_test.go ├── httppage_test.go ├── index.tpl ├── issue_test.go ├── log.go ├── proto.go ├── relay_test.go ├── rpc_test.go ├── signaltester.go └── timer_test.go ├── timer ├── after.go ├── loop.go └── loop_test.go └── util ├── addr.go ├── addr_test.go ├── codec.go ├── ioutil.go ├── ioutil_test.go ├── kvfile.go ├── packet.go ├── queue.go └── sys.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | debug.test 3 | codec/gogopb/test/protoc 4 | codec/gogopb/test/protoc-gen-gogofaster 5 | codec/gogopb/test/protoc-gen-msg 6 | .idea -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | git: 4 | depth: 3 5 | quiet: true 6 | 7 | install: 8 | - go get -u -v github.com/davyxu/golog 9 | - go get -u -v github.com/davyxu/goobjfmt 10 | - go get -u -v github.com/davyxu/protoplus/proto 11 | - go get -u -v github.com/gorilla/websocket 12 | - go get -u -v github.com/go-sql-driver/mysql 13 | - go get -u -v github.com/mediocregopher/radix.v2 14 | 15 | go: 16 | - 1.11.x 17 | 18 | script: 19 | - go test -v -test.run TestEchoTCP github.com/davyxu/cellnet/tests 20 | 21 | after_success: 22 | - bash <(curl -s https://codecov.io/bash) 23 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # V4版本(v4分支) 2 | ## 版本特性 3 | 4 | - 将逻辑层与传输层之间的部分抽象为Processor 5 | 6 | - 添加UDP/HTTP协议支持 7 | 8 | - Peer及Session使用接口查询方式使用模块接口 9 | 10 | - 鼓励直接使用回调+Golang的类型分支高效处理消息 11 | 12 | - 新增proc.SyncReceiver,让Peer可以同步接收消息 13 | 14 | ## 变化及修改 15 | 16 | - 现在使用NewPeer统一按类型创建Peer 17 | 18 | - msglog现在不再是一个Processor,而只是函数库 19 | 20 | - Codec接口的对象参数变为interface{},以前是[]byte 21 | 22 | - Codec接口现在需要实现MimeType 23 | 24 | - RegisterMessage现在由proc包的MessageDispatcher对象提供,可选支持 25 | 26 | # V3版本(v3分支) 27 | ## 版本特性 28 | 29 | - 全面使用Handler处理封包接收,发送, 解析, 日志, RPC等结构 30 | 31 | - 新增编码器扩展, 支持混合编码器 32 | 33 | - 新增socket的各种属性设置, 超时处理等 34 | 35 | - 新的计时器api 36 | 37 | - 新增WebSocket支持 38 | 39 | - 底层采用性能更高的纯二进制协议进行错误及rpc消息传输 40 | 41 | 42 | ## 变化及修改 43 | 44 | - 底层去除Protobuf协议依赖(依然支持Protobuf) 45 | 46 | - 大幅降低底层内存分配, GC降低后, benchmark IOPS提升 47 | 48 | - 现在使用cellnet.RegisterMessage注册消息, 回调参数统一为*Event 49 | 50 | - 去除RPC包装, 解包封包的重复代码. 封包变小 51 | 52 | - 编码解码过程放到线程中处理, 提升性能 53 | 54 | 55 | # V2版本(v2分支) 56 | 57 | ## 版本特性 58 | 59 | - 实现单线程逻辑时, 全局只有1个EventQueue. 而不是一个Peer一个Queue 60 | 61 | - EventDispatcher处理回调 62 | 63 | - 处理DB, Timer等不依赖Dispatcher(Peer)逻辑时, 在Post时, Dispatcher可以指定nil, 通过data的函数得到异步返回 64 | 65 | - 去掉MongoDB支持 66 | 67 | 68 | ## 变化及修改 69 | 70 | - 去掉V1中的EventPipe 71 | 72 | - V1中的EventQueue被拆成EventDispatcher及新的EventQueue 73 | 74 | - 新的EventQueue实现了EventPipe的一部分功能 75 | 76 | - 调整EventQueue的Post命名及DelayPost的参数 77 | 78 | - 去掉PeerEvent支持 79 | 80 | - socket.RegisterEventMessage改为socket.RegisterMessage 81 | 82 | - 例子/测试用例使用sample文件夹命名 83 | 84 | # V1版本(v1分支) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (C) 2017 davyxu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /benchmark/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/davyxu/cellnet" 7 | "github.com/davyxu/cellnet/codec" 8 | _ "github.com/davyxu/cellnet/codec/json" 9 | "github.com/davyxu/cellnet/peer" 10 | _ "github.com/davyxu/cellnet/peer/tcp" 11 | "github.com/davyxu/cellnet/proc" 12 | _ "github.com/davyxu/cellnet/proc/tcp" 13 | "github.com/davyxu/cellnet/util" 14 | "github.com/davyxu/golog" 15 | "log" 16 | "os" 17 | "reflect" 18 | "runtime/pprof" 19 | "time" 20 | ) 21 | 22 | func server() { 23 | queue := cellnet.NewEventQueue() 24 | 25 | p := peer.NewGenericPeer("tcp.Acceptor", "server", "127.0.0.1:7701", queue) 26 | 27 | dispatcher := proc.NewMessageDispatcherBindPeer(p, "tcp.ltv") 28 | 29 | dispatcher.RegisterMessage("main.TestEchoACK", func(ev cellnet.Event) { 30 | 31 | msg := ev.Message().(*TestEchoACK) 32 | 33 | ev.Session().Send(&TestEchoACK{ 34 | Msg: msg.Msg, 35 | Value: msg.Value, 36 | }) 37 | }) 38 | 39 | p.Start() 40 | 41 | queue.StartLoop() 42 | } 43 | 44 | func client() { 45 | 46 | queue := cellnet.NewEventQueue() 47 | 48 | p := peer.NewGenericPeer("tcp.Connector", "client", "127.0.0.1:7701", queue) 49 | 50 | rv := proc.NewSyncReceiver(p) 51 | 52 | proc.BindProcessorHandler(p, "tcp.ltv", rv.EventCallback()) 53 | 54 | p.Start() 55 | 56 | queue.StartLoop() 57 | 58 | rv.WaitMessage("cellnet.SessionConnected") 59 | 60 | p.(cellnet.TCPConnector).Session().Send(&TestEchoACK{ 61 | Msg: "hello", 62 | Value: 1234, 63 | }) 64 | 65 | begin := time.Now() 66 | 67 | var lastcheck time.Time 68 | 69 | const total = 10 * time.Second 70 | 71 | for { 72 | 73 | now := time.Now() 74 | 75 | if now.Sub(begin) >= total { 76 | break 77 | } 78 | 79 | if now.Sub(lastcheck) >= time.Second { 80 | fmt.Printf("progress: %d%%\n", now.Sub(begin)*100/total) 81 | lastcheck = now 82 | } 83 | 84 | rv.Recv(func(ev cellnet.Event) { 85 | 86 | ev.Session().Send(&TestEchoACK{ 87 | Msg: "hello", 88 | Value: 1234, 89 | }) 90 | 91 | }) 92 | } 93 | 94 | } 95 | 96 | var profile = flag.String("profile", "", "write cpu profile to file") 97 | 98 | type TestEchoACK struct { 99 | Msg string 100 | Value int32 101 | } 102 | 103 | func (self *TestEchoACK) String() string { return fmt.Sprintf("%+v", *self) } 104 | 105 | func init() { 106 | cellnet.RegisterMessageMeta(&cellnet.MessageMeta{ 107 | Codec: codec.MustGetCodec("json"), 108 | Type: reflect.TypeOf((*TestEchoACK)(nil)).Elem(), 109 | ID: int(util.StringHash("main.TestEchoACK")), 110 | }) 111 | } 112 | 113 | // go build -o bench.exe main.go 114 | // ./bench.exe -profile=mem.pprof 115 | // go tool pprof -alloc_space -top bench.exe mem.pprof 116 | func main() { 117 | 118 | flag.Parse() 119 | 120 | f, err := os.Create(*profile) 121 | if err != nil { 122 | log.Println(*profile) 123 | } 124 | 125 | golog.SetLevelByString("tcpproc", "info") 126 | 127 | server() 128 | 129 | client() 130 | 131 | if f != nil { 132 | pprof.WriteHeapProfile(f) 133 | f.Close() 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /codec.go: -------------------------------------------------------------------------------- 1 | package cellnet 2 | 3 | // 编码包 4 | type Codec interface { 5 | // 将数据转换为字节数组 6 | Encode(msgObj interface{}, ctx ContextSet) (data interface{}, err error) 7 | 8 | // 将字节数组转换为数据 9 | Decode(data interface{}, msgObj interface{}) error 10 | 11 | // 编码器的名字 12 | Name() string 13 | 14 | // 兼容http类型 15 | MimeType() string 16 | } 17 | -------------------------------------------------------------------------------- /codec/binary/binary.go: -------------------------------------------------------------------------------- 1 | package binary 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/codec" 6 | "github.com/davyxu/goobjfmt" 7 | ) 8 | 9 | type binaryCodec struct { 10 | } 11 | 12 | func (self *binaryCodec) Name() string { 13 | return "binary" 14 | } 15 | 16 | func (self *binaryCodec) MimeType() string { 17 | return "application/binary" 18 | } 19 | 20 | func (self *binaryCodec) Encode(msgObj interface{}, ctx cellnet.ContextSet) (data interface{}, err error) { 21 | 22 | return goobjfmt.BinaryWrite(msgObj) 23 | 24 | } 25 | 26 | func (self *binaryCodec) Decode(data interface{}, msgObj interface{}) error { 27 | 28 | return goobjfmt.BinaryRead(data.([]byte), msgObj) 29 | } 30 | 31 | func init() { 32 | 33 | codec.RegisterCodec(new(binaryCodec)) 34 | } 35 | -------------------------------------------------------------------------------- /codec/codecreg.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davyxu/cellnet" 6 | ) 7 | 8 | var registedCodecs []cellnet.Codec 9 | 10 | // 注册编码器 11 | func RegisterCodec(c cellnet.Codec) { 12 | 13 | if GetCodec(c.Name()) != nil { 14 | panic("duplicate codec: " + c.Name()) 15 | } 16 | 17 | registedCodecs = append(registedCodecs, c) 18 | } 19 | 20 | // 获取编码器 21 | func GetCodec(name string) cellnet.Codec { 22 | 23 | for _, c := range registedCodecs { 24 | if c.Name() == name { 25 | return c 26 | } 27 | } 28 | 29 | return nil 30 | } 31 | 32 | // cellnet自带的编码对应包 33 | func getPackageByCodecName(name string) string { 34 | switch name { 35 | case "binary": 36 | return "github.com/davyxu/cellnet/codec/binary" 37 | case "gogopb": 38 | return "github.com/davyxu/cellnet/codec/gogopb" 39 | case "httpjson": 40 | return "github.com/davyxu/cellnet/codec/httpjson" 41 | case "json": 42 | return "github.com/davyxu/cellnet/codec/json" 43 | case "protoplus": 44 | return "github.com/davyxu/cellnet/codec/protoplus" 45 | default: 46 | return "package/to/your/codec" 47 | } 48 | } 49 | 50 | // 指定编码器不存在时,报错 51 | func MustGetCodec(name string) cellnet.Codec { 52 | codec := GetCodec(name) 53 | 54 | if codec == nil { 55 | panic(fmt.Sprintf("codec not found '%s'\ntry to add code below:\nimport (\n _ \"%s\"\n)\n\n", 56 | name, 57 | getPackageByCodecName(name))) 58 | } 59 | 60 | return codec 61 | } 62 | -------------------------------------------------------------------------------- /codec/gogopb/gogopb.go: -------------------------------------------------------------------------------- 1 | package gogopb 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/codec" 6 | "github.com/gogo/protobuf/proto" 7 | ) 8 | 9 | type gogopbCodec struct { 10 | } 11 | 12 | // 编码器的名称 13 | func (self *gogopbCodec) Name() string { 14 | return "gogopb" 15 | } 16 | 17 | func (self *gogopbCodec) MimeType() string { 18 | return "application/x-protobuf" 19 | } 20 | 21 | func (self *gogopbCodec) Encode(msgObj interface{}, ctx cellnet.ContextSet) (data interface{}, err error) { 22 | 23 | return proto.Marshal(msgObj.(proto.Message)) 24 | 25 | } 26 | 27 | func (self *gogopbCodec) Decode(data interface{}, msgObj interface{}) error { 28 | 29 | return proto.Unmarshal(data.([]byte), msgObj.(proto.Message)) 30 | } 31 | 32 | func init() { 33 | 34 | // 注册编码器 35 | codec.RegisterCodec(new(gogopbCodec)) 36 | } 37 | -------------------------------------------------------------------------------- /codec/gogopb/test/export.bat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davyxu/cellnet/916a7eaabb30c7f29d1f69b977b3ba143162e5c9/codec/gogopb/test/export.bat -------------------------------------------------------------------------------- /codec/gogopb/test/export.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 设置GOPATH 4 | CURR=`pwd` 5 | cd ../../../../../../.. 6 | export GOPATH=`pwd` 7 | cd ${CURR} 8 | 9 | # 插件及protoc存放路径 10 | BIN_PATH=${GOPATH}/bin 11 | 12 | # 错误时退出 13 | set -e 14 | 15 | go install -v github.com/gogo/protobuf/protoc-gen-gogofaster 16 | 17 | go install -v github.com/davyxu/cellnet/protoc-gen-msg 18 | 19 | # windows下时,添加后缀名 20 | if [ `go env GOHOSTOS` == "windows" ];then 21 | EXESUFFIX=.exe 22 | fi 23 | 24 | # 生成协议 25 | ${BIN_PATH}/protoc --plugin=protoc-gen-gogofaster=${BIN_PATH}/protoc-gen-gogofaster${EXESUFFIX} --gogofaster_out=. --proto_path="." pb.proto 26 | 27 | # 生成cellnet 消息注册文件 28 | ${BIN_PATH}/protoc --plugin=protoc-gen-msg=${BIN_PATH}/protoc-gen-msg${EXESUFFIX} --msg_out=msgid.go:. --proto_path="." pb.proto 29 | -------------------------------------------------------------------------------- /codec/gogopb/test/gogopb_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/davyxu/cellnet/codec" 5 | _ "github.com/davyxu/cellnet/codec/gogopb" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestGogopbCodec_Codec(t *testing.T) { 11 | 12 | var a ContentACK 13 | a.Value = 67994 14 | a.Msg = "hello" 15 | 16 | data, meta, err := codec.EncodeMessage(&a) 17 | if err != nil { 18 | t.Log(err) 19 | t.FailNow() 20 | } 21 | 22 | outMsg, _, err := codec.DecodeMessage(meta.ID, data) 23 | 24 | if err != nil { 25 | t.Log(err) 26 | t.FailNow() 27 | } 28 | 29 | if !reflect.DeepEqual(&a, outMsg) { 30 | t.FailNow() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /codec/gogopb/test/msgid.go: -------------------------------------------------------------------------------- 1 | // Generated by github.com/davyxu/cellnet/protoc-gen-msg 2 | // DO NOT EDIT! 3 | // Source: pb.proto 4 | 5 | package test 6 | 7 | import ( 8 | "github.com/davyxu/cellnet" 9 | "reflect" 10 | _ "github.com/davyxu/cellnet/codec/gogopb" 11 | "github.com/davyxu/cellnet/codec" 12 | ) 13 | 14 | func init() { 15 | 16 | // pb.proto 17 | cellnet.RegisterMessageMeta(&cellnet.MessageMeta{ 18 | Codec: codec.MustGetCodec("gogopb"), 19 | Type: reflect.TypeOf((*ContentACK)(nil)).Elem(), 20 | ID: 60952, 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /codec/gogopb/test/pb.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package test; 4 | 5 | message ContentACK 6 | { 7 | string Msg = 1; 8 | int32 Value = 2; 9 | } -------------------------------------------------------------------------------- /codec/httpform/form.go: -------------------------------------------------------------------------------- 1 | package httpform 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/codec" 6 | "net/http" 7 | "net/url" 8 | "reflect" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | type httpFormCodec struct { 14 | } 15 | 16 | const defaultMemory = 32 * 1024 * 1024 17 | 18 | func (self *httpFormCodec) Name() string { 19 | return "httpform" 20 | } 21 | 22 | func (self *httpFormCodec) MimeType() string { 23 | return "application/x-www-form-urlencoded" 24 | } 25 | 26 | func anyToString(any interface{}) string { 27 | 28 | switch v := any.(type) { 29 | case string: 30 | return v 31 | case bool: 32 | return strconv.FormatBool(v) 33 | case int: 34 | return strconv.FormatInt(int64(v), 10) 35 | case int32: 36 | return strconv.FormatInt(int64(v), 10) 37 | case int64: 38 | return strconv.FormatInt(int64(v), 10) 39 | case float32: 40 | return strconv.FormatFloat(float64(v), 'f', -1, 32) 41 | case float64: 42 | return strconv.FormatFloat(v, 'f', -1, 64) 43 | default: 44 | panic("Unknown type to convert to string") 45 | } 46 | } 47 | 48 | func structToUrlValues(obj interface{}) url.Values { 49 | objValue := reflect.Indirect(reflect.ValueOf(obj)) 50 | 51 | objType := objValue.Type() 52 | 53 | var formValues = url.Values{} 54 | for i := 0; i < objValue.NumField(); i++ { 55 | 56 | fieldType := objType.Field(i) 57 | 58 | fieldValue := objValue.Field(i) 59 | 60 | formValues.Add(fieldType.Name, anyToString(fieldValue.Interface())) 61 | } 62 | 63 | return formValues 64 | } 65 | 66 | func (self *httpFormCodec) Encode(msgObj interface{}, ctx cellnet.ContextSet) (data interface{}, err error) { 67 | 68 | return strings.NewReader(structToUrlValues(msgObj).Encode()), err 69 | } 70 | 71 | func (self *httpFormCodec) Decode(data interface{}, msgObj interface{}) error { 72 | 73 | req := data.(*http.Request) 74 | 75 | if err := req.ParseForm(); err != nil { 76 | return err 77 | } 78 | req.ParseMultipartForm(defaultMemory) 79 | if err := mapForm(msgObj, req.Form); err != nil { 80 | return err 81 | } 82 | 83 | return nil 84 | } 85 | 86 | func init() { 87 | 88 | codec.RegisterCodec(new(httpFormCodec)) 89 | } 90 | -------------------------------------------------------------------------------- /codec/httpjson/json.go: -------------------------------------------------------------------------------- 1 | package httpjson 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "github.com/davyxu/cellnet" 7 | "github.com/davyxu/cellnet/codec" 8 | "io" 9 | "io/ioutil" 10 | "net/http" 11 | ) 12 | 13 | type httpjsonCodec struct { 14 | } 15 | 16 | // 编码器的名称 17 | func (self *httpjsonCodec) Name() string { 18 | return "httpjson" 19 | } 20 | 21 | func (self *httpjsonCodec) MimeType() string { 22 | return "application/json" 23 | } 24 | 25 | // 将结构体编码为JSON的字节数组 26 | func (self *httpjsonCodec) Encode(msgObj interface{}, ctx cellnet.ContextSet) (data interface{}, err error) { 27 | 28 | bdata, err := json.Marshal(msgObj) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | return bytes.NewReader(bdata), nil 34 | } 35 | 36 | // 将JSON的字节数组解码为结构体 37 | func (self *httpjsonCodec) Decode(data interface{}, msgObj interface{}) error { 38 | 39 | var reader io.Reader 40 | switch v := data.(type) { 41 | case *http.Request: 42 | reader = v.Body 43 | case io.Reader: 44 | reader = v 45 | } 46 | 47 | body, err := ioutil.ReadAll(reader) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | return json.Unmarshal(body, msgObj) 53 | } 54 | 55 | func init() { 56 | 57 | // 注册编码器 58 | codec.RegisterCodec(new(httpjsonCodec)) 59 | } 60 | -------------------------------------------------------------------------------- /codec/json/json.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/davyxu/cellnet" 6 | "github.com/davyxu/cellnet/codec" 7 | ) 8 | 9 | type jsonCodec struct { 10 | } 11 | 12 | // 编码器的名称 13 | func (self *jsonCodec) Name() string { 14 | return "json" 15 | } 16 | 17 | func (self *jsonCodec) MimeType() string { 18 | return "application/json" 19 | } 20 | 21 | // 将结构体编码为JSON的字节数组 22 | func (self *jsonCodec) Encode(msgObj interface{}, ctx cellnet.ContextSet) (data interface{}, err error) { 23 | 24 | return json.Marshal(msgObj) 25 | 26 | } 27 | 28 | // 将JSON的字节数组解码为结构体 29 | func (self *jsonCodec) Decode(data interface{}, msgObj interface{}) error { 30 | 31 | return json.Unmarshal(data.([]byte), msgObj) 32 | } 33 | 34 | func init() { 35 | 36 | // 注册编码器 37 | codec.RegisterCodec(new(jsonCodec)) 38 | } 39 | -------------------------------------------------------------------------------- /codec/msgcodec.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | ) 6 | 7 | // 编码消息, 在使用了带内存池的codec中,可以传入session或peer的ContextSet,保存内存池上下文,默认ctx传nil 8 | func EncodeMessage(msg interface{}, ctx cellnet.ContextSet) (data []byte, meta *cellnet.MessageMeta, err error) { 9 | 10 | // 获取消息元信息 11 | meta = cellnet.MessageMetaByMsg(msg) 12 | if meta == nil { 13 | return nil, nil, cellnet.NewErrorContext("msg not exists", msg) 14 | } 15 | 16 | // 将消息编码为字节数组 17 | var raw interface{} 18 | raw, err = meta.Codec.Encode(msg, ctx) 19 | 20 | if err != nil { 21 | return 22 | } 23 | 24 | data = raw.([]byte) 25 | 26 | return 27 | } 28 | 29 | // 解码消息 30 | func DecodeMessage(msgid int, data []byte) (interface{}, *cellnet.MessageMeta, error) { 31 | 32 | // 获取消息元信息 33 | meta := cellnet.MessageMetaByID(msgid) 34 | 35 | // 消息没有注册 36 | if meta == nil { 37 | return nil, nil, cellnet.NewErrorContext("msg not exists", msgid) 38 | } 39 | 40 | // 创建消息 41 | msg := meta.NewType() 42 | 43 | // 从字节数组转换为消息 44 | err := meta.Codec.Decode(data, msg) 45 | 46 | if err != nil { 47 | return nil, meta, err 48 | } 49 | 50 | return msg, meta, nil 51 | } 52 | 53 | func DecodeMessageByType(data []byte, msg interface{}) (*cellnet.MessageMeta, error) { 54 | 55 | meta := cellnet.MessageMetaByMsg(msg) 56 | // 消息没有注册 57 | if meta == nil { 58 | return nil, cellnet.NewErrorContext("msg not exists", nil) 59 | } 60 | 61 | err := meta.Codec.Decode(data, msg) 62 | if err != nil { 63 | return meta, err 64 | } 65 | 66 | return meta, nil 67 | 68 | } 69 | 70 | // Codec.Encode内分配的资源,在必要时可以回收,例如内存池对象 71 | type CodecRecycler interface { 72 | Free(data interface{}, ctx cellnet.ContextSet) 73 | } 74 | 75 | func FreeCodecResource(codec cellnet.Codec, data interface{}, ctx cellnet.ContextSet) { 76 | 77 | if codec == nil { 78 | return 79 | } 80 | 81 | if recycler, ok := codec.(CodecRecycler); ok { 82 | recycler.Free(data, ctx) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /codec/protoplus/protoplus.go: -------------------------------------------------------------------------------- 1 | package protoplus 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/codec" 6 | "github.com/davyxu/protoplus/proto" 7 | ) 8 | 9 | type protoplus struct { 10 | } 11 | 12 | func (self *protoplus) Name() string { 13 | return "protoplus" 14 | } 15 | 16 | func (self *protoplus) MimeType() string { 17 | return "application/binary" 18 | } 19 | 20 | func (self *protoplus) Encode(msgObj interface{}, ctx cellnet.ContextSet) (data interface{}, err error) { 21 | 22 | return proto.Marshal(msgObj) 23 | 24 | } 25 | 26 | func (self *protoplus) Decode(data interface{}, msgObj interface{}) error { 27 | 28 | return proto.Unmarshal(data.([]byte), msgObj) 29 | } 30 | 31 | func init() { 32 | 33 | codec.RegisterCodec(new(protoplus)) 34 | } 35 | -------------------------------------------------------------------------------- /doc/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davyxu/cellnet/916a7eaabb30c7f29d1f69b977b3ba143162e5c9/doc/architecture.png -------------------------------------------------------------------------------- /doc/chatlogger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davyxu/cellnet/916a7eaabb30c7f29d1f69b977b3ba143162e5c9/doc/chatlogger.png -------------------------------------------------------------------------------- /doc/customcodec.md: -------------------------------------------------------------------------------- 1 | # 定制Codec 2 | cellnet内建提供基本的编码格式,如果有新的编码需要增加时,可以将这些编码注册到cellnet中。 3 | 4 | 定制一个自己的Codec,可以直接参考codec/json包下的例子即可。 -------------------------------------------------------------------------------- /doc/custompeer.md: -------------------------------------------------------------------------------- 1 | # 定制Peer 2 | cellnet内建提供的tcp/udp/http能满足90%的Peer需求,但在有些情况下,仍然需要定制新的Peer。 3 | 4 | **定制Peer的根本目的:让事件收发处理使用统一的接口和流程** 5 | 6 | 例如: 7 | 8 | - cellnet v4版本暂时没有支持websocket的Peer,可以选定一个第三方库,封装定制为自己的Peer,让Websocket的消息收发与tcp协议一模一样。 9 | 10 | - Redis或MySQL连接器可以定制为特殊的Peer,通过统一的Peer Start配合地址就可以方便的发起连接 -------------------------------------------------------------------------------- /doc/customproc.md: -------------------------------------------------------------------------------- 1 | # 定制Processor 2 | cellnet提供的Processor能满足基本的通信封包格式的处理,但在特殊封包定制需求时,仍然需要编写自己的Processor 3 | 4 | **定制Processor的根本目的:复用业务逻辑层与协议通信层之间的逻辑** 5 | 6 | 例如: 7 | - 编写服务器逻辑时,已有Unity3D客户端网络库及封包格式,需要服务器与客户端通信,封包格式为私有协议。 8 | 9 | - 在连接建立后,需要有握手过程(加密秘钥交换,对时等),此时将这个过程封装到Processor中,可以方便Peer的快速复用。 10 | 11 | - 服务器互相连接时,需要标识连接方的服务类型。 12 | 13 | - KCP协议需要建立在UDP协议层,UDP收发过程已由Peer部分完成,KCP协议解析只需要放在Processor即可。 14 | 15 | - RPC(远程过程调用)是建立在tcp协议之上的调用封装,也可以使用Processor来完成封装过程。 16 | 17 | - 消息收发量统计。 18 | 19 | - 使用nsq、mysql的Peer的基础上,还需要对用户消息进行二次封装,以实现扩展消息。 20 | 21 | ## 不需要使用Processor扩展的情况 22 | 23 | - 编码层更换 24 | 25 | 从二进制编码更换为ProtocolBuffer编码,从JSON更换为二进制编码等。这种情况下直接使用codec编码包即可。 26 | 27 | - 具体的业务逻辑 28 | 29 | 30 | ## 内建处理器(tcp.ltv)封包格式 31 | 32 | tcp.ltv使用util/packet.go中的函数解析封包,同时处理粘包问题,封包格式如下: 33 | 34 | 功能 | 类型 | 备注 35 | ---|---|--- 36 | 包体大小(len) | uint16 | 包含用户数据的封包总大小为=2(len) + 2 (msgid) + n(payload),其中len=2(msgid) + n(payload) 37 | 消息ID(msgid) | uint16 | payload中对应codec编码的消息ID。在cellnet提供的Protobuf代码生成插件(protoc-gen-msg)中使用util.StringHash从完整消息名(包名+消息名, 例如:gamedef.PingACK)生成。手动注册和自动生成时,可以自定义消息ID规则。 38 | 用户消息数据(payload) | []byte | 用户的消息大小,对应消息编码后的数据,例如Protobuf编码后的数据。需要使用codec.DecodeMessage包解码。 39 | 40 | 41 | 42 | 封包解析请参考: 43 | https://github.com/davyxu/cellnet/blob/master/proc/tcp/transmitter.go 44 | 45 | 46 | ## 内建处理器(udp.ltv)封包格式 47 | 48 | 注意UDP封包总长度不超过MTU 49 | 50 | 功能 | 类型 | 备注 51 | ---|---|--- 52 | 包体大小(len) | uint16 | 只做UDP包完整性验证。包含用户数据的封包总大小为=2 (msgid) + n(payload),其中len=2(len) + 2(msgid) + n(payload) 53 | 消息ID(msgid) | uint16 | payload中对应codec编码的消息ID。在cellnet提供的Protobuf代码生成插件(protoc-gen-msg)中使用util.StringHash从完整消息名(包名+消息名, 例如:gamedef.PingACK)生成。手动注册和自动生成时,可以自定义消息ID规则。 54 | 用户消息数据(payload) | []byte | 用户的消息大小,对应消息编码后的数据,例如Protobuf编码后的数据。需要使用codec.DecodeMessage包解码。 55 | 56 | 封包解析请参考: 57 | https://github.com/davyxu/cellnet/blob/master/proc/udp/recv.go 58 | -------------------------------------------------------------------------------- /doc/dirstruct.md: -------------------------------------------------------------------------------- 1 | # 目录功能 2 | 3 | ``` 4 | benchmark 性能测试 5 | 6 | codec 编码支持,以及编码注册 7 | 8 | binary 二进制格式编码(github.com/davyxu/goobjfmt) 9 | 10 | httpform http表单格式 11 | 12 | json json编码格式 13 | 14 | examples 例子 15 | 16 | chat 聊天 17 | 18 | echo 回音服务器 19 | 20 | fileserver 使用cellnet内建HTTP服务器支持文件服务 21 | 22 | websocket WebSocket与网页js通信例子 23 | 24 | msglog 消息日志处理 25 | 26 | peer 各种协议的端实现,以及端注册入口及复用组件 27 | 28 | http HTTP协议处理流程及端封装 29 | 30 | tcp TCP协议处理流程及端封装 31 | 32 | udp UDP协议处理流程及端封装 33 | 34 | gorillaws WebSocket协议处理流程及端封装 35 | 36 | proc 各种处理器实现,以及处理器注册入口 37 | 38 | http HTTP消息处理及文件服务实现 39 | 40 | tcp 在TCP peer上构建的tcp处理器集合 41 | 42 | udp 在UDP peer上构建的udp处理器集合 43 | 44 | gorillaws WeboScket的协议处理 45 | 46 | relay 接力消息封装 47 | 48 | rpc 远程过程调用支持 49 | 50 | tests 测试用例 51 | 52 | timer 计时器接口 53 | 54 | util 工具库 55 | 56 | ``` -------------------------------------------------------------------------------- /doc/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | * 报错panic: Peer type not found怎么办? 4 | 5 | 这是由于需要的peer没有找到或者没有注册,使用cellnet内建的peer请在main入口这样导入包 6 | ``` 7 | import ( 8 | _ "github.com/davyxu/cellnet/peer/tcp" 9 | ) 10 | ``` 11 | 12 | * 报错panic: processor not found怎么办? 13 | 14 | 这是由于需要的processor没有找到或者没有注册,使用cellnet内建的processor请在main入口这样导入包 15 | ``` 16 | import ( 17 | _ "github.com/davyxu/cellnet/proc/tcp" 18 | ) 19 | ``` 20 | 21 | * 这个代码的入口在哪里? 怎么编译为exe? 22 | 23 | 本代码是一个网络库, 需要根据需求, 整合逻辑 24 | 25 | * 混合编码有何用途? 26 | 27 | 在与多种语言写成的服务器进行通信时, 可以使用不同的编码, 28 | 最终在逻辑层都是统一的结构能让逻辑编写更加方便, 无需关注底层处理细节 29 | 30 | * 内建支持的二进制协议能与其他语言写成的网络库互通么? 31 | 32 | 完全支持, 但内建二进制协议支持更适合网关与后台服务器. 33 | 不建议与客户端通信中使用, 二进制协议不会忽略使用默认值的字段 34 | 35 | * 所有的例子都是单线程的, 能编写多线程的逻辑么? 36 | 37 | 完全可以, cellnet并没有全局的队列, 只需在Acceptor和Connector创建时, 38 | 传入不同的队列, socket收到的消息就会被放到这个队列中 39 | 传入空队列时, 使用并发方式(io线程)调用处理回调 40 | 41 | * cellnet有网关和db支持么? 42 | 43 | github.com/davyxu/cellnet/peer/mysql MySQL支持 44 | 45 | github.com/davyxu/cellnet/peer/redis Redis支持 46 | 47 | 使用方法请参考tests 48 | 49 | * 如何关闭调试消息日志? 50 | 51 | golog.SetLevelByString(".", "info") // 将所有日志的级别提高到info级别,debug低于info级别所以不再显示 52 | 53 | 第一个参数支持正则表达式,"."表示所有日志。可以指定日志名关闭 54 | 55 | * cellnet能承受多少连接? 56 | 57 | 承受连接数量和操作系统和硬件有关系,cellnet本身承载数受操作系统和硬件约束。 58 | 59 | * cellnet能做百万请求的服务器么? 60 | 61 | 这是架构设计的问题,和cellnet无关。 62 | 63 | * 为什么把客户端关掉,没有收到cellnet.SessionClosed事件,内存不降? 64 | 65 | TCP挥手失败不会触发cellnet.SessionClosed,请通过修改peer上的TCPSocketOption接口的SetSocketDeadline,设置读超时避免这个问题。 66 | 67 | 游戏服务器请自行实现心跳封包逻辑,以避免攻击者只连接不发包消耗服务器资源。 68 | 69 | TCPSocketOption 接口被TCPAcceptor和TCPConnector实现,因此只要拥有这两种peer都可以直接进行设置,例如: 70 | 71 | // 设置30秒读超时和5秒写超时 72 | peer.(TCPSocketOption).SetSocketDeadline(time.Second * 30, time.Second * 5) 73 | 74 | 75 | * cellnet的http能做路由么?能做web服务器么? 76 | 77 | v4版本中添加的http功能是为了方便用通用的方式接收http消息。如果需要专业的http路由,请使用成熟的http服务器,例如gin。 78 | 79 | * 为什么发送20k的TCP封包会断开? 80 | 81 | TCP封包请在逻辑层约束到MTU(Maximum Transmission Unit)范围内,一般路由器设置为1500,考虑到包头损耗,一般用户数据大约在1400字节较为安全。 82 | 83 | 超过MTU后,在某些路由器将发生封包重传,导致传输性能下降,严重的导致丢包乃至连接断开。 84 | 85 | cellnet底层没有拆分逻辑包的设计,请自行使用Processor扩展。 86 | 87 | * 如何获取会话的远程IP? 88 | 89 | util.GetRemoteAddrss获取到地址, util.SpliteAddress拆分出host部分就是ip 90 | -------------------------------------------------------------------------------- /doc/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davyxu/cellnet/916a7eaabb30c7f29d1f69b977b3ba143162e5c9/doc/logo.png -------------------------------------------------------------------------------- /doc/pbcodec.md: -------------------------------------------------------------------------------- 1 | # Protobuf编码安装 2 | 3 | ## Protobuf 编译器protoc下载 4 | 5 | 地址: https://github.com/google/protobuf/releases 6 | 7 | 说明: 8 | * Mac OS 64位平台,选择 protoc-x.x.x-osx-x86_64.zip 9 | 10 | * Windows 64位平台,选择 protoc-x.x.x-win32.zip 11 | 12 | * Linux 64位平台,选择 protoc-x.x.x-linux-x86_64.zip 13 | 14 | ## protoc编译器 15 | 16 | 确保pb编译器protoc的可执行文件放在GOPATH/bin下 17 | 18 | 19 | ## 安装protoc编译器插件(protoc-gen-gogofaster) 20 | 21 | 该插件根据proto内容,生成xx.pb.go文件,包含pb的序列化相关代码 22 | 23 | ``` 24 | go get -v github.com/gogo/protobuf/protoc-gen-gogofaster 25 | 26 | go install -v github.com/gogo/protobuf/protoc-gen-gogofaster 27 | ``` 28 | 29 | ## 安装protoc编译器插件(protoc-gen-msg) 30 | 31 | 该插件根据proto内容,生成msgid.go文件,可以将消息绑定到cellnet的codec系统中,让cellnet可以识别protobuf的消息 32 | 33 | ``` 34 | go get -v github.com/davyxu/cellnet/protoc-gen-msg 35 | 36 | go install -v github.com/davyxu/cellnet/protoc-gen-msg 37 | ``` 38 | 39 | ## 测试 40 | 41 | 执行以下shell 42 | 43 | ``` 44 | ${GOPATH}/github.com/davyxu/cellnet/codec/gogopb/test/export.sh 45 | ``` 46 | 47 | 将使用protoc读取pb.proto并生成pb.pb.go和msgid.go两个文件 48 | 49 | -------------------------------------------------------------------------------- /doc/peer.md: -------------------------------------------------------------------------------- 1 | # 端(Peer) 2 | 3 | ## 侦听和接受连接 4 | 5 | cellnet使用Acceptor接收多个连接,Acceptor是一种Peer(端),连接到Acceptor的Peer叫做Connector。 6 | 7 | 一个Peer拥有很多属性(名称,地址,队列),peer.NewGenericPeer函数封装了属性的设置过程。 8 | 9 | peer.NewGenericPeer创建好的Peer不会产生任何socket操作,对于Acceptor来说,调用Acceptor的Start方法后,才会真正开始socket的侦听 10 | 11 | 使用如下代码创建一个接受器(Acceptor): 12 | 13 | ```golang 14 | queue := cellnet.NewEventQueue() 15 | 16 | // NewGenericPeer参数依次是: peer类型, peer名称(日志中方便查看), 侦听地址,事件队列 17 | peerIns := peer.NewGenericPeer("tcp.Acceptor", "server", "127.0.0.1:8801", queue) 18 | 19 | peerIns.Start() 20 | ``` 21 | 22 | 23 | ## 创建并发起连接 24 | 25 | Connector也是一种Peer,与Acceptor很很多类似的地方,因此创建过程也是类似的。 26 | 27 | 使用如下代码创建一个连接器(Connector): 28 | 29 | ```golang 30 | queue := cellnet.NewEventQueue() 31 | 32 | peerIns := peer.NewGenericPeer("tcp.Connector", "client", "127.0.0.1:8801", queue) 33 | 34 | peerIns.Start("127.0.0.1:8801") 35 | ``` 36 | 37 | ### 自动重连机制 38 | 使用golang接口查询特性,可以在peerIns(Peer或GenericPeer接口类型)中查询TCPConnector接口。 39 | 40 | 该接口可以使用TCPConnector的进一步功能,例如:自动重连。 41 | 42 | 在服务器连接中,自动重连特性是非常方便的,在连接不成功或者断开时,自动重连会等待一定时间再次发起连接,使用SetReconnectDuration方法可以设置。 43 | 44 | ```golang 45 | // 在peerIns接口中查询TCPConnector接口,设置连接超时2秒后自动重连 46 | peerIns.(cellnet.TCPConnector).SetReconnectDuration(2*time.Second) 47 | ``` 48 | 49 | 无需自动重连时,可以使用SetReconnectDuration(0) 50 | 51 | ## cellnet内建Peer类型 52 | 53 | Peer类型 | 对应接口 | 功能 54 | ---|---|--- 55 | tcp.Connector | TCPConnector | tcp发起连接,自动重连 56 | tcp.Acceptor | TCPAcceptor | tcp接受连接,优雅重启 57 | http.Connector | HTTPConnector | http发起请求和接收解码回应 58 | http.Acceptor | HTTPAcceptor | http文件服务,消息收发 59 | udp.Connector | UDPConnector | udp发起连接,无握手 60 | udp.Acceptor | 没有特殊接口 | udp连接管理 61 | gorillaws.Acceptor | WSAcceptor | websocket连接管理,加密连接 -------------------------------------------------------------------------------- /doc/procflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davyxu/cellnet/916a7eaabb30c7f29d1f69b977b3ba143162e5c9/doc/procflow.png -------------------------------------------------------------------------------- /doc/procmsg.md: -------------------------------------------------------------------------------- 1 | # 收发及处理消息 2 | 3 | ## 接收消息 4 | 5 | cellnet使用Processor处理消息的收发过程。 6 | 7 | 使用proc.BindProcessorHandler函数,将一个Peer绑定到某个Processor上,且设置用户消息处理回调。 8 | 9 | 下面代码尝试将peerIns的Peer,绑定"tcp.ltv"处理器,回调函数为func(ev cellnet.Event) { ... } 10 | 11 | ```golang 12 | proc.BindProcessorHandler(peerIns, "tcp.ltv", func(ev cellnet.Event) { 13 | 14 | switch msg := ev.Message().(type) { 15 | // 有新的连接连到8801端口 16 | case *cellnet.SessionAccepted: 17 | log.Debugln("server accepted") 18 | // 有连接从8801端口断开 19 | case *cellnet.SessionClosed: 20 | log.Debugln("session closed: ", ev.Session().ID()) 21 | // 收到某个连接的ChatREQ消息 22 | case *proto.ChatREQ: 23 | 24 | // 准备回应的消息 25 | ack := proto.ChatACK{ 26 | Content: msg.Content, // 聊天内容 27 | Id: ev.Session().ID(), // 使用会话ID作为发送内容的ID 28 | } 29 | 30 | // 在Peer上查询SessionAccessor接口,并遍历Peer上的所有连接,并发送回应消息(即广播消息) 31 | p.(cellnet.SessionAccessor).VisitSession(func(ses cellnet.Session) bool { 32 | 33 | ses.Send(&ack) 34 | 35 | return true 36 | }) 37 | 38 | } 39 | 40 | }) 41 | ``` 42 | ## cellnet内建的Processor列表 43 | Processor类型 | 功能 44 | ---|--- 45 | tcp.ltv | TCP协议,Length-Type-Value封包格式,带RPC,Relay功能 46 | udp.ltv | UDP协议,Length-Type-Value封包格式 47 | http | 基本HTTP处理 48 | 49 | 50 | ## 接收系统事件 51 | 52 | cellnet将系统事件使用消息派发给用户,这种消息并不是由Socket接收,而是由cellnet内部生成的。 53 | 54 | 例如:当TCP socket连接上服务器时,在回调中,将会收到一个*cellnet.SessionConnected的消息 55 | 56 | 下面列出常用的系统事件, 在sysmsg.go文件中定义。 57 | 58 | 适用Peer类型 | 事件类型 | 事件对应消息 59 | ---|---|--- 60 | tcp.Connector | 连接成功 | cellnet.SessionConnected 61 | tcp.Connector | 连接错误 | cellnet.SessionConnectError 62 | tcp.Acceptor | 接受新连接 | cellnet.SessionAccepted 63 | tcp.Acceptor/tcpConnector | 会话关闭 | cellnet.SessionClosed 64 | 65 | 这样设计的益处: 66 | 67 | - 无需为系统事件准备另外的一套处理回调 68 | 69 | - 系统事件对应的消息也可以使用Hooker处理或者过滤 70 | 71 | ## 发送消息 72 | 73 | 发送消息往往发生在收到消息或系统事件时,例如:连接上服务器时,发送消息;收到客户端的消息时发送消息。 74 | 75 | 76 | TCPConnector某些时候需要主动发送消息时,可以这样写 77 | ```golang 78 | peerIns.(cellnet.TCPConnector).Session().Send( &YourMsg{ ... } ) 79 | ``` 80 | 81 | - 不要缓存Event 82 | 83 | cellnet.Event是消息处理的上下文, 可能在底层存在内存池及各种重用行为, 因此不要缓存Event -------------------------------------------------------------------------------- /doc/queue.md: -------------------------------------------------------------------------------- 1 | # 队列 2 | 3 | 队列在cellnet中使用cellnet.Queue接口, 底层由带缓冲的channel实现 4 | 5 | 6 | ## 创建和开启队列 7 | 8 | 队列使用NewEventQueue创建,使用.StartLoop()开启队列事件处理循环,所有投递到队列中的函数回调会在队列自由的goroutine中被调用,逻辑在此时被处理 9 | 10 | 一般在main goroutine中调用queue.Wait阻塞等待队列结束。 11 | 12 | ```golang 13 | queue := cellnet.NewEventQueue() 14 | 15 | // 启动队列 16 | queue.StartLoop() 17 | 18 | // 这里添加队列使用代码 19 | 20 | // 等待队列结束, 调用queue.StopLoop(0)将退出阻塞 21 | queue.Wait() 22 | ``` 23 | 24 | 25 | ## 往队列中投递回调 26 | 队列中的每一个元素为回调,使用queue的Post方法将回调投递到队列中,回调在Post调用时不会马上被调用。 27 | 28 | ```golang 29 | queue.Post(func() { 30 | fmt.Println("hello") 31 | }) 32 | 33 | ``` 34 | 35 | 在cellnet正常使用中,Post方法会被封装到内部被调用。正常情况下,逻辑处理无需主动调用queue.Post方法。 36 | 37 | ## 多线程和单线程 38 | cellnet中,一个队列可以理论上对应一个线程。默认所有例子都是单队列单线程,这种处理方法并不慢。 39 | 40 | 在cellnet中, 队列根据实际逻辑需要定制数量. 但一般情况下, 推荐使用一个队列(单线程)处理逻辑。 41 | 42 | 多线程处理逻辑并不会让逻辑处理更快,过多的同步锁反而会让并发竞态问题变的很严重,导致性能下降严重,同时逻辑编写难度上升。 43 | 44 | 出现耗时任务时,应该使用生产者和消费者模型,生产者将任务通过channel投放给另外一个goroutine中的消费者处理。 45 | 46 | 需要多线程并发处理时,请在所有peer需要传入队列的地方设置为nil。消息将在IO线程中被派发并推给逻辑层处理。 -------------------------------------------------------------------------------- /err.go: -------------------------------------------------------------------------------- 1 | package cellnet 2 | 3 | import "fmt" 4 | 5 | type Error struct { 6 | s string 7 | context interface{} 8 | } 9 | 10 | func (self *Error) Error() string { 11 | 12 | if self.context == nil { 13 | return self.s 14 | } 15 | 16 | return fmt.Sprintf("%s, '%v'", self.s, self.context) 17 | } 18 | 19 | func NewError(s string) error { 20 | 21 | return &Error{s: s} 22 | } 23 | 24 | func NewErrorContext(s string, context interface{}) error { 25 | return &Error{s: s, context: context} 26 | } 27 | -------------------------------------------------------------------------------- /event.go: -------------------------------------------------------------------------------- 1 | package cellnet 2 | 3 | // 接收到消息 4 | type RecvMsgEvent struct { 5 | Ses Session 6 | Msg interface{} 7 | } 8 | 9 | func (self *RecvMsgEvent) Session() Session { 10 | return self.Ses 11 | } 12 | 13 | func (self *RecvMsgEvent) Message() interface{} { 14 | return self.Msg 15 | } 16 | 17 | func (self *RecvMsgEvent) Send(msg interface{}) { 18 | self.Ses.Send(msg) 19 | } 20 | 21 | // 兼容relay和rpc的回消息接口 22 | func (self *RecvMsgEvent) Reply(msg interface{}) { 23 | self.Ses.Send(msg) 24 | } 25 | 26 | // 会话开始发送数据事件 27 | type SendMsgEvent struct { 28 | Ses Session 29 | Msg interface{} // 用户需要发送的消息 30 | } 31 | 32 | func (self *SendMsgEvent) Message() interface{} { 33 | return self.Msg 34 | } 35 | 36 | func (self *SendMsgEvent) Session() Session { 37 | return self.Ses 38 | } 39 | 40 | // rpc, relay, 普通消息 41 | type ReplyEvent interface { 42 | Reply(msg interface{}) 43 | } 44 | -------------------------------------------------------------------------------- /examples/chat/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "github.com/davyxu/cellnet" 6 | "github.com/davyxu/cellnet/examples/chat/proto" 7 | "github.com/davyxu/cellnet/peer" 8 | "github.com/davyxu/cellnet/proc" 9 | "github.com/davyxu/golog" 10 | "os" 11 | "strings" 12 | 13 | _ "github.com/davyxu/cellnet/peer/tcp" 14 | _ "github.com/davyxu/cellnet/proc/tcp" 15 | ) 16 | 17 | var log = golog.New("client") 18 | 19 | func ReadConsole(callback func(string)) { 20 | 21 | for { 22 | 23 | // 从标准输入读取字符串,以\n为分割 24 | text, err := bufio.NewReader(os.Stdin).ReadString('\n') 25 | if err != nil { 26 | break 27 | } 28 | 29 | // 去掉读入内容的空白符 30 | text = strings.TrimSpace(text) 31 | 32 | callback(text) 33 | 34 | } 35 | 36 | } 37 | 38 | func main() { 39 | 40 | // 创建一个事件处理队列,整个客户端只有这一个队列处理事件,客户端属于单线程模型 41 | queue := cellnet.NewEventQueue() 42 | 43 | // 创建一个tcp的连接器,名称为client,连接地址为127.0.0.1:8801,将事件投递到queue队列,单线程的处理(收发封包过程是多线程) 44 | p := peer.NewGenericPeer("tcp.Connector", "client", "127.0.0.1:18801", queue) 45 | 46 | // 设定封包收发处理的模式为tcp的ltv(Length-Type-Value), Length为封包大小,Type为消息ID,Value为消息内容 47 | // 并使用switch处理收到的消息 48 | proc.BindProcessorHandler(p, "tcp.ltv", func(ev cellnet.Event) { 49 | switch msg := ev.Message().(type) { 50 | case *cellnet.SessionConnected: 51 | log.Debugln("client connected") 52 | case *cellnet.SessionClosed: 53 | log.Debugln("client error") 54 | case *proto.ChatACK: 55 | log.Infof("sid%d say: %s", msg.Id, msg.Content) 56 | } 57 | }) 58 | 59 | // 开始发起到服务器的连接 60 | p.Start() 61 | 62 | // 事件队列开始循环 63 | queue.StartLoop() 64 | 65 | log.Debugln("Ready to chat!") 66 | 67 | // 阻塞的从命令行获取聊天输入 68 | ReadConsole(func(str string) { 69 | 70 | p.(interface { 71 | Session() cellnet.Session 72 | }).Session().Send(&proto.ChatREQ{ 73 | Content: str, 74 | }) 75 | 76 | }) 77 | 78 | } 79 | -------------------------------------------------------------------------------- /examples/chat/proto/msg.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davyxu/cellnet" 6 | "github.com/davyxu/cellnet/codec" 7 | 8 | // 使用binary协议,因此匿名引用这个包,底层会自动注册 9 | _ "github.com/davyxu/cellnet/codec/binary" 10 | "github.com/davyxu/cellnet/util" 11 | "reflect" 12 | ) 13 | 14 | type ChatREQ struct { 15 | Content string 16 | } 17 | 18 | type ChatACK struct { 19 | Content string 20 | Id int64 21 | } 22 | 23 | // 用于消息日志打印消息内容 24 | func (self *ChatREQ) String() string { return fmt.Sprintf("%+v", *self) } 25 | func (self *ChatACK) String() string { return fmt.Sprintf("%+v", *self) } 26 | 27 | // 引用消息时,自动注册消息,这个文件可以由代码生成自动生成 28 | func init() { 29 | 30 | cellnet.RegisterMessageMeta(&cellnet.MessageMeta{ 31 | Codec: codec.MustGetCodec("binary"), 32 | Type: reflect.TypeOf((*ChatREQ)(nil)).Elem(), 33 | ID: int(util.StringHash("proto.ChatREQ")), 34 | }) 35 | 36 | cellnet.RegisterMessageMeta(&cellnet.MessageMeta{ 37 | Codec: codec.MustGetCodec("binary"), 38 | Type: reflect.TypeOf((*ChatACK)(nil)).Elem(), 39 | ID: int(util.StringHash("proto.ChatACK")), 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /examples/chat/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/examples/chat/proto" 6 | "github.com/davyxu/cellnet/peer" 7 | "github.com/davyxu/cellnet/proc" 8 | "github.com/davyxu/golog" 9 | 10 | _ "github.com/davyxu/cellnet/peer/tcp" 11 | _ "github.com/davyxu/cellnet/proc/tcp" 12 | ) 13 | 14 | var log = golog.New("server") 15 | 16 | func main() { 17 | 18 | // 创建一个事件处理队列,整个服务器只有这一个队列处理事件,服务器属于单线程服务器 19 | queue := cellnet.NewEventQueue() 20 | 21 | // 创建一个tcp的侦听器,名称为server,连接地址为127.0.0.1:8801,所有连接将事件投递到queue队列,单线程的处理(收发封包过程是多线程) 22 | p := peer.NewGenericPeer("tcp.Acceptor", "server", "127.0.0.1:18801", queue) 23 | 24 | // 设定封包收发处理的模式为tcp的ltv(Length-Type-Value), Length为封包大小,Type为消息ID,Value为消息内容 25 | // 每一个连接收到的所有消息事件(cellnet.Event)都被派发到用户回调, 用户使用switch判断消息类型,并做出不同的处理 26 | proc.BindProcessorHandler(p, "tcp.ltv", func(ev cellnet.Event) { 27 | 28 | switch msg := ev.Message().(type) { 29 | // 有新的连接 30 | case *cellnet.SessionAccepted: 31 | log.Debugln("server accepted") 32 | // 有连接断开 33 | case *cellnet.SessionClosed: 34 | log.Debugln("session closed: ", ev.Session().ID()) 35 | // 收到某个连接的ChatREQ消息 36 | case *proto.ChatREQ: 37 | 38 | // 准备回应的消息 39 | ack := proto.ChatACK{ 40 | Content: msg.Content, // 聊天内容 41 | Id: ev.Session().ID(), // 使用会话ID作为发送内容的ID 42 | } 43 | 44 | // 在Peer上查询SessionAccessor接口,并遍历Peer上的所有连接,并发送回应消息(即广播消息) 45 | p.(cellnet.SessionAccessor).VisitSession(func(ses cellnet.Session) bool { 46 | 47 | ses.Send(&ack) 48 | 49 | return true 50 | }) 51 | 52 | } 53 | 54 | }) 55 | 56 | // 开始侦听 57 | p.Start() 58 | 59 | // 事件队列开始循环 60 | queue.StartLoop() 61 | 62 | // 阻塞等待事件队列结束退出( 在另外的goroutine调用queue.StopLoop() ) 63 | queue.Wait() 64 | 65 | } 66 | -------------------------------------------------------------------------------- /examples/echo/client_callback_async.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davyxu/cellnet" 6 | "github.com/davyxu/cellnet/peer" 7 | "github.com/davyxu/cellnet/proc" 8 | ) 9 | 10 | func clientAsyncCallback() { 11 | 12 | // 等待服务器返回数据 13 | done := make(chan struct{}) 14 | 15 | queue := cellnet.NewEventQueue() 16 | 17 | p := peer.NewGenericPeer("tcp.Connector", "clientAsyncCallback", peerAddress, queue) 18 | 19 | proc.BindProcessorHandler(p, "tcp.ltv", func(ev cellnet.Event) { 20 | 21 | switch msg := ev.Message().(type) { 22 | case *cellnet.SessionConnected: // 已经连接上 23 | fmt.Println("clientAsyncCallback connected") 24 | ev.Session().Send(&TestEchoACK{ 25 | Msg: "hello", 26 | Value: 1234, 27 | }) 28 | case *TestEchoACK: //收到服务器发送的消息 29 | 30 | fmt.Printf("clientAsyncCallback recv %+v\n", msg) 31 | 32 | // 完成操作 33 | done <- struct{}{} 34 | 35 | case *cellnet.SessionClosed: 36 | fmt.Println("clientAsyncCallback closed") 37 | } 38 | }) 39 | 40 | p.Start() 41 | 42 | queue.StartLoop() 43 | 44 | // 等待客户端收到消息 45 | <-done 46 | } 47 | -------------------------------------------------------------------------------- /examples/echo/client_rpc_async.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davyxu/cellnet" 6 | "github.com/davyxu/cellnet/peer" 7 | "github.com/davyxu/cellnet/proc" 8 | "github.com/davyxu/cellnet/rpc" 9 | "time" 10 | ) 11 | 12 | func clientAsyncRPC() { 13 | // 等待服务器返回数据 14 | done := make(chan struct{}) 15 | 16 | queue := cellnet.NewEventQueue() 17 | 18 | p := peer.NewGenericPeer("tcp.Connector", "async rpc", peerAddress, queue) 19 | 20 | // 创建一个消息同步接收器 21 | rv := proc.NewSyncReceiver(p) 22 | 23 | proc.BindProcessorHandler(p, "tcp.ltv", rv.EventCallback()) 24 | 25 | p.Start() 26 | 27 | queue.StartLoop() 28 | 29 | // 等连接上时 30 | rv.WaitMessage("cellnet.SessionConnected") 31 | 32 | // 异步RPC 33 | rpc.Call(p, &TestEchoACK{ 34 | Msg: "hello", 35 | Value: 1234, 36 | }, time.Second, func(raw interface{}) { 37 | 38 | switch result := raw.(type) { 39 | case error: 40 | fmt.Println(result) 41 | default: 42 | fmt.Println(result) 43 | done <- struct{}{} 44 | } 45 | 46 | }) 47 | 48 | // 等待客户端收到消息 49 | <-done 50 | } 51 | -------------------------------------------------------------------------------- /examples/echo/client_rpc_sync.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/peer" 6 | "github.com/davyxu/cellnet/proc" 7 | "github.com/davyxu/cellnet/rpc" 8 | "time" 9 | ) 10 | 11 | func clientSyncRPC() { 12 | 13 | queue := cellnet.NewEventQueue() 14 | 15 | p := peer.NewGenericPeer("tcp.Connector", "async rpc", peerAddress, queue) 16 | 17 | // 创建一个消息同步接收器 18 | rv := proc.NewSyncReceiver(p) 19 | 20 | proc.BindProcessorHandler(p, "tcp.ltv", rv.EventCallback()) 21 | 22 | p.Start() 23 | 24 | queue.StartLoop() 25 | 26 | // 等连接上时 27 | rv.WaitMessage("cellnet.SessionConnected") 28 | 29 | // 同步RPC 30 | rpc.CallSync(p, &TestEchoACK{ 31 | Msg: "hello", 32 | Value: 1234, 33 | }, time.Second) 34 | } 35 | -------------------------------------------------------------------------------- /examples/echo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | _ "github.com/davyxu/cellnet/peer/tcp" // 注册TCP Peer 7 | _ "github.com/davyxu/cellnet/proc/tcp" // 注册TCP Processor 8 | ) 9 | 10 | const peerAddress = "127.0.0.1:17701" 11 | 12 | var clientmode = flag.Int("clientmode", 0, "0: for async recv, 1: for async rpc, 2: for sync rpc") 13 | 14 | func main() { 15 | 16 | flag.Parse() 17 | 18 | server() 19 | 20 | switch *clientmode { 21 | case 0: 22 | fmt.Println("client mode: async callback") 23 | clientAsyncCallback() 24 | case 1: 25 | fmt.Println("client mode: async rpc") 26 | clientAsyncRPC() 27 | case 2: 28 | fmt.Println("client mode: sync rpc") 29 | clientSyncRPC() 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /examples/echo/proto.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davyxu/cellnet" 6 | "github.com/davyxu/cellnet/codec" 7 | "github.com/davyxu/cellnet/util" 8 | "reflect" 9 | ) 10 | 11 | type TestEchoACK struct { 12 | Msg string 13 | Value int32 14 | } 15 | 16 | func (self *TestEchoACK) String() string { return fmt.Sprintf("%+v", *self) } 17 | 18 | // 将消息注册到系统 19 | func init() { 20 | cellnet.RegisterMessageMeta(&cellnet.MessageMeta{ 21 | Codec: codec.MustGetCodec("binary"), 22 | Type: reflect.TypeOf((*TestEchoACK)(nil)).Elem(), 23 | ID: int(util.StringHash("main.TestEchoACK")), 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /examples/echo/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davyxu/cellnet" 6 | "github.com/davyxu/cellnet/peer" 7 | "github.com/davyxu/cellnet/proc" 8 | "github.com/davyxu/cellnet/rpc" 9 | ) 10 | 11 | func server() { 12 | queue := cellnet.NewEventQueue() 13 | 14 | peerIns := peer.NewGenericPeer("tcp.Acceptor", "server", peerAddress, queue) 15 | 16 | proc.BindProcessorHandler(peerIns, "tcp.ltv", func(ev cellnet.Event) { 17 | 18 | switch msg := ev.Message().(type) { 19 | case *cellnet.SessionAccepted: // 接受一个连接 20 | fmt.Println("server accepted") 21 | case *TestEchoACK: // 收到连接发送的消息 22 | 23 | fmt.Printf("server recv %+v\n", msg) 24 | 25 | ack := &TestEchoACK{ 26 | Msg: msg.Msg, 27 | Value: msg.Value, 28 | } 29 | 30 | // 当服务器收到的是一个rpc消息 31 | if rpcevent, ok := ev.(*rpc.RecvMsgEvent); ok { 32 | 33 | // 以RPC方式回应 34 | rpcevent.Reply(ack) 35 | } else { 36 | 37 | // 收到的是普通消息,回普通消息 38 | ev.Session().Send(ack) 39 | } 40 | 41 | case *cellnet.SessionClosed: // 连接断开 42 | fmt.Println("session closed: ", ev.Session().ID()) 43 | } 44 | 45 | }) 46 | 47 | peerIns.Start() 48 | 49 | queue.StartLoop() 50 | } 51 | -------------------------------------------------------------------------------- /examples/fileserver/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/davyxu/cellnet" 7 | "github.com/davyxu/cellnet/peer" 8 | _ "github.com/davyxu/cellnet/peer/http" 9 | "github.com/davyxu/cellnet/proc" 10 | _ "github.com/davyxu/cellnet/proc/http" 11 | ) 12 | 13 | var shareDir = flag.String("share", ".", "folder to share") 14 | var port = flag.Int("port", 9091, "listen port") 15 | 16 | func main() { 17 | 18 | flag.Parse() 19 | 20 | queue := cellnet.NewEventQueue() 21 | 22 | p := peer.NewGenericPeer("http.Acceptor", "httpfile", fmt.Sprintf(":%d", *port), nil).(cellnet.HTTPAcceptor) 23 | p.SetFileServe(".", *shareDir) 24 | 25 | proc.BindProcessorHandler(p, "http", nil) 26 | 27 | p.Start() 28 | queue.StartLoop() 29 | 30 | queue.Wait() 31 | } 32 | -------------------------------------------------------------------------------- /examples/websocket/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

请将此文件拖拽到Chrome中打开,并按F12打开调试窗口,Console中查看结果

8 | 85 | 86 | -------------------------------------------------------------------------------- /examples/websocket/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "net/http" 8 | "reflect" 9 | "time" 10 | 11 | "github.com/davyxu/cellnet" 12 | "github.com/davyxu/cellnet/codec" 13 | "github.com/davyxu/cellnet/peer" 14 | "github.com/davyxu/cellnet/proc" 15 | "github.com/davyxu/golog" 16 | 17 | _ "github.com/davyxu/cellnet/codec/json" 18 | _ "github.com/davyxu/cellnet/peer/gorillaws" 19 | _ "github.com/davyxu/cellnet/proc/gorillaws" 20 | ) 21 | 22 | var log = golog.New("websocket_server") 23 | 24 | type TestEchoACK struct { 25 | Msg string 26 | Value int32 27 | } 28 | 29 | func (self *TestEchoACK) String() string { return fmt.Sprintf("%+v", *self) } 30 | 31 | // 将消息注册到系统 32 | func init() { 33 | cellnet.RegisterMessageMeta(&cellnet.MessageMeta{ 34 | Codec: codec.MustGetCodec("json"), 35 | Type: reflect.TypeOf((*TestEchoACK)(nil)).Elem(), 36 | ID: 1234, 37 | }) 38 | } 39 | 40 | var ( 41 | flagClient = flag.Bool("client", false, "client mode") 42 | ) 43 | 44 | const ( 45 | TestAddress = "http://127.0.0.1:18802/echo" 46 | ) 47 | 48 | func client() { 49 | // 创建一个事件处理队列,整个服务器只有这一个队列处理事件 50 | queue := cellnet.NewEventQueue() 51 | 52 | p := peer.NewGenericPeer("gorillaws.Connector", "client", TestAddress, queue) 53 | p.(cellnet.WSConnector).SetReconnectDuration(time.Second) 54 | 55 | proc.BindProcessorHandler(p, "gorillaws.ltv", func(ev cellnet.Event) { 56 | 57 | switch msg := ev.Message().(type) { 58 | 59 | case *cellnet.SessionConnected: 60 | log.Debugln("server connected") 61 | 62 | ev.Session().Send(&TestEchoACK{ 63 | Msg: "鲍勃", 64 | Value: 331, 65 | }) 66 | // 有连接断开 67 | case *cellnet.SessionClosed: 68 | log.Debugln("session closed: ", ev.Session().ID()) 69 | case *TestEchoACK: 70 | 71 | log.Debugf("recv: %+v %v", msg, []byte("鲍勃")) 72 | 73 | } 74 | }) 75 | 76 | // 开始侦听 77 | p.Start() 78 | 79 | // 事件队列开始循环 80 | queue.StartLoop() 81 | 82 | // 阻塞等待事件队列结束退出( 在另外的goroutine调用queue.StopLoop() ) 83 | queue.Wait() 84 | } 85 | 86 | func server() { 87 | // 创建一个事件处理队列,整个服务器只有这一个队列处理事件,服务器属于单线程服务器 88 | queue := cellnet.NewEventQueue() 89 | 90 | // 侦听在18802端口 91 | p := peer.NewGenericPeer("gorillaws.Acceptor", "server", TestAddress, queue) 92 | 93 | proc.BindProcessorHandler(p, "gorillaws.ltv", func(ev cellnet.Event) { 94 | 95 | switch msg := ev.Message().(type) { 96 | 97 | case *cellnet.SessionAccepted: 98 | log.Debugln("server accepted") 99 | // 有连接断开 100 | case *cellnet.SessionClosed: 101 | log.Debugln("session closed: ", ev.Session().ID()) 102 | case *TestEchoACK: 103 | 104 | log.Debugf("recv: %+v %v", msg, []byte("鲍勃")) 105 | 106 | val, exist := ev.Session().(cellnet.ContextSet).GetContext("request") 107 | if exist { 108 | if req, ok := val.(*http.Request); ok { 109 | raw, _ := json.Marshal(req.Header) 110 | log.Debugf("origin request header: %s", string(raw)) 111 | } 112 | } 113 | 114 | ev.Session().Send(&TestEchoACK{ 115 | Msg: "中文", 116 | Value: 1234, 117 | }) 118 | } 119 | }) 120 | 121 | // 开始侦听 122 | p.Start() 123 | 124 | // 事件队列开始循环 125 | queue.StartLoop() 126 | 127 | // 阻塞等待事件队列结束退出( 在另外的goroutine调用queue.StopLoop() ) 128 | queue.Wait() 129 | 130 | } 131 | 132 | // 默认启动服务器端 133 | // 网页连接服务器: 在浏览器(Chrome)中打开index.html, F12打开调试窗口->Console标签 查看命令行输出 134 | // 注意:日志中的http://127.0.0.1:18802/echo链接是api地址,不是网页地址,直接打开无法正常工作 135 | // 注意:如果http代理/VPN在运行时可能会导致无法连接, 请关闭 136 | // 客户端连接服务器:命令行模式中添加-client 137 | func main() { 138 | 139 | flag.Parse() 140 | 141 | if *flagClient { 142 | client() 143 | } else { 144 | server() 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/davyxu/cellnet 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/davyxu/golog v0.1.0 7 | github.com/davyxu/goobjfmt v0.1.0 8 | github.com/davyxu/protoplus v0.1.0 9 | github.com/go-sql-driver/mysql v1.4.1 10 | github.com/gorilla/websocket v1.4.1 11 | github.com/mediocregopher/radix.v2 v0.0.0-20181115013041-b67df6e626f9 12 | ) 13 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davyxu/golog v0.1.0 h1:SsV3m2x37sCzFaQzq5OHc5S+PE2VMiL7XUx34JCa7mo= 2 | github.com/davyxu/golog v0.1.0/go.mod h1:YwChkFY5dCYt77yuPlWjcR6KlWqVJNbz3WkwC/8WgQk= 3 | github.com/davyxu/goobjfmt v0.1.0 h1:/Kz4X/UL4Jf5xOaQhP5DxzNtcwsfJqsz6ceoePeHBgA= 4 | github.com/davyxu/goobjfmt v0.1.0/go.mod h1:KKrytCtCXny2sEg3ojQfJ4NThhBP8hKw/qM9vhDwgog= 5 | github.com/davyxu/protoplus v0.1.0 h1:iKk94nwYZdEK8r1r4GZDkW7JnmLJTPYQSVUvBLBxsb8= 6 | github.com/davyxu/protoplus v0.1.0/go.mod h1:WzmNYPvYsyks3G81jCJ/vGY2ljs49qFMfCmXGwvxFLA= 7 | github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= 8 | github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 9 | github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= 10 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 11 | github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= 12 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 13 | github.com/mediocregopher/radix.v2 v0.0.0-20181115013041-b67df6e626f9 h1:ViNuGS149jgnttqhc6XQNPwdupEMBXqCx9wtlW7P3sA= 14 | github.com/mediocregopher/radix.v2 v0.0.0-20181115013041-b67df6e626f9/go.mod h1:fLRUbhbSd5Px2yKUaGYYPltlyxi1guJz1vCmo1RQL50= 15 | -------------------------------------------------------------------------------- /msglog/blocker.go: -------------------------------------------------------------------------------- 1 | package msglog 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | ) 6 | 7 | // Deprecated: 当前的某个消息ID是否被屏蔽 8 | func IsBlockedMessageByID(msgid int) bool { 9 | 10 | _, ok := blackListByMsgID.Load(msgid) 11 | 12 | return ok 13 | } 14 | 15 | // Deprecated: 按指定规则(或消息名)屏蔽消息日志, 需要使用完整消息名 例如 proto.MsgName 16 | func BlockMessageLog(nameRule string) (err error, matchCount int) { 17 | 18 | err = cellnet.MessageMetaVisit(nameRule, func(meta *cellnet.MessageMeta) bool { 19 | 20 | blackListByMsgID.Store(int(meta.ID), meta) 21 | matchCount++ 22 | 23 | return true 24 | }) 25 | 26 | return 27 | } 28 | 29 | // Deprecated: 移除被屏蔽的消息 30 | func RemoveBlockedMessage(nameRule string) (err error, matchCount int) { 31 | 32 | err = cellnet.MessageMetaVisit(nameRule, func(meta *cellnet.MessageMeta) bool { 33 | 34 | blackListByMsgID.Delete(int(meta.ID)) 35 | matchCount++ 36 | 37 | return true 38 | }) 39 | 40 | return 41 | } 42 | 43 | // Deprecated: 遍历被屏蔽的消息 44 | func VisitBlockedMessage(callback func(*cellnet.MessageMeta) bool) { 45 | 46 | blackListByMsgID.Range(func(key, value interface{}) bool { 47 | meta := value.(*cellnet.MessageMeta) 48 | 49 | return callback(meta) 50 | }) 51 | 52 | } 53 | -------------------------------------------------------------------------------- /msglog/listbase.go: -------------------------------------------------------------------------------- 1 | package msglog 2 | 3 | import ( 4 | "errors" 5 | "github.com/davyxu/cellnet" 6 | "sync" 7 | ) 8 | 9 | var ( 10 | whiteListByMsgID sync.Map 11 | blackListByMsgID sync.Map 12 | currMsgLogMode = MsgLogMode_BlackList 13 | currMsgLogModeGuard sync.RWMutex 14 | ) 15 | 16 | type MsgLogRule int 17 | 18 | const ( 19 | // 显示所有的消息日志 20 | MsgLogRule_None MsgLogRule = iota 21 | 22 | // 黑名单内的不显示 23 | MsgLogRule_BlackList 24 | 25 | // 只显示白名单的日志 26 | MsgLogRule_WhiteList 27 | ) 28 | 29 | type MsgLogMode int 30 | 31 | const ( 32 | // 显示所有的消息日志 33 | MsgLogMode_ShowAll MsgLogMode = iota 34 | 35 | // 禁用所有的消息日志 36 | MsgLogMode_Mute 37 | 38 | // 黑名单内的不显示 39 | MsgLogMode_BlackList 40 | 41 | // 只显示白名单的日志 42 | MsgLogMode_WhiteList 43 | ) 44 | 45 | // 设置当前的消息日志处理模式 46 | func SetCurrMsgLogMode(mode MsgLogMode) { 47 | currMsgLogModeGuard.Lock() 48 | currMsgLogMode = mode 49 | currMsgLogModeGuard.Unlock() 50 | } 51 | 52 | // 获取当前的消息日志处理模式 53 | func GetCurrMsgLogMode() MsgLogMode { 54 | currMsgLogModeGuard.RLock() 55 | defer currMsgLogModeGuard.RUnlock() 56 | return currMsgLogMode 57 | } 58 | 59 | // 指定某个消息的处理规则, 消息格式: packageName.MsgName 60 | func SetMsgLogRule(name string, rule MsgLogRule) error { 61 | 62 | meta := cellnet.MessageMetaByFullName(name) 63 | if meta == nil { 64 | return errors.New("msg not found") 65 | } 66 | 67 | switch rule { 68 | case MsgLogRule_BlackList: 69 | blackListByMsgID.Store(int(meta.ID), meta) 70 | case MsgLogRule_WhiteList: 71 | whiteListByMsgID.Store(int(meta.ID), meta) 72 | case MsgLogRule_None: 73 | blackListByMsgID.Delete(int(meta.ID)) 74 | whiteListByMsgID.Delete(int(meta.ID)) 75 | } 76 | 77 | return nil 78 | } 79 | 80 | // 能否显示消息日志 81 | func IsMsgLogValid(msgid int) bool { 82 | switch GetCurrMsgLogMode() { 83 | case MsgLogMode_BlackList: // 黑名单里不显示 84 | if _, ok := blackListByMsgID.Load(msgid); ok { 85 | return false 86 | } else { 87 | return true 88 | } 89 | case MsgLogMode_WhiteList: // 只有在白名单里才显示 90 | if _, ok := whiteListByMsgID.Load(msgid); ok { 91 | return true 92 | } else { 93 | return false 94 | } 95 | case MsgLogMode_Mute: 96 | return false 97 | } 98 | 99 | // MsgLogMode_ShowAll 100 | return true 101 | } 102 | 103 | // 遍历消息规则 104 | func VisitMsgLogRule(mode MsgLogMode, callback func(*cellnet.MessageMeta) bool) { 105 | 106 | switch mode { 107 | case MsgLogMode_BlackList: 108 | blackListByMsgID.Range(func(key, value interface{}) bool { 109 | meta := value.(*cellnet.MessageMeta) 110 | 111 | return callback(meta) 112 | }) 113 | case MsgLogMode_WhiteList: 114 | whiteListByMsgID.Range(func(key, value interface{}) bool { 115 | meta := value.(*cellnet.MessageMeta) 116 | 117 | return callback(meta) 118 | }) 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /msglog/logcolor.go: -------------------------------------------------------------------------------- 1 | package msglog 2 | 3 | // 使用github.com/davyxu/golog的cellnet配色方案 4 | const LogColorDefine = ` 5 | { 6 | "Rule":[ 7 | {"Text":"panic:","Color":"Red"}, 8 | {"Text":"[DB]","Color":"Green"}, 9 | {"Text":"#http.listen","Color":"Blue"}, 10 | {"Text":"#http.recv","Color":"Blue"}, 11 | {"Text":"#http.send","Color":"Purple"}, 12 | 13 | {"Text":"#tcp.listen","Color":"Blue"}, 14 | {"Text":"#tcp.accepted","Color":"Blue"}, 15 | {"Text":"#tcp.closed","Color":"Blue"}, 16 | {"Text":"#tcp.recv","Color":"Blue"}, 17 | {"Text":"#tcp.send","Color":"Purple"}, 18 | {"Text":"#tcp.connected","Color":"Blue"}, 19 | 20 | {"Text":"#ws.listen","Color":"Blue"}, 21 | {"Text":"#ws.accepted","Color":"Blue"}, 22 | {"Text":"#ws.closed","Color":"Blue"}, 23 | {"Text":"#ws.recv","Color":"Blue"}, 24 | {"Text":"#ws.send","Color":"Purple"}, 25 | {"Text":"#ws.connected","Color":"Blue"}, 26 | 27 | {"Text":"#udp.listen","Color":"Blue"}, 28 | {"Text":"#udp.recv","Color":"Blue"}, 29 | {"Text":"#udp.send","Color":"Purple"}, 30 | 31 | {"Text":"#rpc.recv","Color":"Blue"}, 32 | {"Text":"#rpc.send","Color":"Purple"}, 33 | 34 | {"Text":"#relay.recv","Color":"Blue"}, 35 | {"Text":"#relay.send","Color":"Purple"}, 36 | 37 | {"Text":"#agent.recv","Color":"Blue"}, 38 | {"Text":"#agent.send","Color":"Purple"} 39 | ] 40 | } 41 | ` 42 | -------------------------------------------------------------------------------- /msglog/proc.go: -------------------------------------------------------------------------------- 1 | package msglog 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/golog" 6 | ) 7 | 8 | // 萃取消息中的消息 9 | type PacketMessagePeeker interface { 10 | Message() interface{} 11 | } 12 | 13 | func WriteRecvLogger(log *golog.Logger, protocol string, ses cellnet.Session, msg interface{}) { 14 | 15 | if log.IsDebugEnabled() { 16 | 17 | if peeker, ok := msg.(PacketMessagePeeker); ok { 18 | msg = peeker.Message() 19 | } 20 | 21 | if IsMsgLogValid(cellnet.MessageToID(msg)) { 22 | peerInfo := ses.Peer().(cellnet.PeerProperty) 23 | 24 | log.Debugf("#%s.recv(%s)@%d len: %d %s | %s", 25 | protocol, 26 | peerInfo.Name(), 27 | ses.ID(), 28 | cellnet.MessageSize(msg), 29 | cellnet.MessageToName(msg), 30 | cellnet.MessageToString(msg)) 31 | } 32 | 33 | } 34 | } 35 | 36 | func WriteSendLogger(log *golog.Logger, protocol string, ses cellnet.Session, msg interface{}) { 37 | 38 | if log.IsDebugEnabled() { 39 | 40 | if peeker, ok := msg.(PacketMessagePeeker); ok { 41 | msg = peeker.Message() 42 | } 43 | 44 | if IsMsgLogValid(cellnet.MessageToID(msg)) { 45 | peerInfo := ses.Peer().(cellnet.PeerProperty) 46 | 47 | log.Debugf("#%s.send(%s)@%d len: %d %s | %s", 48 | protocol, 49 | peerInfo.Name(), 50 | ses.ID(), 51 | cellnet.MessageSize(msg), 52 | cellnet.MessageToName(msg), 53 | cellnet.MessageToString(msg)) 54 | } 55 | 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /peer.go: -------------------------------------------------------------------------------- 1 | package cellnet 2 | 3 | // 端, 可通过接口查询获得更多接口支持,如PeerProperty,ContextSet, SessionAccessor 4 | type Peer interface { 5 | // 开启端,传入地址 6 | Start() Peer 7 | 8 | // 停止通讯端 9 | Stop() 10 | 11 | // Peer的类型(protocol.type),例如tcp.Connector/udp.Acceptor 12 | TypeName() string 13 | } 14 | 15 | // Peer基础属性 16 | type PeerProperty interface { 17 | Name() string 18 | 19 | Address() string 20 | 21 | Queue() EventQueue 22 | 23 | // 设置名称(可选) 24 | SetName(v string) 25 | 26 | // 设置Peer地址 27 | SetAddress(v string) 28 | 29 | // 设置Peer挂接队列(可选) 30 | SetQueue(v EventQueue) 31 | } 32 | 33 | // 基本的通用Peer 34 | type GenericPeer interface { 35 | Peer 36 | PeerProperty 37 | } 38 | 39 | // 设置和获取自定义属性 40 | type ContextSet interface { 41 | // 为对象设置一个自定义属性 42 | SetContext(key interface{}, v interface{}) 43 | 44 | // 从对象上根据key获取一个自定义属性 45 | GetContext(key interface{}) (interface{}, bool) 46 | 47 | // 给定一个值指针, 自动根据值的类型GetContext后设置到值 48 | FetchContext(key, valuePtr interface{}) bool 49 | } 50 | 51 | // 会话访问 52 | type SessionAccessor interface { 53 | 54 | // 获取一个连接 55 | GetSession(int64) Session 56 | 57 | // 遍历连接 58 | VisitSession(func(Session) bool) 59 | 60 | // 连接数量 61 | SessionCount() int 62 | 63 | // 关闭所有连接 64 | CloseAllSession() 65 | } 66 | 67 | // 检查Peer是否正常工作 68 | type PeerReadyChecker interface { 69 | IsReady() bool 70 | } 71 | 72 | // 开启IO层异常捕获,在生产版本对外端口应该打开此设置 73 | type PeerCaptureIOPanic interface { 74 | // 开启IO层异常捕获 75 | EnableCaptureIOPanic(v bool) 76 | 77 | // 获取当前异常捕获值 78 | CaptureIOPanic() bool 79 | } 80 | -------------------------------------------------------------------------------- /peer/gorillaws/acceptor.go: -------------------------------------------------------------------------------- 1 | package gorillaws 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/peer" 6 | "github.com/davyxu/cellnet/util" 7 | "github.com/gorilla/websocket" 8 | "net" 9 | "net/http" 10 | ) 11 | 12 | type wsAcceptor struct { 13 | peer.CoreSessionManager 14 | peer.CorePeerProperty 15 | peer.CoreContextSet 16 | peer.CoreProcBundle 17 | peer.CoreCaptureIOPanic 18 | 19 | certfile string 20 | keyfile string 21 | 22 | upgrader websocket.Upgrader 23 | // 保存端口 24 | listener net.Listener 25 | 26 | sv *http.Server 27 | } 28 | 29 | func (self *wsAcceptor) SetUpgrader(upgrader interface{}) { 30 | self.upgrader = upgrader.(websocket.Upgrader) 31 | } 32 | func (self *wsAcceptor) Port() int { 33 | if self.listener == nil { 34 | return 0 35 | } 36 | 37 | return self.listener.Addr().(*net.TCPAddr).Port 38 | } 39 | 40 | func (self *wsAcceptor) IsReady() bool { 41 | 42 | return self.Port() != 0 43 | } 44 | 45 | func (self *wsAcceptor) SetHttps(certfile, keyfile string) { 46 | 47 | self.certfile = certfile 48 | self.keyfile = keyfile 49 | } 50 | 51 | func (self *wsAcceptor) Start() cellnet.Peer { 52 | 53 | var ( 54 | addrObj *util.Address 55 | err error 56 | raw interface{} 57 | ) 58 | 59 | raw, err = util.DetectPort(self.Address(), func(a *util.Address, port int) (interface{}, error) { 60 | addrObj = a 61 | return net.Listen("tcp", a.HostPortString(port)) 62 | }) 63 | 64 | if err != nil { 65 | log.Errorf("#ws.listen failed(%s) %v", self.Name(), err.Error()) 66 | return self 67 | } 68 | 69 | self.listener = raw.(net.Listener) 70 | 71 | mux := http.NewServeMux() 72 | 73 | if addrObj.Path == "" { 74 | addrObj.Path = "/" 75 | } 76 | 77 | mux.HandleFunc(addrObj.Path, func(w http.ResponseWriter, r *http.Request) { 78 | 79 | c, err := self.upgrader.Upgrade(w, r, nil) 80 | if err != nil { 81 | log.Debugln(err) 82 | return 83 | } 84 | 85 | ses := newSession(c, self, nil) 86 | ses.SetContext("request", r) 87 | ses.Start() 88 | 89 | self.ProcEvent(&cellnet.RecvMsgEvent{Ses: ses, Msg: &cellnet.SessionAccepted{}}) 90 | 91 | }) 92 | 93 | self.sv = &http.Server{Addr: addrObj.HostPortString(self.Port()), Handler: mux} 94 | 95 | go func() { 96 | 97 | log.Infof("#ws.listen(%s) %s", self.Name(), addrObj.String(self.Port())) 98 | 99 | if self.certfile != "" && self.keyfile != "" { 100 | err = self.sv.ServeTLS(self.listener, self.certfile, self.keyfile) 101 | } else { 102 | err = self.sv.Serve(self.listener) 103 | } 104 | 105 | if err != nil { 106 | log.Errorf("#ws.listen. failed(%s) %v", self.Name(), err.Error()) 107 | } 108 | 109 | }() 110 | 111 | return self 112 | } 113 | 114 | func (self *wsAcceptor) Stop() { 115 | 116 | // TODO 关闭处理 117 | } 118 | 119 | func (self *wsAcceptor) TypeName() string { 120 | return "gorillaws.Acceptor" 121 | } 122 | 123 | func init() { 124 | 125 | peer.RegisterPeerCreator(func() cellnet.Peer { 126 | p := &wsAcceptor{ 127 | upgrader: websocket.Upgrader{ 128 | CheckOrigin: func(r *http.Request) bool { 129 | return true 130 | }, 131 | }, 132 | } 133 | 134 | return p 135 | }) 136 | } 137 | -------------------------------------------------------------------------------- /peer/gorillaws/log.go: -------------------------------------------------------------------------------- 1 | package gorillaws 2 | 3 | import ( 4 | "github.com/davyxu/golog" 5 | ) 6 | 7 | var log = golog.New("gorillaws") 8 | -------------------------------------------------------------------------------- /peer/gorillaws/session.go: -------------------------------------------------------------------------------- 1 | package gorillaws 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/peer" 6 | "github.com/davyxu/cellnet/util" 7 | "github.com/gorilla/websocket" 8 | "sync" 9 | ) 10 | 11 | // Socket会话 12 | type wsSession struct { 13 | peer.CoreContextSet 14 | peer.CoreSessionIdentify 15 | *peer.CoreProcBundle 16 | 17 | pInterface cellnet.Peer 18 | 19 | conn *websocket.Conn 20 | 21 | // 退出同步器 22 | exitSync sync.WaitGroup 23 | 24 | // 发送队列 25 | sendQueue *cellnet.Pipe 26 | 27 | cleanupGuard sync.Mutex 28 | 29 | endNotify func() 30 | } 31 | 32 | func (self *wsSession) Peer() cellnet.Peer { 33 | return self.pInterface 34 | } 35 | 36 | // 取原始连接 37 | func (self *wsSession) Raw() interface{} { 38 | if self.conn == nil { 39 | return nil 40 | } 41 | 42 | return self.conn 43 | } 44 | 45 | func (self *wsSession) Close() { 46 | self.sendQueue.Add(nil) 47 | } 48 | 49 | // 发送封包 50 | func (self *wsSession) Send(msg interface{}) { 51 | self.sendQueue.Add(msg) 52 | } 53 | 54 | func (self *wsSession) protectedReadMessage() (msg interface{}, err error) { 55 | 56 | defer func() { 57 | 58 | if err := recover(); err != nil { 59 | log.Errorf("IO read panic: %s", err) 60 | self.Close() 61 | } 62 | 63 | }() 64 | 65 | msg, err = self.ReadMessage(self) 66 | 67 | return 68 | } 69 | 70 | // 接收循环 71 | func (self *wsSession) recvLoop() { 72 | 73 | var capturePanic bool 74 | 75 | if i, ok := self.Peer().(cellnet.PeerCaptureIOPanic); ok { 76 | capturePanic = i.CaptureIOPanic() 77 | } 78 | 79 | for self.conn != nil { 80 | 81 | var msg interface{} 82 | var err error 83 | 84 | if capturePanic { 85 | msg, err = self.protectedReadMessage() 86 | } else { 87 | msg, err = self.ReadMessage(self) 88 | } 89 | 90 | if err != nil { 91 | 92 | log.Debugln(err) 93 | 94 | if !util.IsEOFOrNetReadError(err) { 95 | log.Errorln("session closed:", err) 96 | } 97 | 98 | self.ProcEvent(&cellnet.RecvMsgEvent{Ses: self, Msg: &cellnet.SessionClosed{}}) 99 | break 100 | } 101 | 102 | self.ProcEvent(&cellnet.RecvMsgEvent{Ses: self, Msg: msg}) 103 | } 104 | 105 | self.Close() 106 | 107 | // 通知完成 108 | self.exitSync.Done() 109 | } 110 | 111 | // 发送循环 112 | func (self *wsSession) sendLoop() { 113 | 114 | var writeList []interface{} 115 | 116 | for { 117 | writeList = writeList[0:0] 118 | exit := self.sendQueue.Pick(&writeList) 119 | 120 | // 遍历要发送的数据 121 | for _, msg := range writeList { 122 | 123 | // TODO SendMsgEvent并不是很有意义 124 | self.SendMessage(&cellnet.SendMsgEvent{Ses: self, Msg: msg}) 125 | } 126 | 127 | if exit { 128 | break 129 | } 130 | } 131 | 132 | // 关闭连接 133 | if self.conn != nil { 134 | self.conn.Close() 135 | self.conn = nil 136 | } 137 | 138 | // 通知完成 139 | self.exitSync.Done() 140 | } 141 | 142 | // 启动会话的各种资源 143 | func (self *wsSession) Start() { 144 | 145 | // 将会话添加到管理器 146 | self.Peer().(peer.SessionManager).Add(self) 147 | 148 | // 需要接收和发送线程同时完成时才算真正的完成 149 | self.exitSync.Add(2) 150 | 151 | go func() { 152 | // 等待2个任务结束 153 | self.exitSync.Wait() 154 | 155 | // 将会话从管理器移除 156 | self.Peer().(peer.SessionManager).Remove(self) 157 | 158 | if self.endNotify != nil { 159 | self.endNotify() 160 | } 161 | 162 | }() 163 | 164 | // 启动并发接收goroutine 165 | go self.recvLoop() 166 | 167 | // 启动并发发送goroutine 168 | go self.sendLoop() 169 | } 170 | 171 | func newSession(conn *websocket.Conn, p cellnet.Peer, endNotify func()) *wsSession { 172 | self := &wsSession{ 173 | conn: conn, 174 | endNotify: endNotify, 175 | sendQueue: cellnet.NewPipe(), 176 | pInterface: p, 177 | CoreProcBundle: p.(interface { 178 | GetBundle() *peer.CoreProcBundle 179 | }).GetBundle(), 180 | } 181 | 182 | return self 183 | } 184 | -------------------------------------------------------------------------------- /peer/gorillaws/syncconn.go: -------------------------------------------------------------------------------- 1 | package gorillaws 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/peer" 6 | "github.com/gorilla/websocket" 7 | "net" 8 | "net/http" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | type wsSyncConnector struct { 14 | peer.SessionManager 15 | 16 | peer.CorePeerProperty 17 | peer.CoreContextSet 18 | peer.CoreProcBundle 19 | peer.CoreTCPSocketOption 20 | 21 | defaultSes *wsSession 22 | } 23 | 24 | func (self *wsSyncConnector) Port() int { 25 | if self.defaultSes.conn == nil { 26 | return 0 27 | } 28 | 29 | return self.defaultSes.conn.LocalAddr().(*net.TCPAddr).Port 30 | } 31 | 32 | func (self *wsSyncConnector) Start() cellnet.Peer { 33 | 34 | dialer := websocket.Dialer{} 35 | dialer.Proxy = http.ProxyFromEnvironment 36 | dialer.HandshakeTimeout = 45 * time.Second 37 | 38 | var finalAddress string 39 | if !strings.HasPrefix(self.Address(), "ws://") { 40 | finalAddress = "ws://" + self.Address() 41 | } 42 | 43 | conn, _, err := dialer.Dial(finalAddress, nil) 44 | 45 | // 发生错误时退出 46 | if err != nil { 47 | 48 | log.Debugf("#ws.connect failed(%s)@%d address: %s", self.Name(), self.defaultSes.ID(), self.Address()) 49 | 50 | self.ProcEvent(&cellnet.RecvMsgEvent{Ses: self.defaultSes, Msg: &cellnet.SessionConnectError{}}) 51 | return self 52 | } 53 | 54 | self.defaultSes.conn = conn 55 | 56 | self.defaultSes.Start() 57 | 58 | self.ProcEvent(&cellnet.RecvMsgEvent{Ses: self.defaultSes, Msg: &cellnet.SessionConnected{}}) 59 | 60 | return self 61 | } 62 | 63 | func (self *wsSyncConnector) Session() cellnet.Session { 64 | return self.defaultSes 65 | } 66 | 67 | func (self *wsSyncConnector) SetSessionManager(raw interface{}) { 68 | self.SessionManager = raw.(peer.SessionManager) 69 | } 70 | 71 | func (self *wsSyncConnector) ReconnectDuration() time.Duration { 72 | return 0 73 | } 74 | 75 | func (self *wsSyncConnector) SetReconnectDuration(v time.Duration) { 76 | 77 | } 78 | 79 | func (self *wsSyncConnector) Stop() { 80 | 81 | if self.defaultSes != nil { 82 | self.defaultSes.Close() 83 | } 84 | 85 | } 86 | 87 | func (self *wsSyncConnector) IsReady() bool { 88 | 89 | return self.SessionCount() != 0 90 | } 91 | 92 | func (self *wsSyncConnector) TypeName() string { 93 | return "gorillaws.SyncConnector" 94 | } 95 | 96 | func init() { 97 | 98 | peer.RegisterPeerCreator(func() cellnet.Peer { 99 | self := &wsSyncConnector{ 100 | SessionManager: new(peer.CoreSessionManager), 101 | } 102 | 103 | self.defaultSes = newSession(nil, self, nil) 104 | 105 | return self 106 | }) 107 | } 108 | -------------------------------------------------------------------------------- /peer/http/connector.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davyxu/cellnet" 6 | "github.com/davyxu/cellnet/codec" 7 | "github.com/davyxu/cellnet/peer" 8 | "io" 9 | "net/http" 10 | "reflect" 11 | ) 12 | 13 | type httpConnector struct { 14 | peer.CorePeerProperty 15 | peer.CoreProcBundle 16 | peer.CoreContextSet 17 | } 18 | 19 | func (self *httpConnector) Start() cellnet.Peer { 20 | 21 | return self 22 | } 23 | 24 | func (self *httpConnector) Stop() { 25 | 26 | } 27 | 28 | func getCodec(codecName string) cellnet.Codec { 29 | 30 | if codecName == "" { 31 | codecName = "httpjson" 32 | } 33 | 34 | return codec.MustGetCodec(codecName) 35 | } 36 | 37 | func getTypeName(msg interface{}) string { 38 | if msg == nil { 39 | return "" 40 | } 41 | 42 | return reflect.TypeOf(msg).Elem().Name() 43 | } 44 | 45 | func (self *httpConnector) Request(method, path string, param *cellnet.HTTPRequest) error { 46 | 47 | // 将消息编码为字节数组 48 | reqCodec := getCodec(param.REQCodecName) 49 | data, err := reqCodec.Encode(param.REQMsg, nil) 50 | 51 | if log.IsDebugEnabled() { 52 | log.Debugf("#http.send(%s) '%s' %s | Message(%s) %s", 53 | self.Name(), 54 | method, 55 | path, 56 | getTypeName(param.REQMsg), 57 | cellnet.MessageToString(param.REQMsg)) 58 | } 59 | 60 | url := fmt.Sprintf("http://%s%s", self.Address(), path) 61 | 62 | req, err := http.NewRequest(method, url, data.(io.Reader)) 63 | 64 | if err != nil { 65 | return err 66 | } 67 | 68 | mimeType := reqCodec.(interface { 69 | MimeType() string 70 | }).MimeType() 71 | 72 | req.Header.Set("Content-Type", mimeType) 73 | 74 | resp, err := http.DefaultClient.Do(req) 75 | if err != nil { 76 | return err 77 | } 78 | 79 | defer resp.Body.Close() 80 | 81 | err = getCodec(param.ACKCodecName).Decode(resp.Body, param.ACKMsg) 82 | 83 | if log.IsDebugEnabled() { 84 | log.Debugf("#http.recv(%s) '%s' %s | [%d] Message(%s) %s", 85 | self.Name(), 86 | resp.Request.Method, 87 | path, 88 | resp.StatusCode, 89 | getTypeName(param.ACKMsg), 90 | cellnet.MessageToString(param.ACKMsg)) 91 | } 92 | 93 | return err 94 | } 95 | 96 | func (self *httpConnector) TypeName() string { 97 | return "http.Connector" 98 | } 99 | 100 | func init() { 101 | 102 | peer.RegisterPeerCreator(func() cellnet.Peer { 103 | p := &httpConnector{} 104 | 105 | return p 106 | }) 107 | } 108 | -------------------------------------------------------------------------------- /peer/http/file.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | "path" 7 | "path/filepath" 8 | "strings" 9 | ) 10 | 11 | func (self *httpAcceptor) SetFileServe(dir string, root string) { 12 | 13 | self.httpDir = dir 14 | self.httpRoot = root 15 | } 16 | 17 | func (self *httpAcceptor) GetDir() http.Dir { 18 | 19 | if filepath.IsAbs(self.httpDir) { 20 | return http.Dir(self.httpDir) 21 | } else { 22 | return http.Dir(filepath.Join(self.httpRoot, self.httpDir)) 23 | } 24 | 25 | //workDir, _ := os.Getwd() 26 | //log.Debugf("Http serve file: %s (%s)", self.dir, workDir) 27 | } 28 | 29 | func (self *httpAcceptor) ServeFile(res http.ResponseWriter, req *http.Request, dir http.Dir) (error, bool) { 30 | if req.Method != "GET" && req.Method != "HEAD" { 31 | return nil, false 32 | } 33 | 34 | file := req.URL.Path 35 | 36 | f, err := dir.Open(file) 37 | if err != nil { 38 | 39 | if err != nil { 40 | return errNotFound, false 41 | } 42 | } 43 | defer f.Close() 44 | 45 | fi, err := f.Stat() 46 | if err != nil { 47 | return errNotFound, false 48 | } 49 | 50 | // try to serve index file 51 | if fi.IsDir() { 52 | // redirect if missing trailing slash 53 | if !strings.HasSuffix(req.URL.Path, "/") { 54 | dest := url.URL{ 55 | Path: req.URL.Path + "/", 56 | RawQuery: req.URL.RawQuery, 57 | Fragment: req.URL.Fragment, 58 | } 59 | http.Redirect(res, req, dest.String(), http.StatusFound) 60 | return nil, false 61 | } 62 | 63 | file = path.Join(file, "index.html") 64 | f, err = dir.Open(file) 65 | if err != nil { 66 | return errNotFound, false 67 | } 68 | defer f.Close() 69 | 70 | fi, err = f.Stat() 71 | if err != nil || fi.IsDir() { 72 | return errNotFound, false 73 | } 74 | } 75 | 76 | http.ServeContent(res, req, file, fi.ModTime(), f) 77 | 78 | return nil, true 79 | } 80 | 81 | func (self *httpAcceptor) ServeFileWithDir(res http.ResponseWriter, req *http.Request) (msg interface{}, err error, handled bool) { 82 | 83 | dir := self.GetDir() 84 | 85 | if dir == "" { 86 | return nil, errNotFound, false 87 | } 88 | 89 | err, handled = self.ServeFile(res, req, dir) 90 | 91 | return 92 | } 93 | -------------------------------------------------------------------------------- /peer/http/log.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "github.com/davyxu/golog" 5 | ) 6 | 7 | var log = golog.New("httppeer") 8 | -------------------------------------------------------------------------------- /peer/http/respond_html.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "bytes" 5 | "github.com/davyxu/cellnet" 6 | "html/template" 7 | "io" 8 | "io/ioutil" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | ) 13 | 14 | func getExt(s string) string { 15 | if strings.Index(s, ".") == -1 { 16 | return "" 17 | } 18 | return "." + strings.Join(strings.Split(s, ".")[1:], ".") 19 | } 20 | 21 | func (self *httpAcceptor) SetTemplateDir(dir string) { 22 | 23 | self.templateDir = dir 24 | } 25 | 26 | func (self *httpAcceptor) SetTemplateDelims(delimsLeft, delimsRight string) { 27 | self.delimsLeft = delimsLeft 28 | self.delimsRight = delimsRight 29 | } 30 | 31 | func (self *httpAcceptor) SetTemplateExtensions(exts []string) { 32 | self.templateExts = exts 33 | } 34 | 35 | func (self *httpAcceptor) SetTemplateFunc(f []template.FuncMap) { 36 | self.templateFuncs = f 37 | } 38 | 39 | func (self *httpAcceptor) Compile() *template.Template { 40 | 41 | if self.templateDir == "" { 42 | self.templateDir = "." 43 | } 44 | 45 | if len(self.templateExts) == 0 { 46 | self.templateExts = []string{".tpl", ".html"} 47 | } 48 | 49 | t := template.New(self.templateDir) 50 | 51 | t.Delims(self.delimsLeft, self.delimsRight) 52 | // parse an initial template in case we don't have any 53 | //template.Must(t.Parse("Martini")) 54 | 55 | filepath.Walk(self.templateDir, func(path string, info os.FileInfo, err error) error { 56 | r, err := filepath.Rel(self.templateDir, path) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | ext := getExt(r) 62 | 63 | for _, extension := range self.templateExts { 64 | if ext == extension { 65 | 66 | buf, err := ioutil.ReadFile(path) 67 | if err != nil { 68 | panic(err) 69 | } 70 | 71 | name := r[0 : len(r)-len(ext)] 72 | tmpl := t.New(filepath.ToSlash(name)) 73 | 74 | // add our funcmaps 75 | for _, funcs := range self.templateFuncs { 76 | tmpl.Funcs(funcs) 77 | } 78 | 79 | // Bomb out if parse fails. We don't want any silent server starts. 80 | template.Must(tmpl.Parse(string(buf))) 81 | break 82 | } 83 | } 84 | 85 | return nil 86 | }) 87 | 88 | return t 89 | } 90 | 91 | type HTMLRespond struct { 92 | StatusCode int 93 | 94 | PageTemplate string 95 | 96 | TemplateModel interface{} 97 | } 98 | 99 | func (self *HTMLRespond) WriteRespond(ses *httpSession) error { 100 | 101 | peerInfo := ses.Peer().(cellnet.PeerProperty) 102 | 103 | log.Debugf("#http.send(%s) '%s' %s | [%d] HTML %s", 104 | peerInfo.Name(), 105 | ses.req.Method, 106 | ses.req.URL.Path, 107 | self.StatusCode, 108 | self.PageTemplate) 109 | 110 | buf := make([]byte, 64) 111 | 112 | bb := bytes.NewBuffer(buf) 113 | bb.Reset() 114 | 115 | err := ses.t.ExecuteTemplate(bb, self.PageTemplate, self.TemplateModel) 116 | 117 | if err != nil { 118 | return err 119 | } 120 | 121 | // template rendered fine, write out the result 122 | ses.resp.Header().Set("Content-Type", "text/html") 123 | ses.resp.WriteHeader(self.StatusCode) 124 | io.Copy(ses.resp, bb) 125 | 126 | return nil 127 | } 128 | 129 | type TextRespond struct { 130 | StatusCode int 131 | Text string 132 | } 133 | 134 | func (self *TextRespond) WriteRespond(ses *httpSession) error { 135 | 136 | if log.IsDebugEnabled() { 137 | peerInfo := ses.Peer().(cellnet.PeerProperty) 138 | 139 | log.Debugf("#http.send(%s) '%s' %s | [%d] HTML '%s'", 140 | peerInfo.Name(), 141 | ses.req.Method, 142 | ses.req.URL.Path, 143 | self.StatusCode, 144 | self.Text) 145 | } 146 | 147 | ses.resp.Header().Set("Content-Type", "text/html;charset=utf-8") 148 | ses.resp.WriteHeader(self.StatusCode) 149 | ses.resp.Write([]byte(self.Text)) 150 | 151 | return nil 152 | } 153 | -------------------------------------------------------------------------------- /peer/http/respond_msg.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/davyxu/cellnet" 7 | "github.com/davyxu/cellnet/codec" 8 | _ "github.com/davyxu/cellnet/codec/httpjson" 9 | "io" 10 | "io/ioutil" 11 | "net/http" 12 | ) 13 | 14 | type MessageRespond struct { 15 | StatusCode int 16 | Msg interface{} 17 | CodecName string 18 | } 19 | 20 | func (self *MessageRespond) String() string { 21 | return fmt.Sprintf("Code: %d Msg: %+v CodeName: %s", self.StatusCode, self.Msg, self.CodecName) 22 | } 23 | 24 | func (self *MessageRespond) WriteRespond(ses *httpSession) error { 25 | peerInfo := ses.Peer().(cellnet.PeerProperty) 26 | 27 | if self.CodecName == "" { 28 | self.CodecName = "httpjson" 29 | } 30 | if self.StatusCode == 0 { 31 | self.StatusCode = http.StatusOK 32 | } 33 | 34 | httpCodec := codec.GetCodec(self.CodecName) 35 | 36 | if httpCodec == nil { 37 | return errors.New("ResponseCodec not found:" + self.CodecName) 38 | } 39 | 40 | msg := self.Msg 41 | 42 | // 将消息编码为字节数组 43 | var data interface{} 44 | data, err := httpCodec.Encode(msg, nil) 45 | 46 | if err != nil { 47 | return err 48 | } 49 | 50 | ses.resp.Header().Set("Content-Type", httpCodec.MimeType()+";charset=UTF-8") 51 | ses.resp.WriteHeader(self.StatusCode) 52 | 53 | bodyData, err := ioutil.ReadAll(data.(io.Reader)) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | if log.IsDebugEnabled() { 59 | log.Debugf("#http.send(%s) '%s' %s | [%d] %s", 60 | peerInfo.Name(), 61 | ses.req.Method, 62 | ses.req.URL.Path, 63 | self.StatusCode, 64 | string(bodyData)) 65 | } 66 | 67 | ses.resp.Write(bodyData) 68 | 69 | return nil 70 | } 71 | -------------------------------------------------------------------------------- /peer/http/respond_status.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import "github.com/davyxu/cellnet" 4 | 5 | type StatusRespond struct { 6 | StatusCode int 7 | } 8 | 9 | func (self *StatusRespond) WriteRespond(ses *httpSession) error { 10 | 11 | peerInfo := ses.Peer().(cellnet.PeerProperty) 12 | 13 | log.Debugf("#http.recv(%s) '%s' %s | [%d] Status", 14 | peerInfo.Name(), 15 | ses.req.Method, 16 | ses.req.URL.Path, 17 | self.StatusCode) 18 | 19 | ses.resp.WriteHeader(int(self.StatusCode)) 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /peer/http/session.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "errors" 5 | "github.com/davyxu/cellnet" 6 | "github.com/davyxu/cellnet/peer" 7 | "html/template" 8 | "net/http" 9 | ) 10 | 11 | type RequestMatcher interface { 12 | Match(method, url string) bool 13 | } 14 | type RespondProc interface { 15 | WriteRespond(*httpSession) error 16 | } 17 | 18 | var ( 19 | ErrUnknownOperation = errors.New("Unknown http operation") 20 | ) 21 | 22 | type httpSession struct { 23 | peer.CoreContextSet 24 | *peer.CoreProcBundle 25 | req *http.Request 26 | resp http.ResponseWriter 27 | 28 | // 单独保存的保存Peer接口 29 | peerInterface cellnet.Peer 30 | 31 | t *template.Template 32 | 33 | respond bool 34 | err error 35 | } 36 | 37 | func (self *httpSession) Match(method, url string) bool { 38 | 39 | return self.req.Method == method && self.req.URL.Path == url 40 | } 41 | 42 | func (self *httpSession) Request() *http.Request { 43 | return self.req 44 | } 45 | 46 | func (self *httpSession) Response() http.ResponseWriter { 47 | return self.resp 48 | } 49 | 50 | // 取原始连接 51 | func (self *httpSession) Raw() interface{} { 52 | return nil 53 | } 54 | 55 | func (self *httpSession) ID() int64 { 56 | return 0 57 | } 58 | 59 | // 取原始连接 60 | func (self *httpSession) Close() { 61 | } 62 | 63 | // 取会话归属的通讯端 64 | func (self *httpSession) Peer() cellnet.Peer { 65 | return self.peerInterface 66 | } 67 | 68 | // 发送封包 69 | func (self *httpSession) Send(raw interface{}) { 70 | 71 | if proc, ok := raw.(RespondProc); ok { 72 | self.err = proc.WriteRespond(self) 73 | self.respond = true 74 | } else { 75 | self.err = ErrUnknownOperation 76 | } 77 | 78 | } 79 | 80 | func newHttpSession(acc *httpAcceptor, req *http.Request, response http.ResponseWriter) *httpSession { 81 | 82 | return &httpSession{ 83 | req: req, 84 | resp: response, 85 | peerInterface: acc, 86 | t: acc.Compile(), 87 | CoreProcBundle: acc.GetBundle(), 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /peer/iopanic.go: -------------------------------------------------------------------------------- 1 | package peer 2 | 3 | type CoreCaptureIOPanic struct { 4 | captureIOPanic bool 5 | } 6 | 7 | func (self *CoreCaptureIOPanic) EnableCaptureIOPanic(v bool) { 8 | self.captureIOPanic = v 9 | } 10 | 11 | func (self *CoreCaptureIOPanic) CaptureIOPanic() bool { 12 | return self.captureIOPanic 13 | } 14 | -------------------------------------------------------------------------------- /peer/mysql/connector.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "database/sql" 5 | "github.com/davyxu/cellnet" 6 | "github.com/davyxu/cellnet/peer" 7 | "github.com/go-sql-driver/mysql" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | type mysqlConnector struct { 13 | peer.CorePeerProperty 14 | peer.CoreContextSet 15 | peer.CoreSQLParameter 16 | 17 | db *sql.DB 18 | dbGuard sync.RWMutex 19 | 20 | reconDur time.Duration 21 | } 22 | 23 | func (self *mysqlConnector) IsReady() bool { 24 | return self.dbConn() != nil 25 | } 26 | 27 | func (self *mysqlConnector) Raw() interface{} { 28 | 29 | return self.dbConn() 30 | } 31 | 32 | func (self *mysqlConnector) Operate(callback func(client interface{}) interface{}) interface{} { 33 | 34 | return callback(self.dbConn()) 35 | } 36 | 37 | func (self *mysqlConnector) dbConn() *sql.DB { 38 | self.dbGuard.RLock() 39 | defer self.dbGuard.RUnlock() 40 | return self.db 41 | } 42 | 43 | func (self *mysqlConnector) TypeName() string { 44 | return "mysql.Connector" 45 | } 46 | 47 | func (self *mysqlConnector) Start() cellnet.Peer { 48 | 49 | for { 50 | 51 | self.tryConnect() 52 | 53 | if self.reconDur == 0 || self.IsReady() { 54 | break 55 | } 56 | 57 | time.Sleep(self.reconDur) 58 | } 59 | 60 | return self 61 | } 62 | 63 | func (self *mysqlConnector) ReconnectDuration() time.Duration { 64 | 65 | return self.reconDur 66 | } 67 | 68 | func (self *mysqlConnector) SetReconnectDuration(v time.Duration) { 69 | self.reconDur = v 70 | } 71 | 72 | func (self *mysqlConnector) tryConnect() { 73 | 74 | config, err := mysql.ParseDSN(self.Address()) 75 | 76 | if err != nil { 77 | log.Errorf("Invalid mysql DSN: %s, %s\n", self.Address(), err.Error()) 78 | return 79 | } 80 | 81 | log.Infof("Connecting to mysql (%s) %s/%s...", self.Name(), config.Addr, config.DBName) 82 | 83 | db, err := sql.Open("mysql", self.Address()) 84 | if err != nil { 85 | log.Errorf("Open mysql database error: %s\n", err) 86 | return 87 | } 88 | 89 | err = db.Ping() 90 | if err != nil { 91 | log.Errorln(err) 92 | return 93 | } 94 | 95 | db.SetMaxOpenConns(int(self.PoolConnCount)) 96 | db.SetMaxIdleConns(int(self.PoolConnCount / 2)) 97 | 98 | self.dbGuard.Lock() 99 | self.db = db 100 | self.dbGuard.Unlock() 101 | 102 | if config != nil { 103 | log.SetColor("green").Infof("Connected to mysql %s/%s", config.Addr, config.DBName) 104 | } 105 | } 106 | 107 | func (self *mysqlConnector) Stop() { 108 | 109 | db := self.dbConn() 110 | if db != nil { 111 | db.Close() 112 | } 113 | 114 | } 115 | 116 | func init() { 117 | 118 | peer.RegisterPeerCreator(func() cellnet.Peer { 119 | self := &mysqlConnector{} 120 | self.CoreSQLParameter.Init() 121 | 122 | return self 123 | }) 124 | } 125 | -------------------------------------------------------------------------------- /peer/mysql/log.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "github.com/davyxu/golog" 5 | ) 6 | 7 | var log = golog.New("mysql") 8 | -------------------------------------------------------------------------------- /peer/mysql/wrapper.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "database/sql" 5 | "errors" 6 | ) 7 | 8 | type Wrapper struct { 9 | drv *sql.DB 10 | row *sql.Rows 11 | query string 12 | 13 | Err error 14 | } 15 | 16 | var ErrDriverNotReady = errors.New("driver not ready") 17 | 18 | func (self *Wrapper) Query(query string, args ...interface{}) *Wrapper { 19 | 20 | if self.drv == nil { 21 | self.Err = ErrDriverNotReady 22 | return self 23 | } 24 | 25 | self.query = query 26 | log.Debugln("[DB]", query, args) 27 | 28 | self.row, self.Err = self.drv.Query(query, args...) 29 | 30 | if self.Err != nil { 31 | log.Errorln("[DB] ", self.query, self.Err.Error()) 32 | } 33 | 34 | return self 35 | } 36 | 37 | func (self *Wrapper) Execute(query string, args ...interface{}) *Wrapper { 38 | if self.drv == nil { 39 | self.Err = ErrDriverNotReady 40 | return self 41 | } 42 | 43 | self.query = query 44 | log.Debugln("[DB]", query, args) 45 | 46 | _, self.Err = self.drv.Exec(query, args...) 47 | 48 | if self.Err != nil { 49 | log.Errorln("[DB] ", self.query, self.Err.Error()) 50 | } 51 | 52 | return self 53 | } 54 | 55 | func (self *Wrapper) One(data ...interface{}) *Wrapper { 56 | 57 | if self.Err != nil { 58 | return self 59 | } 60 | 61 | if self.drv == nil { 62 | self.Err = ErrDriverNotReady 63 | return self 64 | } 65 | 66 | if !self.row.Next() { 67 | return self 68 | } 69 | 70 | self.Err = self.row.Scan(data...) 71 | 72 | if self.Err != nil { 73 | log.Errorln("One.Row.Scan failed", self.query, self.Err) 74 | } 75 | 76 | self.row.Close() 77 | self.row = nil 78 | 79 | return self 80 | } 81 | 82 | func (self *Wrapper) Scan(dest ...interface{}) { 83 | 84 | self.Err = self.row.Scan(dest...) 85 | 86 | if self.Err != nil { 87 | log.Errorln("Scan.Scan failed", self.query, self.Err) 88 | } 89 | 90 | } 91 | 92 | func (self *Wrapper) Each(callback func(*Wrapper) bool) *Wrapper { 93 | 94 | if self.Err != nil { 95 | return self 96 | } 97 | 98 | if self.drv == nil { 99 | self.Err = ErrDriverNotReady 100 | return self 101 | } 102 | 103 | for self.row.Next() { 104 | 105 | if !callback(self) { 106 | break 107 | } 108 | 109 | if self.Err != nil { 110 | return self 111 | } 112 | 113 | } 114 | 115 | self.row.Close() 116 | 117 | return self 118 | } 119 | 120 | func NewWrapper(drv *sql.DB) *Wrapper { 121 | 122 | return &Wrapper{ 123 | drv: drv, 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /peer/peerprop.go: -------------------------------------------------------------------------------- 1 | package peer 2 | 3 | import "github.com/davyxu/cellnet" 4 | 5 | type CorePeerProperty struct { 6 | name string 7 | queue cellnet.EventQueue 8 | addr string 9 | } 10 | 11 | // 获取通讯端的名称 12 | func (self *CorePeerProperty) Name() string { 13 | return self.name 14 | } 15 | 16 | // 获取队列 17 | func (self *CorePeerProperty) Queue() cellnet.EventQueue { 18 | return self.queue 19 | } 20 | 21 | // 获取SetAddress中的侦听或者连接地址 22 | func (self *CorePeerProperty) Address() string { 23 | 24 | return self.addr 25 | } 26 | 27 | func (self *CorePeerProperty) SetName(v string) { 28 | self.name = v 29 | } 30 | 31 | func (self *CorePeerProperty) SetQueue(v cellnet.EventQueue) { 32 | self.queue = v 33 | } 34 | 35 | func (self *CorePeerProperty) SetAddress(v string) { 36 | self.addr = v 37 | } 38 | -------------------------------------------------------------------------------- /peer/peerreg.go: -------------------------------------------------------------------------------- 1 | package peer 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davyxu/cellnet" 6 | "sort" 7 | ) 8 | 9 | type PeerCreateFunc func() cellnet.Peer 10 | 11 | var peerByName = map[string]PeerCreateFunc{} 12 | 13 | // 注册Peer创建器 14 | func RegisterPeerCreator(f PeerCreateFunc) { 15 | 16 | // 临时实例化一个,获取类型 17 | dummyPeer := f() 18 | 19 | if _, ok := peerByName[dummyPeer.TypeName()]; ok { 20 | panic("duplicate peer type: " + dummyPeer.TypeName()) 21 | } 22 | 23 | peerByName[dummyPeer.TypeName()] = f 24 | } 25 | 26 | // Peer创建器列表 27 | func PeerCreatorList() (ret []string) { 28 | 29 | for name := range peerByName { 30 | ret = append(ret, name) 31 | } 32 | 33 | sort.Strings(ret) 34 | return 35 | } 36 | 37 | // cellnet自带的peer对应包 38 | func getPackageByPeerName(name string) string { 39 | switch name { 40 | case "tcp.Connector", "tcp.Acceptor", "tcp.SyncConnector": 41 | return "github.com/davyxu/cellnet/peer/tcp" 42 | case "udp.Connector", "udp.Acceptor": 43 | return "github.com/davyxu/cellnet/peer/udp" 44 | case "gorillaws.Acceptor", "gorillaws.Connector", "gorillaws.SyncConnector": 45 | return "github.com/davyxu/cellnet/peer/gorillaws" 46 | case "http.Connector", "http.Acceptor": 47 | return "github.com/davyxu/cellnet/peer/http" 48 | case "redix.Connector": 49 | return "github.com/davyxu/cellnet/peer/redix" 50 | case "mysql.Connector": 51 | return "github.com/davyxu/cellnet/peer/mysql" 52 | default: 53 | return "package/to/your/peer" 54 | } 55 | } 56 | 57 | // 创建一个Peer 58 | func NewPeer(peerType string) cellnet.Peer { 59 | peerCreator := peerByName[peerType] 60 | if peerCreator == nil { 61 | panic(fmt.Sprintf("peer type not found '%s'\ntry to add code below:\nimport (\n _ \"%s\"\n)\n\n", 62 | peerType, 63 | getPackageByPeerName(peerType))) 64 | } 65 | 66 | return peerCreator() 67 | } 68 | 69 | // 创建Peer后,设置基本属性 70 | func NewGenericPeer(peerType, name, addr string, q cellnet.EventQueue) cellnet.GenericPeer { 71 | 72 | p := NewPeer(peerType) 73 | gp := p.(cellnet.GenericPeer) 74 | gp.SetName(name) 75 | gp.SetAddress(addr) 76 | gp.SetQueue(q) 77 | return gp 78 | } 79 | -------------------------------------------------------------------------------- /peer/procbundle.go: -------------------------------------------------------------------------------- 1 | package peer 2 | 3 | import ( 4 | "errors" 5 | "github.com/davyxu/cellnet" 6 | ) 7 | 8 | // 手动投递消息, 兼容v2的设计 9 | type MessagePoster interface { 10 | 11 | // 投递一个消息到Hooker之前 12 | ProcEvent(ev cellnet.Event) 13 | } 14 | 15 | type CoreProcBundle struct { 16 | transmit cellnet.MessageTransmitter 17 | hooker cellnet.EventHooker 18 | callback cellnet.EventCallback 19 | } 20 | 21 | func (self *CoreProcBundle) GetBundle() *CoreProcBundle { 22 | return self 23 | } 24 | 25 | func (self *CoreProcBundle) SetTransmitter(v cellnet.MessageTransmitter) { 26 | self.transmit = v 27 | } 28 | 29 | func (self *CoreProcBundle) SetHooker(v cellnet.EventHooker) { 30 | self.hooker = v 31 | } 32 | 33 | func (self *CoreProcBundle) SetCallback(v cellnet.EventCallback) { 34 | self.callback = v 35 | } 36 | 37 | var notHandled = errors.New("Processor: Transimitter nil") 38 | 39 | func (self *CoreProcBundle) ReadMessage(ses cellnet.Session) (msg interface{}, err error) { 40 | 41 | if self.transmit != nil { 42 | return self.transmit.OnRecvMessage(ses) 43 | } 44 | 45 | return nil, notHandled 46 | } 47 | 48 | func (self *CoreProcBundle) SendMessage(ev cellnet.Event) { 49 | 50 | if self.hooker != nil { 51 | ev = self.hooker.OnOutboundEvent(ev) 52 | } 53 | 54 | if self.transmit != nil && ev != nil { 55 | self.transmit.OnSendMessage(ev.Session(), ev.Message()) 56 | } 57 | } 58 | 59 | func (self *CoreProcBundle) ProcEvent(ev cellnet.Event) { 60 | 61 | if self.hooker != nil { 62 | ev = self.hooker.OnInboundEvent(ev) 63 | } 64 | 65 | if self.callback != nil && ev != nil { 66 | self.callback(ev) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /peer/property.go: -------------------------------------------------------------------------------- 1 | package peer 2 | 3 | import ( 4 | "reflect" 5 | "sync" 6 | ) 7 | 8 | type ctx struct { 9 | key interface{} 10 | value interface{} 11 | } 12 | 13 | // 上下文记录,绑定用户自定义数据 14 | type CoreContextSet struct { 15 | ctxes []ctx 16 | ctxesGuard sync.RWMutex 17 | } 18 | 19 | func (self *CoreContextSet) FetchContext(key, valuePtr interface{}) bool { 20 | 21 | pv, ok := self.GetContext(key) 22 | if !ok { 23 | return false 24 | } 25 | 26 | switch rawValue := valuePtr.(type) { 27 | case *string: 28 | *rawValue = pv.(string) 29 | case *int: 30 | *rawValue = pv.(int) 31 | case *int32: 32 | *rawValue = pv.(int32) 33 | case *int64: 34 | *rawValue = pv.(int64) 35 | case *uint: 36 | *rawValue = pv.(uint) 37 | case *uint32: 38 | *rawValue = pv.(uint32) 39 | case *uint64: 40 | *rawValue = pv.(uint64) 41 | case *bool: 42 | *rawValue = pv.(bool) 43 | case *float32: 44 | *rawValue = pv.(float32) 45 | case *float64: 46 | *rawValue = pv.(float64) 47 | case *[]byte: 48 | *rawValue = pv.([]byte) 49 | default: 50 | v := reflect.Indirect(reflect.ValueOf(valuePtr)) 51 | 52 | // 避免call of reflect.Value.Set on zero Value 53 | if pv == nil { 54 | v.Set(reflect.Zero(v.Type())) 55 | } else { 56 | v.Set(reflect.ValueOf(pv)) 57 | } 58 | 59 | } 60 | 61 | return true 62 | } 63 | 64 | func (self *CoreContextSet) GetContext(key interface{}) (interface{}, bool) { 65 | 66 | self.ctxesGuard.RLock() 67 | defer self.ctxesGuard.RUnlock() 68 | 69 | for _, t := range self.ctxes { 70 | if t.key == key { 71 | return t.value, true 72 | } 73 | } 74 | 75 | return nil, false 76 | } 77 | 78 | func (self *CoreContextSet) SetContext(key, v interface{}) { 79 | 80 | self.ctxesGuard.Lock() 81 | defer self.ctxesGuard.Unlock() 82 | 83 | for i, t := range self.ctxes { 84 | if t.key == key { 85 | self.ctxes[i] = ctx{key, v} 86 | return 87 | } 88 | } 89 | 90 | self.ctxes = append(self.ctxes, ctx{key, v}) 91 | } 92 | -------------------------------------------------------------------------------- /peer/redisparam.go: -------------------------------------------------------------------------------- 1 | package peer 2 | 3 | type CoreRedisParameter struct { 4 | Password string 5 | DBIndex int 6 | PoolConnCount int 7 | } 8 | 9 | func (self *CoreRedisParameter) Init() { 10 | self.PoolConnCount = 1 11 | } 12 | 13 | func (self *CoreRedisParameter) SetPassword(v string) { 14 | self.Password = v 15 | } 16 | 17 | func (self *CoreRedisParameter) SetDBIndex(v int) { 18 | self.DBIndex = v 19 | } 20 | 21 | func (self *CoreRedisParameter) SetConnectionCount(v int) { 22 | self.PoolConnCount = v 23 | } 24 | -------------------------------------------------------------------------------- /peer/redix/connector.go: -------------------------------------------------------------------------------- 1 | package redix 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/peer" 6 | "github.com/mediocregopher/radix.v2/pool" 7 | "github.com/mediocregopher/radix.v2/redis" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | type redisConnector struct { 13 | peer.CorePeerProperty 14 | peer.CoreContextSet 15 | peer.CoreRedisParameter 16 | 17 | pool *pool.Pool 18 | poolGuard sync.RWMutex 19 | } 20 | 21 | func (self *redisConnector) IsReady() bool { 22 | return self.Pool() != nil 23 | } 24 | 25 | func (self *redisConnector) TypeName() string { 26 | return "redix.Connector" 27 | } 28 | 29 | func (self *redisConnector) Pool() *pool.Pool { 30 | self.poolGuard.RLock() 31 | defer self.poolGuard.RUnlock() 32 | 33 | return self.pool 34 | } 35 | 36 | func (self *redisConnector) Raw() interface{} { 37 | 38 | return self.Pool() 39 | } 40 | 41 | func (self *redisConnector) Operate(callback func(client interface{}) interface{}) interface{} { 42 | 43 | pool := self.Pool() 44 | c, err := pool.Get() 45 | if err != nil { 46 | log.Errorf("get client failed, %s", err) 47 | return err 48 | } 49 | 50 | defer func() { 51 | pool.Put(c) 52 | }() 53 | 54 | return callback(c) 55 | } 56 | 57 | func (self *redisConnector) tryConnect() { 58 | 59 | var faildWaitSec = 2 60 | 61 | for { 62 | pool, err := pool.NewCustom("tcp", self.Address(), self.PoolConnCount, func(network, addr string) (*redis.Client, error) { 63 | 64 | client, err := redis.DialTimeout(network, addr, time.Second*5) 65 | if err != nil { 66 | log.Errorf("redis.Dial %s", err.Error()) 67 | return nil, err 68 | } 69 | 70 | if len(self.Password) > 0 { 71 | if err = client.Cmd("AUTH", self.Password).Err; err != nil { 72 | log.Errorf("redis.Auth %s %s", self.Password, err.Error()) 73 | client.Close() 74 | return nil, err 75 | } 76 | } 77 | if self.DBIndex > 0 { 78 | if err = client.Cmd("SELECT", self.DBIndex).Err; err != nil { 79 | log.Errorf("redis.SELECT %d %s", self.DBIndex, err.Error()) 80 | client.Close() 81 | return nil, err 82 | } 83 | } 84 | 85 | log.Infof("Create redis pool connection: %s name: %s index: %d", addr, self.Name(), self.DBIndex) 86 | 87 | return client, nil 88 | }) 89 | 90 | if err != nil { 91 | log.Errorf("Redis connect failed: %s, wait %d secods retry...", err, faildWaitSec) 92 | 93 | time.Sleep(time.Duration(faildWaitSec) * time.Second) 94 | 95 | if faildWaitSec < 10 { 96 | faildWaitSec += 2 97 | } 98 | 99 | continue 100 | } 101 | 102 | self.poolGuard.Lock() 103 | self.pool = pool 104 | self.poolGuard.Unlock() 105 | 106 | break 107 | } 108 | 109 | } 110 | 111 | func (self *redisConnector) Start() cellnet.Peer { 112 | 113 | go self.tryConnect() 114 | 115 | return self 116 | } 117 | 118 | func (self *redisConnector) Stop() { 119 | 120 | } 121 | 122 | func init() { 123 | 124 | peer.RegisterPeerCreator(func() cellnet.Peer { 125 | 126 | self := &redisConnector{} 127 | self.CoreRedisParameter.Init() 128 | 129 | return self 130 | }) 131 | } 132 | -------------------------------------------------------------------------------- /peer/redix/log.go: -------------------------------------------------------------------------------- 1 | package redix 2 | 3 | import ( 4 | "github.com/davyxu/golog" 5 | ) 6 | 7 | var log = golog.New("redix") 8 | -------------------------------------------------------------------------------- /peer/runningtag.go: -------------------------------------------------------------------------------- 1 | package peer 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | ) 7 | 8 | // 通信通讯端共享的数据 9 | type CoreRunningTag struct { 10 | 11 | // 运行状态 12 | running int64 13 | 14 | stoppingWaitor sync.WaitGroup 15 | stopping int64 16 | } 17 | 18 | func (self *CoreRunningTag) IsRunning() bool { 19 | 20 | return atomic.LoadInt64(&self.running) != 0 21 | } 22 | 23 | func (self *CoreRunningTag) SetRunning(v bool) { 24 | 25 | if v { 26 | atomic.StoreInt64(&self.running, 1) 27 | } else { 28 | atomic.StoreInt64(&self.running, 0) 29 | } 30 | 31 | } 32 | 33 | func (self *CoreRunningTag) WaitStopFinished() { 34 | // 如果正在停止时, 等待停止完成 35 | self.stoppingWaitor.Wait() 36 | } 37 | 38 | func (self *CoreRunningTag) IsStopping() bool { 39 | return atomic.LoadInt64(&self.stopping) != 0 40 | } 41 | 42 | func (self *CoreRunningTag) StartStopping() { 43 | self.stoppingWaitor.Add(1) 44 | atomic.StoreInt64(&self.stopping, 1) 45 | } 46 | 47 | func (self *CoreRunningTag) EndStopping() { 48 | 49 | if self.IsStopping() { 50 | self.stoppingWaitor.Done() 51 | atomic.StoreInt64(&self.stopping, 0) 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /peer/sesidentify.go: -------------------------------------------------------------------------------- 1 | package peer 2 | 3 | type CoreSessionIdentify struct { 4 | id int64 5 | } 6 | 7 | func (self *CoreSessionIdentify) ID() int64 { 8 | return self.id 9 | } 10 | 11 | func (self *CoreSessionIdentify) SetID(id int64) { 12 | self.id = id 13 | } 14 | -------------------------------------------------------------------------------- /peer/sesmgr.go: -------------------------------------------------------------------------------- 1 | package peer 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "sync" 6 | "sync/atomic" 7 | ) 8 | 9 | // 完整功能的会话管理 10 | type SessionManager interface { 11 | cellnet.SessionAccessor 12 | 13 | Add(cellnet.Session) 14 | Remove(cellnet.Session) 15 | Count() int 16 | 17 | // 设置ID开始的号 18 | SetIDBase(base int64) 19 | } 20 | 21 | type CoreSessionManager struct { 22 | sesById sync.Map // 使用Id关联会话 23 | 24 | sesIDGen int64 // 记录已经生成的会话ID流水号 25 | 26 | count int64 // 记录当前在使用的会话数量 27 | } 28 | 29 | func (self *CoreSessionManager) SetIDBase(base int64) { 30 | 31 | atomic.StoreInt64(&self.sesIDGen, base) 32 | } 33 | 34 | func (self *CoreSessionManager) Count() int { 35 | return int(atomic.LoadInt64(&self.count)) 36 | } 37 | 38 | func (self *CoreSessionManager) Add(ses cellnet.Session) { 39 | 40 | id := atomic.AddInt64(&self.sesIDGen, 1) 41 | 42 | atomic.AddInt64(&self.count, 1) 43 | 44 | ses.(interface { 45 | SetID(int64) 46 | }).SetID(id) 47 | 48 | self.sesById.Store(id, ses) 49 | } 50 | 51 | func (self *CoreSessionManager) Remove(ses cellnet.Session) { 52 | 53 | self.sesById.Delete(ses.ID()) 54 | 55 | atomic.AddInt64(&self.count, -1) 56 | } 57 | 58 | // 获得一个连接 59 | func (self *CoreSessionManager) GetSession(id int64) cellnet.Session { 60 | if v, ok := self.sesById.Load(id); ok { 61 | return v.(cellnet.Session) 62 | } 63 | 64 | return nil 65 | } 66 | 67 | func (self *CoreSessionManager) VisitSession(callback func(cellnet.Session) bool) { 68 | 69 | self.sesById.Range(func(key, value interface{}) bool { 70 | 71 | return callback(value.(cellnet.Session)) 72 | 73 | }) 74 | } 75 | 76 | func (self *CoreSessionManager) CloseAllSession() { 77 | 78 | self.VisitSession(func(ses cellnet.Session) bool { 79 | 80 | ses.Close() 81 | 82 | return true 83 | }) 84 | } 85 | 86 | // 活跃的会话数量 87 | func (self *CoreSessionManager) SessionCount() int { 88 | 89 | v := atomic.LoadInt64(&self.count) 90 | 91 | return int(v) 92 | } 93 | -------------------------------------------------------------------------------- /peer/socketoption.go: -------------------------------------------------------------------------------- 1 | package peer 2 | 3 | import ( 4 | "net" 5 | "time" 6 | ) 7 | 8 | type CoreTCPSocketOption struct { 9 | readBufferSize int 10 | writeBufferSize int 11 | noDelay bool 12 | maxPacketSize int 13 | 14 | readTimeout time.Duration 15 | writeTimeout time.Duration 16 | } 17 | 18 | func (self *CoreTCPSocketOption) SetSocketBuffer(readBufferSize, writeBufferSize int, noDelay bool) { 19 | self.readBufferSize = readBufferSize 20 | self.writeBufferSize = writeBufferSize 21 | self.noDelay = noDelay 22 | } 23 | 24 | func (self *CoreTCPSocketOption) SetSocketDeadline(read, write time.Duration) { 25 | 26 | self.readTimeout = read 27 | self.writeTimeout = write 28 | } 29 | 30 | func (self *CoreTCPSocketOption) SetMaxPacketSize(maxSize int) { 31 | self.maxPacketSize = maxSize 32 | } 33 | 34 | func (self *CoreTCPSocketOption) MaxPacketSize() int { 35 | 36 | return self.maxPacketSize 37 | } 38 | 39 | func (self *CoreTCPSocketOption) ApplySocketOption(conn net.Conn) { 40 | 41 | if cc, ok := conn.(*net.TCPConn); ok { 42 | 43 | if self.readBufferSize >= 0 { 44 | cc.SetReadBuffer(self.readBufferSize) 45 | } 46 | 47 | if self.writeBufferSize >= 0 { 48 | cc.SetWriteBuffer(self.writeBufferSize) 49 | } 50 | 51 | cc.SetNoDelay(self.noDelay) 52 | } 53 | 54 | } 55 | 56 | func (self *CoreTCPSocketOption) ApplySocketReadTimeout(conn net.Conn, callback func()) { 57 | 58 | if self.readTimeout > 0 { 59 | 60 | // issue: http://blog.sina.com.cn/s/blog_9be3b8f10101lhiq.html 61 | conn.SetReadDeadline(time.Now().Add(self.readTimeout)) 62 | callback() 63 | conn.SetReadDeadline(time.Time{}) 64 | 65 | } else { 66 | callback() 67 | } 68 | } 69 | 70 | func (self *CoreTCPSocketOption) ApplySocketWriteTimeout(conn net.Conn, callback func()) { 71 | 72 | if self.writeTimeout > 0 { 73 | 74 | conn.SetWriteDeadline(time.Now().Add(self.writeTimeout)) 75 | callback() 76 | conn.SetWriteDeadline(time.Time{}) 77 | 78 | } else { 79 | callback() 80 | } 81 | } 82 | 83 | func (self *CoreTCPSocketOption) Init() { 84 | self.readBufferSize = -1 85 | self.writeBufferSize = -1 86 | } 87 | -------------------------------------------------------------------------------- /peer/sqlparam.go: -------------------------------------------------------------------------------- 1 | package peer 2 | 3 | type CoreSQLParameter struct { 4 | PoolConnCount int 5 | } 6 | 7 | func (self *CoreSQLParameter) Init() { 8 | self.PoolConnCount = 1 9 | } 10 | 11 | func (self *CoreSQLParameter) SetPassword(v string) { 12 | } 13 | 14 | func (self *CoreSQLParameter) SetConnectionCount(v int) { 15 | self.PoolConnCount = v 16 | } 17 | -------------------------------------------------------------------------------- /peer/sysmsgreg.go: -------------------------------------------------------------------------------- 1 | package peer 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/codec" 6 | _ "github.com/davyxu/cellnet/codec/binary" 7 | "github.com/davyxu/cellnet/util" 8 | "reflect" 9 | ) 10 | 11 | func init() { 12 | cellnet.RegisterMessageMeta(&cellnet.MessageMeta{ 13 | Codec: codec.MustGetCodec("binary"), 14 | Type: reflect.TypeOf((*cellnet.SessionAccepted)(nil)).Elem(), 15 | ID: int(util.StringHash("cellnet.SessionAccepted")), 16 | }) 17 | cellnet.RegisterMessageMeta(&cellnet.MessageMeta{ 18 | Codec: codec.MustGetCodec("binary"), 19 | Type: reflect.TypeOf((*cellnet.SessionConnected)(nil)).Elem(), 20 | ID: int(util.StringHash("cellnet.SessionConnected")), 21 | }) 22 | cellnet.RegisterMessageMeta(&cellnet.MessageMeta{ 23 | Codec: codec.MustGetCodec("binary"), 24 | Type: reflect.TypeOf((*cellnet.SessionConnectError)(nil)).Elem(), 25 | ID: int(util.StringHash("cellnet.SessionConnectError")), 26 | }) 27 | cellnet.RegisterMessageMeta(&cellnet.MessageMeta{ 28 | Codec: codec.MustGetCodec("binary"), 29 | Type: reflect.TypeOf((*cellnet.SessionClosed)(nil)).Elem(), 30 | ID: int(util.StringHash("cellnet.SessionClosed")), 31 | }) 32 | cellnet.RegisterMessageMeta(&cellnet.MessageMeta{ 33 | Codec: codec.MustGetCodec("binary"), 34 | Type: reflect.TypeOf((*cellnet.SessionCloseNotify)(nil)).Elem(), 35 | ID: int(util.StringHash("cellnet.SessionCloseNotify")), 36 | }) 37 | cellnet.RegisterMessageMeta(&cellnet.MessageMeta{ 38 | Codec: codec.MustGetCodec("binary"), 39 | Type: reflect.TypeOf((*cellnet.SessionInit)(nil)).Elem(), 40 | ID: int(util.StringHash("cellnet.SessionInit")), 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /peer/tcp/acceptor.go: -------------------------------------------------------------------------------- 1 | package tcp 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/peer" 6 | "github.com/davyxu/cellnet/util" 7 | "net" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | // 接受器 13 | type tcpAcceptor struct { 14 | peer.SessionManager 15 | peer.CorePeerProperty 16 | peer.CoreContextSet 17 | peer.CoreRunningTag 18 | peer.CoreProcBundle 19 | peer.CoreTCPSocketOption 20 | peer.CoreCaptureIOPanic 21 | 22 | // 保存侦听器 23 | listener net.Listener 24 | } 25 | 26 | func (self *tcpAcceptor) Port() int { 27 | if self.listener == nil { 28 | return 0 29 | } 30 | 31 | return self.listener.Addr().(*net.TCPAddr).Port 32 | } 33 | 34 | func (self *tcpAcceptor) IsReady() bool { 35 | 36 | return self.IsRunning() 37 | } 38 | 39 | // 异步开始侦听 40 | func (self *tcpAcceptor) Start() cellnet.Peer { 41 | 42 | self.WaitStopFinished() 43 | 44 | if self.IsRunning() { 45 | return self 46 | } 47 | 48 | ln, err := util.DetectPort(self.Address(), func(a *util.Address, port int) (interface{}, error) { 49 | return net.Listen("tcp", a.HostPortString(port)) 50 | }) 51 | 52 | if err != nil { 53 | 54 | log.Errorf("#tcp.listen failed(%s) %v", self.Name(), err.Error()) 55 | 56 | self.SetRunning(false) 57 | 58 | return self 59 | } 60 | 61 | self.listener = ln.(net.Listener) 62 | 63 | log.Infof("#tcp.listen(%s) %s", self.Name(), self.ListenAddress()) 64 | 65 | go self.accept() 66 | 67 | return self 68 | } 69 | 70 | func (self *tcpAcceptor) ListenAddress() string { 71 | 72 | pos := strings.Index(self.Address(), ":") 73 | if pos == -1 { 74 | return self.Address() 75 | } 76 | 77 | host := self.Address()[:pos] 78 | 79 | return util.JoinAddress(host, self.Port()) 80 | } 81 | 82 | func (self *tcpAcceptor) accept() { 83 | self.SetRunning(true) 84 | 85 | for { 86 | conn, err := self.listener.Accept() 87 | 88 | if self.IsStopping() { 89 | break 90 | } 91 | 92 | if err == nil { 93 | // 处理连接进入独立线程, 防止accept无法响应 94 | go self.onNewSession(conn) 95 | 96 | }else{ 97 | 98 | if nerr, ok := err.(net.Error); ok && nerr.Temporary(){ 99 | time.Sleep(time.Millisecond) 100 | continue 101 | } 102 | 103 | // 调试状态时, 才打出accept的具体错误 104 | log.Errorf("#tcp.accept failed(%s) %v", self.Name(), err.Error()) 105 | break 106 | } 107 | } 108 | 109 | self.SetRunning(false) 110 | 111 | self.EndStopping() 112 | 113 | } 114 | 115 | func (self *tcpAcceptor) onNewSession(conn net.Conn) { 116 | 117 | self.ApplySocketOption(conn) 118 | 119 | ses := newSession(conn, self, nil) 120 | 121 | ses.Start() 122 | 123 | self.ProcEvent(&cellnet.RecvMsgEvent{ 124 | Ses: ses, 125 | Msg: &cellnet.SessionAccepted{}, 126 | }) 127 | } 128 | 129 | // 停止侦听器 130 | func (self *tcpAcceptor) Stop() { 131 | if !self.IsRunning() { 132 | return 133 | } 134 | 135 | if self.IsStopping() { 136 | return 137 | } 138 | 139 | self.StartStopping() 140 | 141 | self.listener.Close() 142 | 143 | // 断开所有连接 144 | self.CloseAllSession() 145 | 146 | // 等待线程结束 147 | self.WaitStopFinished() 148 | } 149 | 150 | func (self *tcpAcceptor) TypeName() string { 151 | return "tcp.Acceptor" 152 | } 153 | 154 | func init() { 155 | 156 | peer.RegisterPeerCreator(func() cellnet.Peer { 157 | p := &tcpAcceptor{ 158 | SessionManager: new(peer.CoreSessionManager), 159 | } 160 | 161 | p.CoreTCPSocketOption.Init() 162 | 163 | return p 164 | }) 165 | } 166 | -------------------------------------------------------------------------------- /peer/tcp/connector.go: -------------------------------------------------------------------------------- 1 | package tcp 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/peer" 6 | "net" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | type tcpConnector struct { 12 | peer.SessionManager 13 | 14 | peer.CorePeerProperty 15 | peer.CoreContextSet 16 | peer.CoreRunningTag 17 | peer.CoreProcBundle 18 | peer.CoreTCPSocketOption 19 | peer.CoreCaptureIOPanic 20 | 21 | defaultSes *tcpSession 22 | 23 | tryConnTimes int // 尝试连接次数 24 | 25 | sesEndSignal sync.WaitGroup 26 | 27 | reconDur time.Duration 28 | } 29 | 30 | func (self *tcpConnector) Start() cellnet.Peer { 31 | 32 | self.WaitStopFinished() 33 | 34 | if self.IsRunning() { 35 | return self 36 | } 37 | 38 | go self.connect(self.Address()) 39 | 40 | return self 41 | } 42 | 43 | func (self *tcpConnector) Session() cellnet.Session { 44 | return self.defaultSes 45 | } 46 | 47 | func (self *tcpConnector) SetSessionManager(raw interface{}) { 48 | self.SessionManager = raw.(peer.SessionManager) 49 | } 50 | 51 | func (self *tcpConnector) Stop() { 52 | if !self.IsRunning() { 53 | return 54 | } 55 | 56 | if self.IsStopping() { 57 | return 58 | } 59 | 60 | self.StartStopping() 61 | 62 | // 通知发送关闭 63 | self.defaultSes.Close() 64 | 65 | // 等待线程结束 66 | self.WaitStopFinished() 67 | 68 | } 69 | 70 | func (self *tcpConnector) ReconnectDuration() time.Duration { 71 | 72 | return self.reconDur 73 | } 74 | 75 | func (self *tcpConnector) SetReconnectDuration(v time.Duration) { 76 | self.reconDur = v 77 | } 78 | 79 | func (self *tcpConnector) Port() int { 80 | 81 | conn := self.defaultSes.Conn() 82 | 83 | if conn == nil { 84 | return 0 85 | } 86 | 87 | return conn.LocalAddr().(*net.TCPAddr).Port 88 | } 89 | 90 | const reportConnectFailedLimitTimes = 3 91 | 92 | // 连接器,传入连接地址和发送封包次数 93 | func (self *tcpConnector) connect(address string) { 94 | 95 | self.SetRunning(true) 96 | 97 | for { 98 | self.tryConnTimes++ 99 | 100 | // 尝试用Socket连接地址 101 | conn, err := net.Dial("tcp", address) 102 | 103 | self.defaultSes.setConn(conn) 104 | 105 | // 发生错误时退出 106 | if err != nil { 107 | 108 | if self.tryConnTimes <= reportConnectFailedLimitTimes { 109 | log.Errorf("#tcp.connect failed(%s) %v", self.Name(), err.Error()) 110 | 111 | if self.tryConnTimes == reportConnectFailedLimitTimes { 112 | log.Errorf("(%s) continue reconnecting, but mute log", self.Name()) 113 | } 114 | } 115 | 116 | // 没重连就退出 117 | if self.ReconnectDuration() == 0 || self.IsStopping() { 118 | 119 | self.ProcEvent(&cellnet.RecvMsgEvent{ 120 | Ses: self.defaultSes, 121 | Msg: &cellnet.SessionConnectError{}, 122 | }) 123 | break 124 | } 125 | 126 | // 有重连就等待 127 | time.Sleep(self.ReconnectDuration()) 128 | 129 | // 继续连接 130 | continue 131 | } 132 | 133 | self.sesEndSignal.Add(1) 134 | 135 | self.ApplySocketOption(conn) 136 | 137 | self.defaultSes.Start() 138 | 139 | self.tryConnTimes = 0 140 | 141 | self.ProcEvent(&cellnet.RecvMsgEvent{Ses: self.defaultSes, Msg: &cellnet.SessionConnected{}}) 142 | 143 | self.sesEndSignal.Wait() 144 | 145 | self.defaultSes.setConn(nil) 146 | 147 | // 没重连就退出/主动退出 148 | if self.IsStopping() || self.ReconnectDuration() == 0 { 149 | break 150 | } 151 | 152 | // 有重连就等待 153 | time.Sleep(self.ReconnectDuration()) 154 | 155 | // 继续连接 156 | continue 157 | 158 | } 159 | 160 | self.SetRunning(false) 161 | 162 | self.EndStopping() 163 | } 164 | 165 | func (self *tcpConnector) IsReady() bool { 166 | 167 | return self.SessionCount() != 0 168 | } 169 | 170 | func (self *tcpConnector) TypeName() string { 171 | return "tcp.Connector" 172 | } 173 | 174 | func init() { 175 | 176 | peer.RegisterPeerCreator(func() cellnet.Peer { 177 | self := &tcpConnector{ 178 | SessionManager: new(peer.CoreSessionManager), 179 | } 180 | 181 | self.defaultSes = newSession(nil, self, func() { 182 | self.sesEndSignal.Done() 183 | }) 184 | 185 | self.CoreTCPSocketOption.Init() 186 | 187 | return self 188 | }) 189 | } 190 | -------------------------------------------------------------------------------- /peer/tcp/log.go: -------------------------------------------------------------------------------- 1 | package tcp 2 | 3 | import ( 4 | "github.com/davyxu/golog" 5 | ) 6 | 7 | var log = golog.New("tcppeer") 8 | -------------------------------------------------------------------------------- /peer/tcp/syncconn.go: -------------------------------------------------------------------------------- 1 | package tcp 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/peer" 6 | "net" 7 | "time" 8 | ) 9 | 10 | type tcpSyncConnector struct { 11 | peer.SessionManager 12 | 13 | peer.CorePeerProperty 14 | peer.CoreContextSet 15 | peer.CoreProcBundle 16 | peer.CoreTCPSocketOption 17 | 18 | defaultSes *tcpSession 19 | } 20 | 21 | func (self *tcpSyncConnector) Port() int { 22 | conn := self.defaultSes.Conn() 23 | 24 | if conn == nil { 25 | return 0 26 | } 27 | 28 | return conn.LocalAddr().(*net.TCPAddr).Port 29 | } 30 | 31 | func (self *tcpSyncConnector) Start() cellnet.Peer { 32 | 33 | // 尝试用Socket连接地址 34 | conn, err := net.Dial("tcp", self.Address()) 35 | 36 | // 发生错误时退出 37 | if err != nil { 38 | 39 | log.Debugf("#tcp.connect failed(%s)@%d address: %s", self.Name(), self.defaultSes.ID(), self.Address()) 40 | 41 | self.ProcEvent(&cellnet.RecvMsgEvent{Ses: self.defaultSes, Msg: &cellnet.SessionConnectError{}}) 42 | return self 43 | } 44 | 45 | self.defaultSes.setConn(conn) 46 | 47 | self.ApplySocketOption(conn) 48 | 49 | self.defaultSes.Start() 50 | 51 | self.ProcEvent(&cellnet.RecvMsgEvent{Ses: self.defaultSes, Msg: &cellnet.SessionConnected{}}) 52 | 53 | return self 54 | } 55 | 56 | func (self *tcpSyncConnector) Session() cellnet.Session { 57 | return self.defaultSes 58 | } 59 | 60 | func (self *tcpSyncConnector) SetSessionManager(raw interface{}) { 61 | self.SessionManager = raw.(peer.SessionManager) 62 | } 63 | 64 | func (self *tcpSyncConnector) ReconnectDuration() time.Duration { 65 | return 0 66 | } 67 | 68 | func (self *tcpSyncConnector) SetReconnectDuration(v time.Duration) { 69 | 70 | } 71 | 72 | func (self *tcpSyncConnector) Stop() { 73 | 74 | if self.defaultSes != nil { 75 | self.defaultSes.Close() 76 | } 77 | 78 | } 79 | 80 | func (self *tcpSyncConnector) IsReady() bool { 81 | 82 | return self.SessionCount() != 0 83 | } 84 | 85 | func (self *tcpSyncConnector) TypeName() string { 86 | return "tcp.SyncConnector" 87 | } 88 | 89 | func init() { 90 | 91 | peer.RegisterPeerCreator(func() cellnet.Peer { 92 | self := &tcpSyncConnector{ 93 | SessionManager: new(peer.CoreSessionManager), 94 | } 95 | 96 | self.defaultSes = newSession(nil, self, nil) 97 | 98 | self.CoreTCPSocketOption.Init() 99 | 100 | return self 101 | }) 102 | } 103 | -------------------------------------------------------------------------------- /peer/udp/connector.go: -------------------------------------------------------------------------------- 1 | package udp 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/peer" 6 | "net" 7 | ) 8 | 9 | type udpConnector struct { 10 | peer.CoreSessionManager 11 | peer.CorePeerProperty 12 | peer.CoreContextSet 13 | peer.CoreRunningTag 14 | peer.CoreProcBundle 15 | peer.CoreCaptureIOPanic 16 | 17 | remoteAddr *net.UDPAddr 18 | 19 | defaultSes *udpSession 20 | } 21 | 22 | func (self *udpConnector) Start() cellnet.Peer { 23 | 24 | var err error 25 | self.remoteAddr, err = net.ResolveUDPAddr("udp", self.Address()) 26 | 27 | if err != nil { 28 | 29 | log.Errorf("#resolve udp address failed(%s) %v", self.Name(), err.Error()) 30 | return self 31 | } 32 | 33 | go self.connect() 34 | 35 | return self 36 | } 37 | 38 | func (self *udpConnector) Session() cellnet.Session { 39 | return self.defaultSes 40 | } 41 | 42 | func (self *udpConnector) IsReady() bool { 43 | 44 | return self.defaultSes.Conn() != nil 45 | } 46 | 47 | func (self *udpConnector) connect() { 48 | 49 | conn, err := net.DialUDP("udp", nil, self.remoteAddr) 50 | if err != nil { 51 | 52 | log.Errorf("#udp.connect failed(%s) %v", self.Name(), err.Error()) 53 | return 54 | } 55 | 56 | self.defaultSes.setConn(conn) 57 | 58 | ses := self.defaultSes 59 | 60 | self.ProcEvent(&cellnet.RecvMsgEvent{ses, &cellnet.SessionConnected{}}) 61 | 62 | recvBuff := make([]byte, MaxUDPRecvBuffer) 63 | 64 | self.SetRunning(true) 65 | 66 | for self.IsRunning() { 67 | 68 | n, _, err := conn.ReadFromUDP(recvBuff) 69 | if err != nil { 70 | break 71 | } 72 | 73 | if n > 0 { 74 | ses.Recv(recvBuff[:n]) 75 | } 76 | 77 | } 78 | } 79 | 80 | func (self *udpConnector) Stop() { 81 | 82 | self.SetRunning(false) 83 | 84 | if c := self.defaultSes.Conn(); c != nil { 85 | c.Close() 86 | } 87 | } 88 | 89 | func (self *udpConnector) TypeName() string { 90 | return "udp.Connector" 91 | } 92 | 93 | func init() { 94 | 95 | peer.RegisterPeerCreator(func() cellnet.Peer { 96 | p := &udpConnector{} 97 | 98 | p.defaultSes = &udpSession{ 99 | pInterface: p, 100 | CoreProcBundle: &p.CoreProcBundle, 101 | } 102 | 103 | return p 104 | }) 105 | } 106 | -------------------------------------------------------------------------------- /peer/udp/log.go: -------------------------------------------------------------------------------- 1 | package udp 2 | 3 | import ( 4 | "github.com/davyxu/golog" 5 | ) 6 | 7 | var log = golog.New("udppeer") 8 | -------------------------------------------------------------------------------- /peer/udp/session.go: -------------------------------------------------------------------------------- 1 | package udp 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/peer" 6 | "net" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | type DataReader interface { 12 | ReadData() []byte 13 | } 14 | 15 | type DataWriter interface { 16 | WriteData(data []byte) 17 | } 18 | 19 | // Socket会话 20 | type udpSession struct { 21 | *peer.CoreProcBundle 22 | peer.CoreContextSet 23 | 24 | pInterface cellnet.Peer 25 | 26 | pkt []byte 27 | 28 | // Socket原始连接 29 | remote *net.UDPAddr 30 | conn *net.UDPConn 31 | connGuard sync.RWMutex 32 | timeOutTick time.Time 33 | key *connTrackKey 34 | } 35 | 36 | func (self *udpSession) setConn(conn *net.UDPConn) { 37 | self.connGuard.Lock() 38 | self.conn = conn 39 | self.connGuard.Unlock() 40 | } 41 | 42 | func (self *udpSession) Conn() *net.UDPConn { 43 | self.connGuard.RLock() 44 | defer self.connGuard.RUnlock() 45 | return self.conn 46 | } 47 | 48 | func (self *udpSession) IsAlive() bool { 49 | return time.Now().Before(self.timeOutTick) 50 | } 51 | 52 | func (self *udpSession) ID() int64 { 53 | return 0 54 | } 55 | 56 | func (self *udpSession) LocalAddress() net.Addr { 57 | return self.Conn().LocalAddr() 58 | } 59 | 60 | func (self *udpSession) Peer() cellnet.Peer { 61 | return self.pInterface 62 | } 63 | 64 | // 取原始连接 65 | func (self *udpSession) Raw() interface{} { 66 | return self 67 | } 68 | 69 | func (self *udpSession) Recv(data []byte) { 70 | 71 | self.pkt = data 72 | 73 | msg, err := self.ReadMessage(self) 74 | 75 | if msg != nil && err == nil { 76 | self.ProcEvent(&cellnet.RecvMsgEvent{self, msg}) 77 | } 78 | } 79 | 80 | func (self *udpSession) ReadData() []byte { 81 | return self.pkt 82 | } 83 | 84 | func (self *udpSession) WriteData(data []byte) { 85 | 86 | c := self.Conn() 87 | if c == nil { 88 | return 89 | } 90 | 91 | // Connector中的Session 92 | if self.remote == nil { 93 | 94 | c.Write(data) 95 | 96 | // Acceptor中的Session 97 | } else { 98 | c.WriteToUDP(data, self.remote) 99 | } 100 | } 101 | 102 | // 发送封包 103 | func (self *udpSession) Send(msg interface{}) { 104 | 105 | self.SendMessage(&cellnet.SendMsgEvent{self, msg}) 106 | } 107 | 108 | func (self *udpSession) Close() { 109 | 110 | } 111 | -------------------------------------------------------------------------------- /peer/udp/trackkey.go: -------------------------------------------------------------------------------- 1 | package udp 2 | 3 | import ( 4 | "encoding/binary" 5 | "net" 6 | ) 7 | 8 | // https://github.com/docker/go-connections/blob/master/proxy/udp_proxy.go 9 | // A net.Addr where the IP is split into two fields so you can use it as a key 10 | // in a map: 11 | type connTrackKey struct { 12 | IPHigh uint64 13 | IPLow uint64 14 | Port int 15 | } 16 | 17 | func newConnTrackKey(addr *net.UDPAddr) *connTrackKey { 18 | if len(addr.IP) == net.IPv4len { 19 | return &connTrackKey{ 20 | IPHigh: 0, 21 | IPLow: uint64(binary.BigEndian.Uint32(addr.IP)), 22 | Port: addr.Port, 23 | } 24 | } 25 | return &connTrackKey{ 26 | IPHigh: binary.BigEndian.Uint64(addr.IP[:8]), 27 | IPLow: binary.BigEndian.Uint64(addr.IP[8:]), 28 | Port: addr.Port, 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /peer_db.go: -------------------------------------------------------------------------------- 1 | package cellnet 2 | 3 | import "time" 4 | 5 | type RedisPoolOperator interface { 6 | // 获取 7 | Operate(callback func(rawClient interface{}) interface{}) interface{} 8 | } 9 | 10 | type RedisConnector interface { 11 | GenericPeer 12 | 13 | // 设置密码 14 | SetPassword(v string) 15 | 16 | // 设置连接数 17 | SetConnectionCount(v int) 18 | 19 | // 设置库索引 20 | SetDBIndex(v int) 21 | } 22 | 23 | type MySQLOperator interface { 24 | Operate(callback func(rawClient interface{}) interface{}) interface{} 25 | } 26 | 27 | type MySQLConnector interface { 28 | GenericPeer 29 | 30 | // 设置密码 31 | SetPassword(v string) 32 | 33 | // 设置连接数 34 | SetConnectionCount(v int) 35 | 36 | // 设置自动重连间隔, 0为默认值,关闭自动重连 37 | SetReconnectDuration(v time.Duration) 38 | 39 | ReconnectDuration() time.Duration 40 | } 41 | -------------------------------------------------------------------------------- /peer_http.go: -------------------------------------------------------------------------------- 1 | package cellnet 2 | 3 | import ( 4 | "html/template" 5 | "net/http" 6 | ) 7 | 8 | type HTTPAcceptor interface { 9 | GenericPeer 10 | 11 | // 设置http文件服务虚拟地址和文件系统根目录 12 | SetFileServe(dir string, root string) 13 | 14 | // 设置模板文件地址 15 | SetTemplateDir(dir string) 16 | 17 | // 设置http模板的分隔符,解决默认{{ }}冲突问题 18 | SetTemplateDelims(delimsLeft, delimsRight string) 19 | 20 | // 设置模板的扩展名,默认: .tpl .html 21 | SetTemplateExtensions(exts []string) 22 | 23 | // 设置模板函数入口 24 | SetTemplateFunc(f []template.FuncMap) 25 | } 26 | 27 | type HTTPRequest struct { 28 | REQMsg interface{} // 请求消息, 指针 29 | ACKMsg interface{} // 回应消息, 指针 30 | REQCodecName string // 可为空, 默认为json格式 31 | ACKCodecName string // 可为空, 默认为json格式 32 | } 33 | 34 | // HTTP连接器接口 35 | type HTTPConnector interface { 36 | GenericPeer 37 | Request(method, path string, param *HTTPRequest) error 38 | } 39 | 40 | type HTTPSession interface { 41 | Request() *http.Request 42 | } 43 | -------------------------------------------------------------------------------- /peer_tcp.go: -------------------------------------------------------------------------------- 1 | package cellnet 2 | 3 | import "time" 4 | 5 | // TCP 6 | type TCPSocketOption interface { 7 | // 收发缓冲大小,默认-1 8 | SetSocketBuffer(readBufferSize, writeBufferSize int, noDelay bool) 9 | 10 | // 设置最大的封包大小 11 | SetMaxPacketSize(maxSize int) 12 | 13 | // 设置读写超时,默认0,不超时 14 | SetSocketDeadline(read, write time.Duration) 15 | } 16 | 17 | // TCP接受器,具备会话访问 18 | type TCPAcceptor interface { 19 | GenericPeer 20 | 21 | // 访问会话 22 | SessionAccessor 23 | 24 | TCPSocketOption 25 | 26 | // 查看当前侦听端口,使用host:0 作为Address时,socket底层自动分配侦听端口 27 | Port() int 28 | } 29 | 30 | // TCP连接器 31 | type TCPConnector interface { 32 | GenericPeer 33 | 34 | TCPSocketOption 35 | 36 | // 设置重连时间 37 | SetReconnectDuration(time.Duration) 38 | 39 | // 获取重连时间 40 | ReconnectDuration() time.Duration 41 | 42 | // 默认会话 43 | Session() Session 44 | 45 | // 设置会话管理器 实现peer.SessionManager接口 46 | SetSessionManager(raw interface{}) 47 | 48 | // 查看当前连接使用的端口 49 | Port() int 50 | } 51 | -------------------------------------------------------------------------------- /peer_udp.go: -------------------------------------------------------------------------------- 1 | package cellnet 2 | 3 | import "time" 4 | 5 | // UDP连接器 6 | type UDPConnector interface { 7 | GenericPeer 8 | 9 | // 默认会话 10 | Session() Session 11 | } 12 | 13 | // UDP接受器 14 | type UDPAcceptor interface { 15 | 16 | // 底层使用TTL做session生命期管理,超时时间越短,内存占用越低 17 | SetSessionTTL(dur time.Duration) 18 | } 19 | -------------------------------------------------------------------------------- /peer_ws.go: -------------------------------------------------------------------------------- 1 | package cellnet 2 | 3 | import "time" 4 | 5 | // Websocket接受器,具备会话访问 6 | type WSAcceptor interface { 7 | GenericPeer 8 | 9 | // 访问会话 10 | SessionAccessor 11 | 12 | SetHttps(certfile, keyfile string) 13 | 14 | // 设置升级器 15 | SetUpgrader(upgrader interface{}) 16 | 17 | // 查看当前侦听端口,使用host:0 作为Address时,socket底层自动分配侦听端口 18 | Port() int 19 | } 20 | 21 | // Websocket连接器 22 | type WSConnector interface { 23 | GenericPeer 24 | 25 | // 设置重连时间 26 | SetReconnectDuration(time.Duration) 27 | 28 | // 获取重连时间 29 | ReconnectDuration() time.Duration 30 | 31 | // 默认会话 32 | Session() Session 33 | 34 | // 设置会话管理器 实现peer.SessionManager接口 35 | SetSessionManager(raw interface{}) 36 | 37 | // 查看当前连接使用的端口 38 | Port() int 39 | } 40 | -------------------------------------------------------------------------------- /pipe.go: -------------------------------------------------------------------------------- 1 | package cellnet 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // 不限制大小,添加不发生阻塞,接收阻塞等待 8 | type Pipe struct { 9 | list []interface{} 10 | listGuard sync.Mutex 11 | listCond *sync.Cond 12 | } 13 | 14 | // 添加时不会发送阻塞 15 | func (self *Pipe) Add(msg interface{}) { 16 | self.listGuard.Lock() 17 | self.list = append(self.list, msg) 18 | self.listGuard.Unlock() 19 | 20 | self.listCond.Signal() 21 | } 22 | 23 | func (self *Pipe) Count() int { 24 | self.listGuard.Lock() 25 | defer self.listGuard.Unlock() 26 | return len(self.list) 27 | } 28 | 29 | func (self *Pipe) Reset() { 30 | self.listGuard.Lock() 31 | self.list = self.list[0:0] 32 | self.listGuard.Unlock() 33 | } 34 | 35 | // 如果没有数据,发生阻塞 36 | func (self *Pipe) Pick(retList *[]interface{}) (exit bool) { 37 | 38 | self.listGuard.Lock() 39 | 40 | for len(self.list) == 0 { 41 | self.listCond.Wait() 42 | } 43 | 44 | // self.listGuard.Unlock() 45 | 46 | // self.listGuard.Lock() 47 | 48 | // 复制出队列 49 | 50 | for _, data := range self.list { 51 | 52 | if data == nil { 53 | exit = true 54 | break 55 | } else { 56 | *retList = append(*retList, data) 57 | } 58 | } 59 | 60 | self.list = self.list[0:0] 61 | self.listGuard.Unlock() 62 | 63 | return 64 | } 65 | 66 | func NewPipe() *Pipe { 67 | self := &Pipe{} 68 | self.listCond = sync.NewCond(&self.listGuard) 69 | 70 | return self 71 | } 72 | -------------------------------------------------------------------------------- /proc/gorillaws/hooker.go: -------------------------------------------------------------------------------- 1 | package gorillaws 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/msglog" 6 | ) 7 | 8 | // 带有RPC和relay功能 9 | type MsgHooker struct { 10 | } 11 | 12 | func (self MsgHooker) OnInboundEvent(inputEvent cellnet.Event) (outputEvent cellnet.Event) { 13 | 14 | msglog.WriteRecvLogger(log, "ws", inputEvent.Session(), inputEvent.Message()) 15 | 16 | return inputEvent 17 | } 18 | 19 | func (self MsgHooker) OnOutboundEvent(inputEvent cellnet.Event) (outputEvent cellnet.Event) { 20 | 21 | msglog.WriteSendLogger(log, "ws", inputEvent.Session(), inputEvent.Message()) 22 | 23 | return inputEvent 24 | } 25 | -------------------------------------------------------------------------------- /proc/gorillaws/log.go: -------------------------------------------------------------------------------- 1 | package gorillaws 2 | 3 | import ( 4 | "github.com/davyxu/golog" 5 | ) 6 | 7 | var log = golog.New("gorillawsproc") 8 | -------------------------------------------------------------------------------- /proc/gorillaws/setup.go: -------------------------------------------------------------------------------- 1 | package gorillaws 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/proc" 6 | ) 7 | 8 | func init() { 9 | 10 | proc.RegisterProcessor("gorillaws.ltv", func(bundle proc.ProcessorBundle, userCallback cellnet.EventCallback, args ...interface{}) { 11 | 12 | bundle.SetTransmitter(new(WSMessageTransmitter)) 13 | bundle.SetHooker(new(MsgHooker)) 14 | bundle.SetCallback(proc.NewQueuedEventCallback(userCallback)) 15 | 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /proc/gorillaws/transmitter.go: -------------------------------------------------------------------------------- 1 | package gorillaws 2 | 3 | import ( 4 | "encoding/binary" 5 | "github.com/davyxu/cellnet" 6 | "github.com/davyxu/cellnet/codec" 7 | "github.com/davyxu/cellnet/util" 8 | "github.com/gorilla/websocket" 9 | ) 10 | 11 | const ( 12 | MsgIDSize = 2 // uint16 13 | ) 14 | 15 | type WSMessageTransmitter struct { 16 | } 17 | 18 | func (WSMessageTransmitter) OnRecvMessage(ses cellnet.Session) (msg interface{}, err error) { 19 | 20 | conn, ok := ses.Raw().(*websocket.Conn) 21 | 22 | // 转换错误,或者连接已经关闭时退出 23 | if !ok || conn == nil { 24 | return nil, nil 25 | } 26 | 27 | var messageType int 28 | var raw []byte 29 | messageType, raw, err = conn.ReadMessage() 30 | 31 | if err != nil { 32 | return 33 | } 34 | 35 | if len(raw) < MsgIDSize { 36 | return nil, util.ErrMinPacket 37 | } 38 | 39 | switch messageType { 40 | case websocket.BinaryMessage: 41 | msgID := binary.LittleEndian.Uint16(raw) 42 | msgData := raw[MsgIDSize:] 43 | 44 | msg, _, err = codec.DecodeMessage(int(msgID), msgData) 45 | } 46 | 47 | return 48 | } 49 | 50 | func (WSMessageTransmitter) OnSendMessage(ses cellnet.Session, msg interface{}) error { 51 | 52 | conn, ok := ses.Raw().(*websocket.Conn) 53 | 54 | // 转换错误,或者连接已经关闭时退出 55 | if !ok || conn == nil { 56 | return nil 57 | } 58 | 59 | var ( 60 | msgData []byte 61 | msgID int 62 | ) 63 | 64 | switch m := msg.(type) { 65 | case *cellnet.RawPacket: // 发裸包 66 | msgData = m.MsgData 67 | msgID = m.MsgID 68 | default: // 发普通编码包 69 | var err error 70 | var meta *cellnet.MessageMeta 71 | 72 | // 将用户数据转换为字节数组和消息ID 73 | msgData, meta, err = codec.EncodeMessage(msg, nil) 74 | 75 | if err != nil { 76 | return err 77 | } 78 | 79 | msgID = meta.ID 80 | } 81 | 82 | pkt := make([]byte, MsgIDSize+len(msgData)) 83 | binary.LittleEndian.PutUint16(pkt, uint16(msgID)) 84 | copy(pkt[MsgIDSize:], msgData) 85 | 86 | conn.WriteMessage(websocket.BinaryMessage, pkt) 87 | 88 | return nil 89 | } 90 | -------------------------------------------------------------------------------- /proc/http/log.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "github.com/davyxu/golog" 5 | ) 6 | 7 | var log = golog.New("httpproc") 8 | -------------------------------------------------------------------------------- /proc/http/setup.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/proc" 6 | ) 7 | 8 | func init() { 9 | 10 | proc.RegisterProcessor("http", func(bundle proc.ProcessorBundle, userCallback cellnet.EventCallback, args ...interface{}) { 11 | // 如果http的peer有队列,依然会在队列中排队执行 12 | bundle.SetCallback(proc.NewQueuedEventCallback(userCallback)) 13 | }) 14 | 15 | } 16 | -------------------------------------------------------------------------------- /proc/msgdispatcher.go: -------------------------------------------------------------------------------- 1 | package proc 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "reflect" 6 | "sync" 7 | ) 8 | 9 | // 消息派发器,可选件,兼容v3以前的注册及派发消息方式,在没有代码生成框架及工具时是较方便的接收处理接口 10 | type MessageDispatcher struct { 11 | handlerByType map[reflect.Type][]cellnet.EventCallback 12 | handlerByTypeGuard sync.RWMutex 13 | } 14 | 15 | func (self *MessageDispatcher) OnEvent(ev cellnet.Event) { 16 | 17 | msgType := reflect.TypeOf(ev.Message()) 18 | 19 | if msgType == nil { 20 | return 21 | } 22 | 23 | self.handlerByTypeGuard.RLock() 24 | handlers, ok := self.handlerByType[msgType.Elem()] 25 | self.handlerByTypeGuard.RUnlock() 26 | 27 | if ok { 28 | 29 | for _, callback := range handlers { 30 | 31 | callback(ev) 32 | } 33 | 34 | } 35 | } 36 | 37 | func (self *MessageDispatcher) Exists(msgName string) bool { 38 | meta := cellnet.MessageMetaByFullName(msgName) 39 | if meta == nil { 40 | return false 41 | } 42 | 43 | self.handlerByTypeGuard.Lock() 44 | defer self.handlerByTypeGuard.Unlock() 45 | 46 | handlers, _ := self.handlerByType[meta.Type] 47 | return len(handlers) > 0 48 | } 49 | 50 | func (self *MessageDispatcher) RegisterMessage(msgName string, userCallback cellnet.EventCallback) { 51 | meta := cellnet.MessageMetaByFullName(msgName) 52 | if meta == nil { 53 | panic("message not found:" + msgName) 54 | } 55 | 56 | self.handlerByTypeGuard.Lock() 57 | handlers, _ := self.handlerByType[meta.Type] 58 | handlers = append(handlers, userCallback) 59 | self.handlerByType[meta.Type] = handlers 60 | self.handlerByTypeGuard.Unlock() 61 | } 62 | 63 | func NewMessageDispatcher() *MessageDispatcher { 64 | 65 | return &MessageDispatcher{ 66 | handlerByType: make(map[reflect.Type][]cellnet.EventCallback), 67 | } 68 | } 69 | 70 | func NewMessageDispatcherBindPeer(peer cellnet.Peer, processorName string) *MessageDispatcher { 71 | 72 | self := NewMessageDispatcher() 73 | 74 | BindProcessorHandler(peer, processorName, self.OnEvent) 75 | 76 | return self 77 | } 78 | -------------------------------------------------------------------------------- /proc/procbundle.go: -------------------------------------------------------------------------------- 1 | package proc 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | ) 6 | 7 | // 处理器设置接口,由各Peer实现 8 | type ProcessorBundle interface { 9 | 10 | // 设置 传输器,负责收发消息 11 | SetTransmitter(v cellnet.MessageTransmitter) 12 | 13 | // 设置 接收后,发送前的事件处理流程 14 | SetHooker(v cellnet.EventHooker) 15 | 16 | // 设置 接收后最终处理回调 17 | SetCallback(v cellnet.EventCallback) 18 | } 19 | 20 | // 让EventCallback保证放在ses的队列里,而不是并发的 21 | func NewQueuedEventCallback(callback cellnet.EventCallback) cellnet.EventCallback { 22 | 23 | return func(ev cellnet.Event) { 24 | if callback != nil { 25 | cellnet.SessionQueuedCall(ev.Session(), func() { 26 | 27 | callback(ev) 28 | }) 29 | } 30 | } 31 | } 32 | 33 | // 当需要多个Hooker时,使用NewMultiHooker将多个hooker合并成1个hooker处理 34 | type MultiHooker []cellnet.EventHooker 35 | 36 | func (self MultiHooker) OnInboundEvent(input cellnet.Event) (output cellnet.Event) { 37 | 38 | for _, h := range self { 39 | 40 | input = h.OnInboundEvent(input) 41 | 42 | if input == nil { 43 | break 44 | } 45 | } 46 | 47 | return input 48 | } 49 | 50 | func (self MultiHooker) OnOutboundEvent(input cellnet.Event) (output cellnet.Event) { 51 | 52 | for _, h := range self { 53 | 54 | input = h.OnOutboundEvent(input) 55 | 56 | if input == nil { 57 | break 58 | } 59 | } 60 | 61 | return input 62 | } 63 | 64 | func NewMultiHooker(h ...cellnet.EventHooker) cellnet.EventHooker { 65 | 66 | return MultiHooker(h) 67 | } 68 | -------------------------------------------------------------------------------- /proc/procreg.go: -------------------------------------------------------------------------------- 1 | package proc 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davyxu/cellnet" 6 | "sort" 7 | ) 8 | 9 | type ProcessorBinder func(bundle ProcessorBundle, userCallback cellnet.EventCallback, args ...interface{}) 10 | 11 | var ( 12 | procByName = map[string]ProcessorBinder{} 13 | ) 14 | 15 | // 注册事件处理器,内部及自定义收发流程时使用 16 | func RegisterProcessor(procName string, f ProcessorBinder) { 17 | 18 | if _, ok := procByName[procName]; ok { 19 | panic("duplicate peer type: " + procName) 20 | } 21 | 22 | procByName[procName] = f 23 | } 24 | 25 | // 获取处理器列表 26 | func ProcessorList() (ret []string) { 27 | 28 | for name := range procByName { 29 | ret = append(ret, name) 30 | } 31 | 32 | sort.Strings(ret) 33 | return 34 | } 35 | 36 | func getPackageByCodecName(name string) string { 37 | switch name { 38 | case "gorillaws.ltv": 39 | return "github.com/davyxu/cellnet/proc/gorillaws" 40 | case "http": 41 | return "github.com/davyxu/cellnet/proc/http" 42 | case "tcp.ltv": 43 | return "github.com/davyxu/cellnet/proc/tcp" 44 | case "udp.ltv": 45 | return "github.com/davyxu/cellnet/proc/udp" 46 | default: 47 | return "package/to/your/proc" 48 | } 49 | } 50 | 51 | // 绑定固定回调处理器, procName来源于RegisterProcessor注册的处理器,形如: 'tcp.ltv' 52 | func BindProcessorHandler(peer cellnet.Peer, procName string, userCallback cellnet.EventCallback, args ...interface{}) { 53 | 54 | if proc, ok := procByName[procName]; ok { 55 | 56 | bundle := peer.(ProcessorBundle) 57 | 58 | proc(bundle, userCallback, args...) 59 | 60 | } else { 61 | panic(fmt.Sprintf("processor not found '%s'\ntry to add code below:\nimport (\n _ \"%s\"\n)\n\n", 62 | procName, 63 | getPackageByCodecName(procName))) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /proc/syncrecv.go: -------------------------------------------------------------------------------- 1 | package proc 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "reflect" 6 | "sync" 7 | ) 8 | 9 | // 同步接收消息器, 可选件,可作为流程测试辅助工具 10 | type SyncReceiver struct { 11 | evChan chan cellnet.Event 12 | 13 | callback func(ev cellnet.Event) 14 | } 15 | 16 | // 将处理回调返回给BindProcessorHandler用于注册 17 | func (self *SyncReceiver) EventCallback() cellnet.EventCallback { 18 | 19 | return self.callback 20 | } 21 | 22 | // 持续阻塞,直到某个消息到达后,使用回调返回消息 23 | func (self *SyncReceiver) Recv(callback cellnet.EventCallback) *SyncReceiver { 24 | callback(<-self.evChan) 25 | return self 26 | } 27 | 28 | // 持续阻塞,直到某个消息到达后,返回消息 29 | func (self *SyncReceiver) WaitMessage(msgName string) (msg interface{}) { 30 | 31 | var wg sync.WaitGroup 32 | 33 | meta := cellnet.MessageMetaByFullName(msgName) 34 | if meta == nil { 35 | panic("unknown message name:" + msgName) 36 | } 37 | 38 | wg.Add(1) 39 | 40 | self.Recv(func(ev cellnet.Event) { 41 | 42 | inMeta := cellnet.MessageMetaByType(reflect.TypeOf(ev.Message())) 43 | if inMeta == meta { 44 | msg = ev.Message() 45 | wg.Done() 46 | } 47 | 48 | }) 49 | 50 | wg.Wait() 51 | return 52 | } 53 | 54 | func NewSyncReceiver(p cellnet.Peer) *SyncReceiver { 55 | 56 | self := &SyncReceiver{ 57 | evChan: make(chan cellnet.Event), 58 | } 59 | 60 | self.callback = func(ev cellnet.Event) { 61 | 62 | self.evChan <- ev 63 | } 64 | 65 | return self 66 | } 67 | -------------------------------------------------------------------------------- /proc/tcp/hooker.go: -------------------------------------------------------------------------------- 1 | package tcp 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/msglog" 6 | "github.com/davyxu/cellnet/relay" 7 | "github.com/davyxu/cellnet/rpc" 8 | ) 9 | 10 | // 带有RPC和relay功能 11 | type MsgHooker struct { 12 | } 13 | 14 | func (self MsgHooker) OnInboundEvent(inputEvent cellnet.Event) (outputEvent cellnet.Event) { 15 | 16 | var handled bool 17 | var err error 18 | 19 | inputEvent, handled, err = rpc.ResolveInboundEvent(inputEvent) 20 | 21 | if err != nil { 22 | log.Errorln("rpc.ResolveInboundEvent:", err) 23 | return 24 | } 25 | 26 | if !handled { 27 | 28 | inputEvent, handled, err = relay.ResoleveInboundEvent(inputEvent) 29 | 30 | if err != nil { 31 | log.Errorln("relay.ResoleveInboundEvent:", err) 32 | return 33 | } 34 | 35 | if !handled { 36 | msglog.WriteRecvLogger(log, "tcp", inputEvent.Session(), inputEvent.Message()) 37 | } 38 | } 39 | 40 | return inputEvent 41 | } 42 | 43 | func (self MsgHooker) OnOutboundEvent(inputEvent cellnet.Event) (outputEvent cellnet.Event) { 44 | 45 | handled, err := rpc.ResolveOutboundEvent(inputEvent) 46 | 47 | if err != nil { 48 | log.Errorln("rpc.ResolveOutboundEvent:", err) 49 | return nil 50 | } 51 | 52 | if !handled { 53 | 54 | handled, err = relay.ResolveOutboundEvent(inputEvent) 55 | 56 | if err != nil { 57 | log.Errorln("relay.ResolveOutboundEvent:", err) 58 | return nil 59 | } 60 | 61 | if !handled { 62 | msglog.WriteSendLogger(log, "tcp", inputEvent.Session(), inputEvent.Message()) 63 | } 64 | } 65 | 66 | return inputEvent 67 | } 68 | -------------------------------------------------------------------------------- /proc/tcp/log.go: -------------------------------------------------------------------------------- 1 | package tcp 2 | 3 | import ( 4 | "github.com/davyxu/golog" 5 | ) 6 | 7 | var log = golog.New("tcpproc") 8 | -------------------------------------------------------------------------------- /proc/tcp/setup.go: -------------------------------------------------------------------------------- 1 | package tcp 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/proc" 6 | ) 7 | 8 | func init() { 9 | 10 | proc.RegisterProcessor("tcp.ltv", func(bundle proc.ProcessorBundle, userCallback cellnet.EventCallback, args ...interface{}) { 11 | 12 | bundle.SetTransmitter(new(TCPMessageTransmitter)) 13 | bundle.SetHooker(new(MsgHooker)) 14 | bundle.SetCallback(proc.NewQueuedEventCallback(userCallback)) 15 | 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /proc/tcp/transmitter.go: -------------------------------------------------------------------------------- 1 | package tcp 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/util" 6 | "io" 7 | "net" 8 | ) 9 | 10 | type TCPMessageTransmitter struct { 11 | } 12 | 13 | type socketOpt interface { 14 | MaxPacketSize() int 15 | ApplySocketReadTimeout(conn net.Conn, callback func()) 16 | ApplySocketWriteTimeout(conn net.Conn, callback func()) 17 | } 18 | 19 | func (TCPMessageTransmitter) OnRecvMessage(ses cellnet.Session) (msg interface{}, err error) { 20 | 21 | reader, ok := ses.Raw().(io.Reader) 22 | 23 | // 转换错误,或者连接已经关闭时退出 24 | if !ok || reader == nil { 25 | return nil, nil 26 | } 27 | 28 | opt := ses.Peer().(socketOpt) 29 | 30 | if conn, ok := reader.(net.Conn); ok { 31 | 32 | // 有读超时时,设置超时 33 | opt.ApplySocketReadTimeout(conn, func() { 34 | 35 | msg, err = util.RecvLTVPacket(reader, opt.MaxPacketSize()) 36 | 37 | }) 38 | } 39 | 40 | return 41 | } 42 | 43 | func (TCPMessageTransmitter) OnSendMessage(ses cellnet.Session, msg interface{}) (err error) { 44 | 45 | writer, ok := ses.Raw().(io.Writer) 46 | 47 | // 转换错误,或者连接已经关闭时退出 48 | if !ok || writer == nil { 49 | return nil 50 | } 51 | 52 | opt := ses.Peer().(socketOpt) 53 | 54 | // 有写超时时,设置超时 55 | opt.ApplySocketWriteTimeout(writer.(net.Conn), func() { 56 | 57 | err = util.SendLTVPacket(writer, ses.(cellnet.ContextSet), msg) 58 | 59 | }) 60 | 61 | return 62 | } 63 | -------------------------------------------------------------------------------- /proc/udp/log.go: -------------------------------------------------------------------------------- 1 | package udp 2 | 3 | import ( 4 | "github.com/davyxu/golog" 5 | ) 6 | 7 | var log = golog.New("udpproc") 8 | -------------------------------------------------------------------------------- /proc/udp/recv.go: -------------------------------------------------------------------------------- 1 | package udp 2 | 3 | import ( 4 | "encoding/binary" 5 | "github.com/davyxu/cellnet/codec" 6 | ) 7 | 8 | const ( 9 | MTU = 1472 // 最大传输单元 10 | packetLen = 2 // 包体大小字段 11 | MsgIDLen = 2 // 消息ID字段 12 | 13 | HeaderSize = MsgIDLen + MsgIDLen // 整个UDP包头部分 14 | ) 15 | 16 | func RecvPacket(pktData []byte) (msg interface{}, err error) { 17 | 18 | // 小于包头,使用nc指令测试时,为1 19 | if len(pktData) < packetLen { 20 | return nil, nil 21 | } 22 | 23 | // 用小端格式读取Size 24 | datasize := binary.LittleEndian.Uint16(pktData) 25 | 26 | // 出错,等待下次数据 27 | if int(datasize) != len(pktData) || datasize > MTU { 28 | return nil, nil 29 | } 30 | 31 | // 读取消息ID 32 | msgid := binary.LittleEndian.Uint16(pktData[packetLen:]) 33 | 34 | msgData := pktData[HeaderSize:] 35 | 36 | // 将字节数组和消息ID用户解出消息 37 | msg, _, err = codec.DecodeMessage(int(msgid), msgData) 38 | if err != nil { 39 | // TODO 接收错误时,返回消息 40 | return nil, err 41 | } 42 | 43 | return 44 | } 45 | -------------------------------------------------------------------------------- /proc/udp/send.go: -------------------------------------------------------------------------------- 1 | package udp 2 | 3 | import ( 4 | "encoding/binary" 5 | "github.com/davyxu/cellnet" 6 | "github.com/davyxu/cellnet/codec" 7 | "github.com/davyxu/cellnet/peer/udp" 8 | ) 9 | 10 | func sendPacket(writer udp.DataWriter, ctx cellnet.ContextSet, msg interface{}) error { 11 | 12 | // 将用户数据转换为字节数组和消息ID 13 | msgData, meta, err := codec.EncodeMessage(msg, ctx) 14 | 15 | if err != nil { 16 | log.Errorf("send message encode error: %s", err) 17 | return err 18 | } 19 | 20 | pktData := make([]byte, HeaderSize+len(msgData)) 21 | 22 | // 写入消息长度做验证 23 | binary.LittleEndian.PutUint16(pktData, uint16(HeaderSize+len(msgData))) 24 | 25 | // Type 26 | binary.LittleEndian.PutUint16(pktData[2:], uint16(meta.ID)) 27 | 28 | // Value 29 | copy(pktData[HeaderSize:], msgData) 30 | 31 | writer.WriteData(pktData) 32 | 33 | codec.FreeCodecResource(meta.Codec, msgData, ctx) 34 | 35 | return nil 36 | } 37 | -------------------------------------------------------------------------------- /proc/udp/setup.go: -------------------------------------------------------------------------------- 1 | package udp 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/msglog" 6 | "github.com/davyxu/cellnet/peer/udp" 7 | "github.com/davyxu/cellnet/proc" 8 | ) 9 | 10 | type UDPMessageTransmitter struct { 11 | } 12 | 13 | func (UDPMessageTransmitter) OnRecvMessage(ses cellnet.Session) (msg interface{}, err error) { 14 | 15 | data := ses.Raw().(udp.DataReader).ReadData() 16 | 17 | msg, err = RecvPacket(data) 18 | 19 | msglog.WriteRecvLogger(log, "udp", ses, msg) 20 | 21 | return 22 | } 23 | 24 | func (UDPMessageTransmitter) OnSendMessage(ses cellnet.Session, msg interface{}) error { 25 | 26 | writer := ses.(udp.DataWriter) 27 | 28 | msglog.WriteSendLogger(log, "udp", ses, msg) 29 | 30 | // ses不再被复用, 所以使用session自己的contextset做内存池, 避免串台 31 | return sendPacket(writer, ses.(cellnet.ContextSet), msg) 32 | } 33 | 34 | func init() { 35 | 36 | proc.RegisterProcessor("udp.ltv", func(bundle proc.ProcessorBundle, userCallback cellnet.EventCallback, args ...interface{}) { 37 | 38 | bundle.SetTransmitter(new(UDPMessageTransmitter)) 39 | bundle.SetCallback(userCallback) 40 | 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /processor.go: -------------------------------------------------------------------------------- 1 | package cellnet 2 | 3 | // 事件 4 | type Event interface { 5 | 6 | // 事件对应的会话 7 | Session() Session 8 | 9 | // 事件携带的消息 10 | Message() interface{} 11 | } 12 | 13 | // 消息收发器 14 | type MessageTransmitter interface { 15 | 16 | // 接收消息 17 | OnRecvMessage(ses Session) (msg interface{}, err error) 18 | 19 | // 发送消息 20 | OnSendMessage(ses Session, msg interface{}) error 21 | } 22 | 23 | // 处理钩子(参数输入, 返回输出, 不给MessageProccessor处理时,可以将Event设置为nil) 24 | type EventHooker interface { 25 | 26 | // 入站(接收)的事件处理 27 | OnInboundEvent(input Event) (output Event) 28 | 29 | // 出站(发送)的事件处理 30 | OnOutboundEvent(input Event) (output Event) 31 | } 32 | 33 | // 用户端处理 34 | type EventCallback func(ev Event) 35 | -------------------------------------------------------------------------------- /protoc-gen-msg/file.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "go/parser" 7 | "go/printer" 8 | "go/token" 9 | "text/template" 10 | 11 | "github.com/davyxu/cellnet/util" 12 | "github.com/davyxu/pbmeta" 13 | ) 14 | 15 | const codeTemplate = `// Generated by github.com/davyxu/cellnet/protoc-gen-msg 16 | // DO NOT EDIT!{{range .Protos}} 17 | // Source: {{.Name}}{{end}} 18 | 19 | package {{.PackageName}} 20 | 21 | {{if gt .TotalMessages 0}} 22 | import ( 23 | "github.com/davyxu/cellnet" 24 | "reflect" 25 | _ "github.com/davyxu/cellnet/codec/gogopb" 26 | "github.com/davyxu/cellnet/codec" 27 | ) 28 | {{end}} 29 | 30 | func init() { 31 | {{range .Protos}} 32 | // {{.Name}}{{range .Messages}} 33 | cellnet.RegisterMessageMeta(&cellnet.MessageMeta{ 34 | Codec: codec.MustGetCodec("gogopb"), 35 | Type: reflect.TypeOf((*{{.Name}})(nil)).Elem(), 36 | ID: {{.MsgID}}, 37 | }){{end}} {{end}} 38 | } 39 | 40 | ` 41 | 42 | type msgModel struct { 43 | *pbmeta.Descriptor 44 | 45 | parent *pbmeta.FileDescriptor 46 | } 47 | 48 | func (self *msgModel) MsgID() int { 49 | return int(util.StringHash(self.FullName())) 50 | } 51 | 52 | func (self *msgModel) FullName() string { 53 | return fmt.Sprintf("%s.%s", self.parent.PackageName(), self.Name()) 54 | } 55 | 56 | type protoModel struct { 57 | *pbmeta.FileDescriptor 58 | 59 | Messages []*msgModel 60 | } 61 | 62 | func (self *protoModel) Name() string { 63 | return self.FileDescriptor.FileName() 64 | } 65 | 66 | type fileModel struct { 67 | TotalMessages int 68 | Protos []*protoModel 69 | PackageName string 70 | } 71 | 72 | func printFile(pool *pbmeta.DescriptorPool) (string, bool) { 73 | 74 | tpl, err := template.New("msgid").Parse(codeTemplate) 75 | if err != nil { 76 | log.Errorln(err) 77 | return "", false 78 | } 79 | 80 | if pool.FileCount() == 0 { 81 | return "", false 82 | } 83 | 84 | var model fileModel 85 | model.PackageName = pool.File(0).PackageName() 86 | 87 | for f := 0; f < pool.FileCount(); f++ { 88 | 89 | file := pool.File(f) 90 | 91 | pm := &protoModel{ 92 | FileDescriptor: file, 93 | } 94 | 95 | for m := 0; m < file.MessageCount(); m++ { 96 | 97 | d := file.Message(m) 98 | 99 | pm.Messages = append(pm.Messages, &msgModel{ 100 | Descriptor: d, 101 | parent: file, 102 | }) 103 | 104 | } 105 | 106 | model.TotalMessages += file.MessageCount() 107 | 108 | model.Protos = append(model.Protos, pm) 109 | 110 | } 111 | 112 | var bf bytes.Buffer 113 | 114 | err = tpl.Execute(&bf, &model) 115 | if err != nil { 116 | log.Errorln(err) 117 | return "", false 118 | } 119 | 120 | err = formatCode(&bf) 121 | if err != nil { 122 | log.Errorln(err) 123 | return "", false 124 | } 125 | 126 | return bf.String(), true 127 | } 128 | 129 | func formatCode(bf *bytes.Buffer) error { 130 | // Reformat generated code. 131 | fset := token.NewFileSet() 132 | 133 | ast, err := parser.ParseFile(fset, "", bf, parser.ParseComments) 134 | if err != nil { 135 | return err 136 | } 137 | 138 | bf.Reset() 139 | 140 | err = (&printer.Config{Mode: printer.TabIndent | printer.UseSpaces, Tabwidth: 8}).Fprint(bf, fset, ast) 141 | if err != nil { 142 | return err 143 | } 144 | 145 | return nil 146 | } 147 | -------------------------------------------------------------------------------- /protoc-gen-msg/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | 7 | "bytes" 8 | "github.com/davyxu/golog" 9 | "github.com/davyxu/pbmeta" 10 | "github.com/gogo/protobuf/proto" 11 | pbprotos "github.com/gogo/protobuf/protoc-gen-gogo/descriptor" 12 | plugin "github.com/gogo/protobuf/protoc-gen-gogo/plugin" 13 | ) 14 | 15 | var log = golog.New("main") 16 | 17 | func main() { 18 | 19 | var errBuffer bytes.Buffer 20 | 21 | golog.SetOutput("main", &errBuffer) 22 | 23 | var Request plugin.CodeGeneratorRequest // The input. 24 | var Response plugin.CodeGeneratorResponse // The output. 25 | 26 | defer func() { 27 | 28 | if errBuffer.Len() > 0 { 29 | str := errBuffer.String() 30 | Response.Error = &str 31 | } 32 | 33 | // 发回处理结果 34 | data, _ := proto.Marshal(&Response) 35 | 36 | os.Stdout.Write(data) 37 | 38 | }() 39 | 40 | // 读取protoc请求 41 | data, err := ioutil.ReadAll(os.Stdin) 42 | if err != nil { 43 | log.Errorln("reading input") 44 | os.Exit(1) 45 | } 46 | 47 | // 解析请求 48 | if err := proto.Unmarshal(data, &Request); err != nil { 49 | log.Errorln("parsing input proto") 50 | os.Exit(1) 51 | } 52 | 53 | if len(Request.FileToGenerate) == 0 { 54 | log.Errorln("no files to generate") 55 | os.Exit(1) 56 | } 57 | 58 | // 建立解析池 59 | pool := pbmeta.NewDescriptorPool(&pbprotos.FileDescriptorSet{ 60 | File: Request.ProtoFile, 61 | }) 62 | 63 | Response.File = make([]*plugin.CodeGeneratorResponse_File, 0) 64 | 65 | contenxt, ok := printFile(pool) 66 | 67 | if !ok { 68 | os.Exit(1) 69 | } 70 | 71 | Response.File = append(Response.File, &plugin.CodeGeneratorResponse_File{ 72 | Name: proto.String(Request.GetParameter()), 73 | Content: proto.String(contenxt), 74 | }) 75 | 76 | } 77 | -------------------------------------------------------------------------------- /queue.go: -------------------------------------------------------------------------------- 1 | package cellnet 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "runtime/debug" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | // 事件队列 12 | type EventQueue interface { 13 | // 事件队列开始工作 14 | StartLoop() EventQueue 15 | 16 | // 停止事件队列 17 | StopLoop() EventQueue 18 | 19 | // 等待退出 20 | Wait() 21 | 22 | // 投递事件, 通过队列到达消费者端 23 | Post(callback func()) 24 | 25 | // 是否捕获异常 26 | EnableCapturePanic(v bool) 27 | 28 | // 获取事件数量 29 | Count() int 30 | } 31 | 32 | type CapturePanicNotifyFunc func(interface{}, EventQueue) 33 | 34 | type eventQueue struct { 35 | *Pipe 36 | 37 | endSignal sync.WaitGroup 38 | 39 | capturePanic bool 40 | 41 | onPanic CapturePanicNotifyFunc 42 | } 43 | 44 | // 启动崩溃捕获 45 | func (self *eventQueue) EnableCapturePanic(v bool) { 46 | self.capturePanic = v 47 | } 48 | 49 | // 设置捕获崩溃通知 50 | func (self *eventQueue) SetCapturePanicNotify(callback CapturePanicNotifyFunc) { 51 | self.onPanic = callback 52 | } 53 | 54 | // 派发事件处理回调到队列中 55 | func (self *eventQueue) Post(callback func()) { 56 | 57 | if callback == nil { 58 | return 59 | } 60 | 61 | self.Add(callback) 62 | } 63 | 64 | // 保护调用用户函数 65 | func (self *eventQueue) protectedCall(callback func()) { 66 | 67 | if self.capturePanic { 68 | defer func() { 69 | 70 | if err := recover(); err != nil { 71 | self.onPanic(err, self) 72 | } 73 | 74 | }() 75 | } 76 | 77 | callback() 78 | } 79 | 80 | // 开启事件循环 81 | func (self *eventQueue) StartLoop() EventQueue { 82 | 83 | self.endSignal.Add(1) 84 | 85 | go func() { 86 | 87 | var writeList []interface{} 88 | 89 | for { 90 | writeList = writeList[0:0] 91 | exit := self.Pick(&writeList) 92 | 93 | // 遍历要发送的数据 94 | for _, msg := range writeList { 95 | switch t := msg.(type) { 96 | case func(): 97 | self.protectedCall(t) 98 | case nil: 99 | break 100 | default: 101 | log.Printf("unexpected type %T", t) 102 | } 103 | } 104 | 105 | if exit { 106 | break 107 | } 108 | } 109 | 110 | self.endSignal.Done() 111 | }() 112 | 113 | return self 114 | } 115 | 116 | // 停止事件循环 117 | func (self *eventQueue) StopLoop() EventQueue { 118 | self.Add(nil) 119 | return self 120 | } 121 | 122 | // 等待退出消息 123 | func (self *eventQueue) Wait() { 124 | self.endSignal.Wait() 125 | } 126 | 127 | // 创建默认长度的队列 128 | func NewEventQueue() EventQueue { 129 | 130 | return &eventQueue{ 131 | Pipe: NewPipe(), 132 | 133 | // 默认的崩溃捕获打印 134 | onPanic: func(raw interface{}, queue EventQueue) { 135 | 136 | fmt.Printf("%s: %v \n%s\n", time.Now().Format("2006-01-02 15:04:05"), raw, string(debug.Stack())) 137 | debug.PrintStack() 138 | }, 139 | } 140 | } 141 | 142 | // 在会话对应的Peer上的事件队列中执行callback,如果没有队列,则马上执行 143 | func SessionQueuedCall(ses Session, callback func()) { 144 | if ses == nil { 145 | return 146 | } 147 | q := ses.Peer().(interface { 148 | Queue() EventQueue 149 | }).Queue() 150 | 151 | QueuedCall(q, callback) 152 | } 153 | 154 | // 有队列时队列调用,无队列时直接调用 155 | func QueuedCall(queue EventQueue, callback func()) { 156 | if queue == nil { 157 | callback() 158 | } else { 159 | queue.Post(callback) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /relay/Make.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | CURRDIR=`pwd` 3 | cd ../../../../.. 4 | export GOPATH=`pwd` 5 | cd ${CURRDIR} 6 | 7 | go build -v -o=${GOPATH}/bin/protoplus github.com/davyxu/protoplus 8 | 9 | 10 | ${GOPATH}/bin/protoplus -go_out=msg_gen.go -package=relay msg.proto -------------------------------------------------------------------------------- /relay/broadcast.go: -------------------------------------------------------------------------------- 1 | package relay 2 | 3 | type BroadcasterFunc func(event *RecvMsgEvent) 4 | 5 | var bcFunc BroadcasterFunc 6 | 7 | // 设置广播函数, 回调时,按对应Peer/Session所在的队列中调用 8 | func SetBroadcaster(callback BroadcasterFunc) { 9 | 10 | bcFunc = callback 11 | } 12 | -------------------------------------------------------------------------------- /relay/event.go: -------------------------------------------------------------------------------- 1 | package relay 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | ) 6 | 7 | type RecvMsgEvent struct { 8 | Ses cellnet.Session 9 | ack *RelayACK 10 | Msg interface{} 11 | } 12 | 13 | func (self *RecvMsgEvent) PassThroughAsInt64() int64 { 14 | if self.ack == nil { 15 | return 0 16 | } 17 | 18 | return self.ack.Int64 19 | } 20 | 21 | func (self *RecvMsgEvent) PassThroughAsInt64Slice() []int64 { 22 | if self.ack == nil { 23 | return nil 24 | } 25 | 26 | return self.ack.Int64Slice 27 | } 28 | 29 | func (self *RecvMsgEvent) PassThroughAsString() string { 30 | if self.ack == nil { 31 | return "" 32 | } 33 | 34 | return self.ack.Str 35 | } 36 | 37 | func (self *RecvMsgEvent) Session() cellnet.Session { 38 | return self.Ses 39 | } 40 | 41 | func (self *RecvMsgEvent) Message() interface{} { 42 | return self.Msg 43 | } 44 | 45 | // 消息原路返回 46 | func (self *RecvMsgEvent) Reply(msg interface{}) { 47 | 48 | // 没填的值不会被发送 49 | Relay(self.Ses, msg, self.ack.Int64, self.ack.Int64Slice, self.ack.Str) 50 | 51 | } 52 | -------------------------------------------------------------------------------- /relay/log.go: -------------------------------------------------------------------------------- 1 | package relay 2 | 3 | import ( 4 | "github.com/davyxu/golog" 5 | ) 6 | 7 | var log = golog.New("relay") 8 | -------------------------------------------------------------------------------- /relay/msg.proto: -------------------------------------------------------------------------------- 1 | 2 | [AutoMsgID] 3 | struct RelayACK 4 | { 5 | Msg bytes // 数据消息转换后传输bytes 6 | MsgID uint32 // 消息ID 7 | Bytes bytes // 数据bytes 8 | 9 | Int64 int64 // 透传int64 10 | Int64Slice []int64 // 透传int64切片 11 | Str string 12 | } -------------------------------------------------------------------------------- /relay/msg_gen.go: -------------------------------------------------------------------------------- 1 | // Generated by github.com/davyxu/protoplus 2 | // DO NOT EDIT! 3 | package relay 4 | 5 | import ( 6 | "github.com/davyxu/cellnet" 7 | "github.com/davyxu/cellnet/codec" 8 | _ "github.com/davyxu/cellnet/codec/protoplus" 9 | "github.com/davyxu/protoplus/proto" 10 | "reflect" 11 | "unsafe" 12 | ) 13 | 14 | var ( 15 | _ *proto.Buffer 16 | _ codec.CodecRecycler 17 | _ cellnet.Session 18 | _ reflect.Type 19 | _ unsafe.Pointer 20 | ) 21 | 22 | type RelayACK struct { 23 | Msg []byte `text:"-"` // 数据消息转换后传输bytes 24 | MsgID uint32 `text:"-"` // 消息ID 25 | Bytes []byte `text:"-"` // 数据bytes 26 | Int64 int64 // 透传int64 27 | Int64Slice []int64 // 透传int64切片 28 | Str string 29 | } 30 | 31 | func (self *RelayACK) String() string { return proto.CompactTextString(self) } 32 | 33 | func (self *RelayACK) Size() (ret int) { 34 | 35 | ret += proto.SizeBytes(0, self.Msg) 36 | 37 | ret += proto.SizeUInt32(1, self.MsgID) 38 | 39 | ret += proto.SizeBytes(2, self.Bytes) 40 | 41 | ret += proto.SizeInt64(3, self.Int64) 42 | 43 | ret += proto.SizeInt64Slice(4, self.Int64Slice) 44 | 45 | ret += proto.SizeString(5, self.Str) 46 | 47 | return 48 | } 49 | 50 | func (self *RelayACK) Marshal(buffer *proto.Buffer) error { 51 | 52 | proto.MarshalBytes(buffer, 0, self.Msg) 53 | 54 | proto.MarshalUInt32(buffer, 1, self.MsgID) 55 | 56 | proto.MarshalBytes(buffer, 2, self.Bytes) 57 | 58 | proto.MarshalInt64(buffer, 3, self.Int64) 59 | 60 | proto.MarshalInt64Slice(buffer, 4, self.Int64Slice) 61 | 62 | proto.MarshalString(buffer, 5, self.Str) 63 | 64 | return nil 65 | } 66 | 67 | func (self *RelayACK) Unmarshal(buffer *proto.Buffer, fieldIndex uint64, wt proto.WireType) error { 68 | switch fieldIndex { 69 | case 0: 70 | return proto.UnmarshalBytes(buffer, wt, &self.Msg) 71 | case 1: 72 | return proto.UnmarshalUInt32(buffer, wt, &self.MsgID) 73 | case 2: 74 | return proto.UnmarshalBytes(buffer, wt, &self.Bytes) 75 | case 3: 76 | return proto.UnmarshalInt64(buffer, wt, &self.Int64) 77 | case 4: 78 | return proto.UnmarshalInt64Slice(buffer, wt, &self.Int64Slice) 79 | case 5: 80 | return proto.UnmarshalString(buffer, wt, &self.Str) 81 | 82 | } 83 | 84 | return proto.ErrUnknownField 85 | } 86 | 87 | func init() { 88 | 89 | cellnet.RegisterMessageMeta(&cellnet.MessageMeta{ 90 | Codec: codec.MustGetCodec("protoplus"), 91 | Type: reflect.TypeOf((*RelayACK)(nil)).Elem(), 92 | ID: 45545, 93 | }) 94 | } 95 | -------------------------------------------------------------------------------- /relay/proc.go: -------------------------------------------------------------------------------- 1 | package relay 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/codec" 6 | "github.com/davyxu/cellnet/msglog" 7 | ) 8 | 9 | type PassthroughContent struct { 10 | Int64 int64 // 透传int64 11 | Int64Slice []int64 // 透传int64切片 12 | Str string 13 | } 14 | 15 | // 处理入站的relay消息 16 | func ResoleveInboundEvent(inputEvent cellnet.Event) (ouputEvent cellnet.Event, handled bool, err error) { 17 | 18 | switch relayMsg := inputEvent.Message().(type) { 19 | case *RelayACK: 20 | 21 | ev := &RecvMsgEvent{ 22 | Ses: inputEvent.Session(), 23 | ack: relayMsg, 24 | } 25 | 26 | if relayMsg.MsgID != 0 { 27 | 28 | ev.Msg, _, err = codec.DecodeMessage(int(relayMsg.MsgID), relayMsg.Msg) 29 | if err != nil { 30 | return 31 | } 32 | } 33 | 34 | if msglog.IsMsgLogValid(int(relayMsg.MsgID)) { 35 | 36 | peerInfo := inputEvent.Session().Peer().(cellnet.PeerProperty) 37 | 38 | log.Debugf("#relay.recv(%s)@%d len: %d %s {%s}| %s", 39 | peerInfo.Name(), 40 | inputEvent.Session().ID(), 41 | cellnet.MessageSize(ev.Message()), 42 | cellnet.MessageToName(ev.Message()), 43 | cellnet.MessageToString(relayMsg), 44 | cellnet.MessageToString(ev.Message())) 45 | } 46 | 47 | if bcFunc != nil { 48 | // 转到对应线程中调用 49 | cellnet.SessionQueuedCall(inputEvent.Session(), func() { 50 | bcFunc(ev) 51 | }) 52 | } 53 | 54 | return ev, true, nil 55 | } 56 | 57 | return inputEvent, false, nil 58 | } 59 | 60 | // 处理relay.Relay出站消息的日志 61 | func ResolveOutboundEvent(inputEvent cellnet.Event) (handled bool, err error) { 62 | 63 | switch relayMsg := inputEvent.Message().(type) { 64 | case *RelayACK: 65 | if msglog.IsMsgLogValid(int(relayMsg.MsgID)) { 66 | 67 | var payload interface{} 68 | if relayMsg.MsgID != 0 { 69 | 70 | payload, _, err = codec.DecodeMessage(int(relayMsg.MsgID), relayMsg.Msg) 71 | if err != nil { 72 | return 73 | } 74 | } 75 | 76 | peerInfo := inputEvent.Session().Peer().(cellnet.PeerProperty) 77 | 78 | log.Debugf("#relay.send(%s)@%d len: %d %s {%s}| %s", 79 | peerInfo.Name(), 80 | inputEvent.Session().ID(), 81 | cellnet.MessageSize(payload), 82 | cellnet.MessageToName(payload), 83 | cellnet.MessageToString(relayMsg), 84 | cellnet.MessageToString(payload)) 85 | } 86 | 87 | return true, nil 88 | 89 | } 90 | 91 | return 92 | } 93 | -------------------------------------------------------------------------------- /relay/relay.go: -------------------------------------------------------------------------------- 1 | package relay 2 | 3 | import ( 4 | "errors" 5 | "github.com/davyxu/cellnet" 6 | "github.com/davyxu/cellnet/codec" 7 | ) 8 | 9 | var ( 10 | ErrInvalidPeerSession = errors.New("Require valid cellnet.Session or cellnet.TCPConnector") 11 | ) 12 | 13 | // payload: msg/bytes passthrough: int64, []int64, string 14 | func Relay(sesDetector interface{}, dataList ...interface{}) error { 15 | 16 | ses, err := getSession(sesDetector) 17 | if err != nil { 18 | log.Errorln("relay.Relay:", err) 19 | return err 20 | } 21 | 22 | var ack RelayACK 23 | 24 | for _, payload := range dataList { 25 | switch value := payload.(type) { 26 | case int64: 27 | ack.Int64 = value 28 | case []int64: 29 | ack.Int64Slice = value 30 | 31 | case string: 32 | ack.Str = value 33 | case []byte: // 作为payload 34 | ack.Bytes = value 35 | default: 36 | if ack.MsgID == 0 { 37 | var meta *cellnet.MessageMeta 38 | ack.Msg, meta, err = codec.EncodeMessage(payload, nil) 39 | 40 | if err != nil { 41 | return err 42 | } 43 | 44 | ack.MsgID = uint32(meta.ID) 45 | } else { 46 | panic("Multi message relay not support") 47 | } 48 | 49 | } 50 | } 51 | ses.Send(&ack) 52 | 53 | return nil 54 | } 55 | 56 | func getSession(sesDetector interface{}) (cellnet.Session, error) { 57 | switch unknown := sesDetector.(type) { 58 | case cellnet.Session: 59 | return unknown, nil 60 | case cellnet.TCPConnector: 61 | return unknown.Session(), nil 62 | default: 63 | return nil, ErrInvalidPeerSession 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /rpc/Make.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | CURRDIR=`pwd` 3 | cd ../../../../.. 4 | export GOPATH=`pwd` 5 | cd ${CURRDIR} 6 | 7 | go build -v -o=${GOPATH}/bin/protoplus github.com/davyxu/protoplus 8 | 9 | 10 | ${GOPATH}/bin/protoplus -go_out=msg_gen.go -package=rpc msg.proto -------------------------------------------------------------------------------- /rpc/event.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/codec" 6 | ) 7 | 8 | type RecvMsgEvent struct { 9 | ses cellnet.Session 10 | Msg interface{} 11 | callid int64 12 | } 13 | 14 | func (self *RecvMsgEvent) Session() cellnet.Session { 15 | return self.ses 16 | } 17 | 18 | func (self *RecvMsgEvent) Message() interface{} { 19 | return self.Msg 20 | } 21 | 22 | func (self *RecvMsgEvent) Queue() cellnet.EventQueue { 23 | return self.ses.Peer().(interface { 24 | Queue() cellnet.EventQueue 25 | }).Queue() 26 | } 27 | 28 | func (self *RecvMsgEvent) Reply(msg interface{}) { 29 | 30 | data, meta, err := codec.EncodeMessage(msg, nil) 31 | 32 | if err != nil { 33 | log.Errorf("rpc reply message encode error: %s", err) 34 | return 35 | } 36 | 37 | self.ses.Send(&RemoteCallACK{ 38 | MsgID: uint32(meta.ID), 39 | Data: data, 40 | CallID: self.callid, 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /rpc/log.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "github.com/davyxu/golog" 5 | ) 6 | 7 | var log = golog.New("rpc") 8 | -------------------------------------------------------------------------------- /rpc/msg.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | func (self *RemoteCallREQ) GetMsgID() uint16 { return uint16(self.MsgID) } 4 | func (self *RemoteCallREQ) GetMsgData() []byte { return self.Data } 5 | func (self *RemoteCallREQ) GetCallID() int64 { return self.CallID } 6 | func (self *RemoteCallACK) GetMsgID() uint16 { return uint16(self.MsgID) } 7 | func (self *RemoteCallACK) GetMsgData() []byte { return self.Data } 8 | func (self *RemoteCallACK) GetCallID() int64 { return self.CallID } 9 | -------------------------------------------------------------------------------- /rpc/msg.proto: -------------------------------------------------------------------------------- 1 | 2 | [AutoMsgID] 3 | struct RemoteCallREQ 4 | { 5 | MsgID uint32 6 | Data bytes 7 | CallID int64 8 | } 9 | 10 | 11 | [AutoMsgID] 12 | struct RemoteCallACK 13 | { 14 | MsgID uint32 15 | Data bytes 16 | CallID int64 17 | } -------------------------------------------------------------------------------- /rpc/msg_gen.go: -------------------------------------------------------------------------------- 1 | // Generated by github.com/davyxu/protoplus 2 | // DO NOT EDIT! 3 | package rpc 4 | 5 | import ( 6 | "github.com/davyxu/protoplus/proto" 7 | "github.com/davyxu/cellnet" 8 | "github.com/davyxu/cellnet/codec" 9 | _ "github.com/davyxu/cellnet/codec/protoplus" 10 | "reflect" 11 | "unsafe" 12 | ) 13 | 14 | var ( 15 | _ *proto.Buffer 16 | _ codec.CodecRecycler 17 | _ cellnet.Session 18 | _ reflect.Type 19 | _ unsafe.Pointer 20 | ) 21 | 22 | type RemoteCallREQ struct { 23 | MsgID uint32 24 | Data []byte 25 | CallID int64 26 | } 27 | 28 | func (self *RemoteCallREQ) String() string { return proto.CompactTextString(self) } 29 | 30 | func (self *RemoteCallREQ) Size() (ret int) { 31 | 32 | ret += proto.SizeUInt32(0, self.MsgID) 33 | 34 | ret += proto.SizeBytes(1, self.Data) 35 | 36 | ret += proto.SizeInt64(2, self.CallID) 37 | 38 | return 39 | } 40 | 41 | func (self *RemoteCallREQ) Marshal(buffer *proto.Buffer) error { 42 | 43 | proto.MarshalUInt32(buffer, 0, self.MsgID) 44 | 45 | proto.MarshalBytes(buffer, 1, self.Data) 46 | 47 | proto.MarshalInt64(buffer, 2, self.CallID) 48 | 49 | return nil 50 | } 51 | 52 | func (self *RemoteCallREQ) Unmarshal(buffer *proto.Buffer, fieldIndex uint64, wt proto.WireType) error { 53 | switch fieldIndex { 54 | case 0: 55 | return proto.UnmarshalUInt32(buffer, wt, &self.MsgID) 56 | case 1: 57 | return proto.UnmarshalBytes(buffer, wt, &self.Data) 58 | case 2: 59 | return proto.UnmarshalInt64(buffer, wt, &self.CallID) 60 | 61 | } 62 | 63 | return proto.ErrUnknownField 64 | } 65 | 66 | type RemoteCallACK struct { 67 | MsgID uint32 68 | Data []byte 69 | CallID int64 70 | } 71 | 72 | func (self *RemoteCallACK) String() string { return proto.CompactTextString(self) } 73 | 74 | func (self *RemoteCallACK) Size() (ret int) { 75 | 76 | ret += proto.SizeUInt32(0, self.MsgID) 77 | 78 | ret += proto.SizeBytes(1, self.Data) 79 | 80 | ret += proto.SizeInt64(2, self.CallID) 81 | 82 | return 83 | } 84 | 85 | func (self *RemoteCallACK) Marshal(buffer *proto.Buffer) error { 86 | 87 | proto.MarshalUInt32(buffer, 0, self.MsgID) 88 | 89 | proto.MarshalBytes(buffer, 1, self.Data) 90 | 91 | proto.MarshalInt64(buffer, 2, self.CallID) 92 | 93 | return nil 94 | } 95 | 96 | func (self *RemoteCallACK) Unmarshal(buffer *proto.Buffer, fieldIndex uint64, wt proto.WireType) error { 97 | switch fieldIndex { 98 | case 0: 99 | return proto.UnmarshalUInt32(buffer, wt, &self.MsgID) 100 | case 1: 101 | return proto.UnmarshalBytes(buffer, wt, &self.Data) 102 | case 2: 103 | return proto.UnmarshalInt64(buffer, wt, &self.CallID) 104 | 105 | } 106 | 107 | return proto.ErrUnknownField 108 | } 109 | 110 | func init() { 111 | 112 | cellnet.RegisterMessageMeta(&cellnet.MessageMeta{ 113 | Codec: codec.MustGetCodec("protoplus"), 114 | Type: reflect.TypeOf((*RemoteCallREQ)(nil)).Elem(), 115 | ID: 58645, 116 | }) 117 | cellnet.RegisterMessageMeta(&cellnet.MessageMeta{ 118 | Codec: codec.MustGetCodec("protoplus"), 119 | Type: reflect.TypeOf((*RemoteCallACK)(nil)).Elem(), 120 | ID: 20476, 121 | }) 122 | } 123 | -------------------------------------------------------------------------------- /rpc/proc.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/codec" 6 | "github.com/davyxu/cellnet/msglog" 7 | ) 8 | 9 | type RemoteCallMsg interface { 10 | GetMsgID() uint16 11 | GetMsgData() []byte 12 | GetCallID() int64 13 | } 14 | 15 | func ResolveInboundEvent(inputEvent cellnet.Event) (ouputEvent cellnet.Event, handled bool, err error) { 16 | 17 | if _, ok := inputEvent.(*RecvMsgEvent); ok { 18 | return inputEvent, false, nil 19 | } 20 | 21 | rpcMsg, ok := inputEvent.Message().(RemoteCallMsg) 22 | if !ok { 23 | return inputEvent, false, nil 24 | } 25 | 26 | userMsg, _, err := codec.DecodeMessage(int(rpcMsg.GetMsgID()), rpcMsg.GetMsgData()) 27 | 28 | if err != nil { 29 | return inputEvent, false, err 30 | } 31 | 32 | if msglog.IsMsgLogValid(int(rpcMsg.GetMsgID())) { 33 | peerInfo := inputEvent.Session().Peer().(cellnet.PeerProperty) 34 | 35 | log.Debugf("#rpc.recv(%s)@%d len: %d %s | %s", 36 | peerInfo.Name(), 37 | inputEvent.Session().ID(), 38 | cellnet.MessageSize(userMsg), 39 | cellnet.MessageToName(userMsg), 40 | cellnet.MessageToString(userMsg)) 41 | } 42 | 43 | switch inputEvent.Message().(type) { 44 | case *RemoteCallREQ: // 服务端收到客户端的请求 45 | 46 | return &RecvMsgEvent{ 47 | inputEvent.Session(), 48 | userMsg, 49 | rpcMsg.GetCallID(), 50 | }, true, nil 51 | 52 | case *RemoteCallACK: // 客户端收到服务器的回应 53 | request := getRequest(rpcMsg.GetCallID()) 54 | if request != nil { 55 | request.RecvFeedback(userMsg) 56 | } 57 | 58 | return inputEvent, true, nil 59 | } 60 | 61 | return inputEvent, false, nil 62 | } 63 | 64 | func ResolveOutboundEvent(inputEvent cellnet.Event) (handled bool, err error) { 65 | rpcMsg, ok := inputEvent.Message().(RemoteCallMsg) 66 | if !ok { 67 | return false, nil 68 | } 69 | 70 | userMsg, _, err := codec.DecodeMessage(int(rpcMsg.GetMsgID()), rpcMsg.GetMsgData()) 71 | 72 | if err != nil { 73 | return false, err 74 | } 75 | 76 | if msglog.IsMsgLogValid(int(rpcMsg.GetMsgID())) { 77 | peerInfo := inputEvent.Session().Peer().(cellnet.PeerProperty) 78 | 79 | log.Debugf("#rpc.send(%s)@%d len: %d %s | %s", 80 | peerInfo.Name(), 81 | inputEvent.Session().ID(), 82 | cellnet.MessageSize(userMsg), 83 | cellnet.MessageToName(userMsg), 84 | cellnet.MessageToString(userMsg)) 85 | } 86 | 87 | // 避免后续环节处理 88 | 89 | return true, nil 90 | } 91 | -------------------------------------------------------------------------------- /rpc/req.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "errors" 5 | "github.com/davyxu/cellnet" 6 | "github.com/davyxu/cellnet/codec" 7 | "sync" 8 | "sync/atomic" 9 | ) 10 | 11 | var ( 12 | rpcIDSeq int64 13 | requestByCallID sync.Map 14 | ) 15 | 16 | type request struct { 17 | id int64 18 | onRecv func(interface{}) 19 | } 20 | 21 | var ErrTimeout = errors.New("RPC time out") 22 | 23 | func (self *request) RecvFeedback(msg interface{}) { 24 | 25 | // 异步和同步执行复杂,队列处理在具体的逻辑中手动处理 26 | self.onRecv(msg) 27 | } 28 | 29 | func (self *request) Send(ses cellnet.Session, msg interface{}) { 30 | 31 | //ctx, _ := ses.(cellnet.ContextSet) 32 | 33 | data, meta, err := codec.EncodeMessage(msg, nil) 34 | 35 | if err != nil { 36 | log.Errorf("rpc request message encode error: %s", err) 37 | return 38 | } 39 | 40 | ses.Send(&RemoteCallREQ{ 41 | MsgID: uint32(meta.ID), 42 | Data: data, 43 | CallID: self.id, 44 | }) 45 | 46 | //codec.FreeCodecResource(meta.Codec, data, ctx) 47 | } 48 | 49 | func createRequest(onRecv func(interface{})) *request { 50 | 51 | self := &request{ 52 | onRecv: onRecv, 53 | } 54 | 55 | self.id = atomic.AddInt64(&rpcIDSeq, 1) 56 | 57 | requestByCallID.Store(self.id, self) 58 | 59 | return self 60 | } 61 | 62 | func getRequest(callid int64) *request { 63 | 64 | if v, ok := requestByCallID.Load(callid); ok { 65 | 66 | requestByCallID.Delete(callid) 67 | return v.(*request) 68 | } 69 | 70 | return nil 71 | } 72 | -------------------------------------------------------------------------------- /rpc/req_async.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "time" 6 | ) 7 | 8 | // 异步RPC请求 9 | // ud: peer/session, reqMsg:请求用的消息, userCallback: 返回消息类型回调 func( ackMsg *ackMsgType) 10 | func Call(sesOrPeer interface{}, reqMsg interface{}, timeout time.Duration, userCallback func(raw interface{})) { 11 | 12 | ses, err := getPeerSession(sesOrPeer) 13 | 14 | if err != nil { 15 | 16 | cellnet.SessionQueuedCall(ses, func() { 17 | userCallback(err) 18 | }) 19 | 20 | return 21 | } 22 | 23 | // 发送RPC请求 24 | req := createRequest(func(raw interface{}) { 25 | cellnet.SessionQueuedCall(ses, func() { 26 | userCallback(raw) 27 | }) 28 | }) 29 | 30 | req.Send(ses, reqMsg) 31 | 32 | // 等待RPC回复 33 | time.AfterFunc(timeout, func() { 34 | 35 | // 取出请求,如果存在,调用超时 36 | if getRequest(req.id) != nil { 37 | cellnet.SessionQueuedCall(ses, func() { 38 | userCallback(ErrTimeout) 39 | }) 40 | } 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /rpc/req_sync.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // 同步RPC请求, ud: peer/session, reqMsg:请求用的消息, 返回消息为返回值 8 | func CallSync(ud interface{}, reqMsg interface{}, timeout time.Duration) (interface{}, error) { 9 | 10 | ses, err := getPeerSession(ud) 11 | 12 | if err != nil { 13 | return nil, err 14 | } 15 | 16 | ret := make(chan interface{}) 17 | // 发送RPC请求 18 | req := createRequest(func(feedbackMsg interface{}) { 19 | ret <- feedbackMsg 20 | }) 21 | 22 | req.Send(ses, reqMsg) 23 | 24 | // 等待RPC回复 25 | select { 26 | case v := <-ret: 27 | return v, nil 28 | case <-time.After(timeout): 29 | 30 | // 清理请求 31 | getRequest(req.id) 32 | 33 | return nil, ErrTimeout 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /rpc/req_type.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "reflect" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | func CallType(sesOrPeer interface{}, reqMsg interface{}, timeout time.Duration, userCallback interface{}) { 11 | callType(sesOrPeer, false, reqMsg, timeout, userCallback) 12 | } 13 | 14 | func CallSyncType(sesOrPeer interface{}, reqMsg interface{}, timeout time.Duration, userCallback interface{}) { 15 | callType(sesOrPeer, true, reqMsg, timeout, userCallback) 16 | } 17 | 18 | // 异步RPC请求,按消息类型,一般用于客户端请求 19 | // ud: peer/session, reqMsg:请求用的消息, userCallback: 返回消息类型回调 func( ackMsg *ackMsgType, error ) 20 | func callType(sesOrPeer interface{}, sync bool, reqMsg interface{}, timeout time.Duration, userCallback interface{}) { 21 | 22 | // 获取回调第一个参数 23 | funcType := reflect.TypeOf(userCallback) 24 | if funcType.Kind() != reflect.Func { 25 | panic("type rpc callback require 'func'") 26 | } 27 | 28 | if funcType.NumIn() != 2 { 29 | panic("callback func param format like 'func(ack *YouMsgACK)'") 30 | } 31 | 32 | ackType := funcType.In(0) 33 | if ackType.Kind() != reflect.Ptr { 34 | panic("callback func param format like 'func(ack *YouMsgACK)'") 35 | } 36 | 37 | ackType = ackType.Elem() 38 | 39 | callFunc := func(rawACK interface{}, err error) { 40 | vCall := reflect.ValueOf(userCallback) 41 | 42 | if rawACK == nil { 43 | rawACK = reflect.New(ackType).Interface() 44 | } 45 | 46 | var errV reflect.Value 47 | if err == nil { 48 | errV = nilError 49 | } else { 50 | errV = reflect.ValueOf(err) 51 | } 52 | 53 | vCall.Call([]reflect.Value{reflect.ValueOf(rawACK), errV}) 54 | } 55 | 56 | ses, err := getPeerSession(sesOrPeer) 57 | 58 | if err != nil { 59 | callFunc(nil, err) 60 | return 61 | } 62 | 63 | createTypeRequest(sync, ackType, timeout, func() { 64 | ses.Send(reqMsg) 65 | }, callFunc) 66 | 67 | } 68 | 69 | var ( 70 | nilError = reflect.Zero(reflect.TypeOf((*error)(nil)).Elem()) 71 | callByType sync.Map // map[reflect.Type]func(interface{}) 72 | ) 73 | 74 | func createTypeRequest(sync bool, ackType reflect.Type, timeout time.Duration, onSend func(), onRecv func(rawACK interface{}, err error)) { 75 | 76 | if sync { 77 | feedBack := make(chan interface{}) 78 | callByType.Store(ackType, feedBack) 79 | 80 | defer callByType.Delete(ackType) 81 | 82 | onSend() 83 | 84 | select { 85 | case ack := <-feedBack: 86 | onRecv(ack, nil) 87 | case <-time.After(timeout): 88 | onRecv(nil, ErrTimeout) 89 | } 90 | } else { 91 | 92 | callByType.Store(ackType, func(rawACK interface{}, err error) { 93 | onRecv(rawACK, err) 94 | callByType.Delete(ackType) 95 | }) 96 | 97 | onSend() 98 | 99 | // 丢弃超时的类型,避免重复请求时,将第二次请求的消息删除 100 | } 101 | 102 | } 103 | 104 | type TypeRPCHooker struct { 105 | } 106 | 107 | func (TypeRPCHooker) OnInboundEvent(inputEvent cellnet.Event) (outputEvent cellnet.Event) { 108 | 109 | incomingMsgType := reflect.TypeOf(inputEvent.Message()).Elem() 110 | 111 | if rawFeedback, ok := callByType.Load(incomingMsgType); ok { 112 | 113 | switch feedBack := rawFeedback.(type) { 114 | case func(rawACK interface{}, err error): 115 | feedBack(inputEvent.Message(), nil) 116 | case chan interface{}: 117 | feedBack <- inputEvent.Message() 118 | } 119 | 120 | return inputEvent 121 | } 122 | 123 | return inputEvent 124 | } 125 | 126 | func (TypeRPCHooker) OnOutboundEvent(inputEvent cellnet.Event) (outputEvent cellnet.Event) { 127 | 128 | return inputEvent 129 | } 130 | -------------------------------------------------------------------------------- /rpc/util.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "errors" 5 | "github.com/davyxu/cellnet" 6 | ) 7 | 8 | var ( 9 | ErrInvalidPeerSession = errors.New("rpc: Invalid peer type, require cellnet.RPCSessionGetter or cellnet.Session") 10 | ErrEmptySession = errors.New("rpc: Empty session") 11 | ) 12 | 13 | type RPCSessionGetter interface { 14 | RPCSession() cellnet.Session 15 | } 16 | 17 | // 从peer获取rpc使用的session 18 | func getPeerSession(ud interface{}) (ses cellnet.Session, err error) { 19 | 20 | if ud == nil { 21 | return nil, ErrInvalidPeerSession 22 | } 23 | 24 | switch i := ud.(type) { 25 | case RPCSessionGetter: 26 | ses = i.RPCSession() 27 | case cellnet.Session: 28 | ses = i 29 | case cellnet.TCPConnector: 30 | ses = i.Session() 31 | default: 32 | err = ErrInvalidPeerSession 33 | return 34 | } 35 | 36 | if ses == nil { 37 | return nil, ErrEmptySession 38 | } 39 | 40 | return 41 | } 42 | -------------------------------------------------------------------------------- /session.go: -------------------------------------------------------------------------------- 1 | package cellnet 2 | 3 | // 长连接 4 | type Session interface { 5 | 6 | // 获得原始的Socket连接 7 | Raw() interface{} 8 | 9 | // 获得Session归属的Peer 10 | Peer() Peer 11 | 12 | // 发送消息,消息需要以指针格式传入 13 | Send(msg interface{}) 14 | 15 | // 断开 16 | Close() 17 | 18 | // 标示ID 19 | ID() int64 20 | } 21 | 22 | // 直接发送数据时,将*RawPacket作为Send参数 23 | type RawPacket struct { 24 | MsgData []byte 25 | MsgID int 26 | } 27 | 28 | func (self *RawPacket) Message() interface{} { 29 | 30 | // 获取消息元信息 31 | meta := MessageMetaByID(self.MsgID) 32 | 33 | // 消息没有注册 34 | if meta == nil { 35 | return struct{}{} 36 | } 37 | 38 | // 创建消息 39 | msg := meta.NewType() 40 | 41 | // 从字节数组转换为消息 42 | err := meta.Codec.Decode(self.MsgData, msg) 43 | if err != nil { 44 | return struct{}{} 45 | } 46 | 47 | return msg 48 | } 49 | -------------------------------------------------------------------------------- /sysmsg.go: -------------------------------------------------------------------------------- 1 | package cellnet 2 | 3 | import "fmt" 4 | 5 | type SessionInit struct { 6 | } 7 | 8 | type SessionAccepted struct { 9 | } 10 | 11 | type SessionConnected struct { 12 | } 13 | 14 | type SessionConnectError struct { 15 | } 16 | 17 | type CloseReason int32 18 | 19 | const ( 20 | CloseReason_IO CloseReason = iota // 普通IO断开 21 | CloseReason_Manual // 关闭前,调用过Session.Close 22 | ) 23 | 24 | func (self CloseReason) String() string { 25 | switch self { 26 | case CloseReason_IO: 27 | return "IO" 28 | case CloseReason_Manual: 29 | return "Manual" 30 | } 31 | 32 | return "Unknown" 33 | } 34 | 35 | type SessionClosed struct { 36 | Reason CloseReason // 断开原因 37 | } 38 | 39 | // udp通知关闭,内部使用 40 | type SessionCloseNotify struct { 41 | } 42 | 43 | func (self *SessionInit) String() string { return fmt.Sprintf("%+v", *self) } 44 | func (self *SessionAccepted) String() string { return fmt.Sprintf("%+v", *self) } 45 | func (self *SessionConnected) String() string { return fmt.Sprintf("%+v", *self) } 46 | func (self *SessionConnectError) String() string { return fmt.Sprintf("%+v", *self) } 47 | func (self *SessionClosed) String() string { return fmt.Sprintf("%+v", *self) } 48 | func (self *SessionCloseNotify) String() string { return fmt.Sprintf("%+v", *self) } 49 | 50 | // 标记系统消息 51 | func (self *SessionInit) SystemMessage() {} 52 | func (self *SessionAccepted) SystemMessage() {} 53 | func (self *SessionConnected) SystemMessage() {} 54 | func (self *SessionConnectError) SystemMessage() {} 55 | func (self *SessionClosed) SystemMessage() {} 56 | func (self *SessionCloseNotify) SystemMessage() {} 57 | 58 | // 使用类型断言判断是否为系统消息 59 | type SystemMessageIdentifier interface { 60 | SystemMessage() 61 | } 62 | -------------------------------------------------------------------------------- /tests/Run.bat: -------------------------------------------------------------------------------- 1 | "c:\Program Files\Git\bin\bash.exe" --login -i .\Run.sh -------------------------------------------------------------------------------- /tests/Run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | # 测试 6 | go test -race -v . 7 | 8 | # 出错时,自动删除文件夹 9 | trap 'rm -rf examplebin' EXIT 10 | 11 | mkdir -p examplebin 12 | 13 | # 保证工程能编译 14 | go build -p 4 -v -o ./examplebin/echo github.com/davyxu/cellnet/examples/echo 15 | go build -p 4 -v -o ./examplebin/echo github.com/davyxu/cellnet/examples/chat/client 16 | go build -p 4 -v -o ./examplebin/echo github.com/davyxu/cellnet/examples/chat/server 17 | go build -p 4 -v -o ./examplebin/echo github.com/davyxu/cellnet/examples/fileserver 18 | go build -p 4 -v -o ./examplebin/echo github.com/davyxu/cellnet/examples/websocket 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/db/redis_test.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/peer" 6 | _ "github.com/davyxu/cellnet/peer/redix" 7 | "github.com/mediocregopher/radix.v2/redis" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func prepareOperator(t *testing.T) cellnet.RedisPoolOperator { 13 | p := peer.NewGenericPeer("redix.Connector", "redis", "127.0.0.1:6379", nil) 14 | p.(cellnet.RedisConnector).SetConnectionCount(1) 15 | p.Start() 16 | 17 | for i := 0; i < 5; i++ { 18 | 19 | if p.(cellnet.PeerReadyChecker).IsReady() { 20 | break 21 | } 22 | 23 | time.Sleep(time.Millisecond * 200) 24 | } 25 | 26 | if !p.(cellnet.PeerReadyChecker).IsReady() { 27 | t.FailNow() 28 | } 29 | 30 | return p.(cellnet.RedisPoolOperator) 31 | } 32 | 33 | func TestRedix(t *testing.T) { 34 | 35 | prepareOperator(t).Operate(func(rawClient interface{}) interface{} { 36 | 37 | client := rawClient.(*redis.Client) 38 | client.Cmd("SET", "mykey", "myvalue") 39 | 40 | v, err := client.Cmd("GET", "mykey").Str() 41 | if err != nil { 42 | t.Error(err) 43 | t.FailNow() 44 | } 45 | if v != "myvalue" { 46 | t.FailNow() 47 | } 48 | 49 | client.Cmd("DEL", "mykey") 50 | 51 | return nil 52 | }) 53 | 54 | } 55 | -------------------------------------------------------------------------------- /tests/db/sql_test.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "github.com/davyxu/cellnet" 7 | "github.com/davyxu/cellnet/peer" 8 | "github.com/davyxu/cellnet/peer/mysql" 9 | "testing" 10 | ) 11 | 12 | func TestMySQL(t *testing.T) { 13 | // 从地址中选择mysql数据库,这里选择mysql系统库 14 | p := peer.NewGenericPeer("mysql.Connector", "mysqldemo", "root:123456@(localhost:3306)/mysql", nil) 15 | p.(cellnet.MySQLConnector).SetConnectionCount(3) 16 | 17 | // 阻塞 18 | p.Start() 19 | 20 | op := p.(cellnet.MySQLOperator) 21 | 22 | op.Operate(func(rawClient interface{}) interface{} { 23 | 24 | client := rawClient.(*sql.DB) 25 | 26 | // 查找默认用户 27 | mysql.NewWrapper(client).Query("select User from user").Each(func(wrapper *mysql.Wrapper) bool { 28 | 29 | var name string 30 | wrapper.Scan(&name) 31 | fmt.Println(name) 32 | return true 33 | }) 34 | 35 | return nil 36 | }) 37 | 38 | } 39 | -------------------------------------------------------------------------------- /tests/echo_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davyxu/cellnet" 6 | "github.com/davyxu/cellnet/peer" 7 | _ "github.com/davyxu/cellnet/peer/gorillaws" 8 | _ "github.com/davyxu/cellnet/peer/tcp" 9 | _ "github.com/davyxu/cellnet/peer/udp" 10 | "github.com/davyxu/cellnet/proc" 11 | _ "github.com/davyxu/cellnet/proc/gorillaws" 12 | _ "github.com/davyxu/cellnet/proc/tcp" 13 | _ "github.com/davyxu/cellnet/proc/udp" 14 | "testing" 15 | "time" 16 | ) 17 | 18 | type echoContext struct { 19 | Address string 20 | Protocol string 21 | Processor string 22 | Tester *SignalTester 23 | Acceptor cellnet.GenericPeer 24 | } 25 | 26 | var ( 27 | echoContexts = []*echoContext{ 28 | { 29 | Address: "127.0.0.1:7701", 30 | Protocol: "tcp", 31 | Processor: "tcp.ltv", 32 | }, 33 | { 34 | Address: "127.0.0.1:7702", 35 | Protocol: "udp", 36 | Processor: "udp.ltv", 37 | }, 38 | 39 | { 40 | Address: "127.0.0.1:7703", 41 | Protocol: "gorillaws", 42 | Processor: "gorillaws.ltv", 43 | }, 44 | } 45 | ) 46 | 47 | func echo_StartServer(context *echoContext) { 48 | queue := cellnet.NewEventQueue() 49 | 50 | context.Acceptor = peer.NewGenericPeer(context.Protocol+".Acceptor", context.Protocol+"server", context.Address, queue) 51 | 52 | proc.BindProcessorHandler(context.Acceptor, context.Processor, func(ev cellnet.Event) { 53 | 54 | switch msg := ev.Message().(type) { 55 | case *cellnet.SessionAccepted: 56 | fmt.Println("server accepted") 57 | case *TestEchoACK: 58 | 59 | fmt.Printf("server recv %+v\n", msg) 60 | 61 | ev.Session().Send(&TestEchoACK{ 62 | Msg: msg.Msg, 63 | Value: msg.Value, 64 | }) 65 | 66 | case *cellnet.SessionClosed: 67 | fmt.Println("session closed: ", ev.Session().ID()) 68 | } 69 | 70 | }) 71 | 72 | context.Acceptor.Start() 73 | 74 | queue.StartLoop() 75 | } 76 | 77 | func echo_StartClient(echoContext *echoContext) { 78 | queue := cellnet.NewEventQueue() 79 | 80 | p := peer.NewGenericPeer(echoContext.Protocol+".Connector", echoContext.Protocol+"client", echoContext.Address, queue) 81 | 82 | proc.BindProcessorHandler(p, echoContext.Processor, func(ev cellnet.Event) { 83 | 84 | switch msg := ev.Message().(type) { 85 | case *cellnet.SessionConnected: 86 | fmt.Println("client connected") 87 | ev.Session().Send(&TestEchoACK{ 88 | Msg: "hello", 89 | Value: 1234, 90 | }) 91 | case *TestEchoACK: 92 | 93 | fmt.Printf("client recv %+v\n", msg) 94 | 95 | echoContext.Tester.Done(1) 96 | 97 | case *cellnet.SessionClosed: 98 | fmt.Println("client closed") 99 | } 100 | }) 101 | 102 | p.Start() 103 | 104 | queue.StartLoop() 105 | 106 | echoContext.Tester.WaitAndExpect("not recv data", 1) 107 | } 108 | 109 | func runEcho(t *testing.T, index int) { 110 | 111 | ctx := echoContexts[index] 112 | 113 | ctx.Tester = NewSignalTester(t) 114 | ctx.Tester.SetTimeout(time.Hour) 115 | 116 | echo_StartServer(ctx) 117 | 118 | echo_StartClient(ctx) 119 | 120 | ctx.Acceptor.Stop() 121 | } 122 | 123 | func TestEchoTCP(t *testing.T) { 124 | 125 | runEcho(t, 0) 126 | } 127 | 128 | func TestEchoUDP(t *testing.T) { 129 | 130 | runEcho(t, 1) 131 | } 132 | 133 | func TestEchoWS(t *testing.T) { 134 | 135 | runEcho(t, 2) 136 | } 137 | -------------------------------------------------------------------------------- /tests/gracefulexit_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davyxu/cellnet" 6 | "github.com/davyxu/cellnet/peer" 7 | "github.com/davyxu/cellnet/proc" 8 | "sync" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | const recreateConn_Address = "127.0.0.1:7201" 14 | 15 | var recreateConn_Signal *SignalTester 16 | 17 | func recreateConn_StartServer() { 18 | queue := cellnet.NewEventQueue() 19 | 20 | p := peer.NewGenericPeer("tcp.Acceptor", "server", recreateConn_Address, queue) 21 | 22 | proc.BindProcessorHandler(p, "tcp.ltv", func(ev cellnet.Event) { 23 | 24 | switch msg := ev.Message().(type) { 25 | case *TestEchoACK: 26 | 27 | fmt.Printf("server recv %+v\n", msg) 28 | 29 | ev.Session().Send(&TestEchoACK{ 30 | Msg: msg.Msg, 31 | Value: msg.Value, 32 | }) 33 | } 34 | }) 35 | 36 | p.Start() 37 | 38 | queue.StartLoop() 39 | } 40 | 41 | // 客户端连接上后, 主动断开连接, 确保连接正常关闭 42 | func runConnClose() { 43 | queue := cellnet.NewEventQueue() 44 | 45 | var times int 46 | 47 | p := peer.NewGenericPeer("tcp.Connector", "client.ConnClose", recreateConn_Address, queue) 48 | 49 | proc.BindProcessorHandler(p, "tcp.ltv", func(ev cellnet.Event) { 50 | 51 | switch ev.Message().(type) { 52 | case *cellnet.SessionConnected: 53 | p.Stop() 54 | 55 | time.Sleep(time.Millisecond * 100) 56 | 57 | if times < 3 { 58 | p.Start() 59 | times++ 60 | } else { 61 | recreateConn_Signal.Done(1) 62 | } 63 | } 64 | }) 65 | 66 | p.Start() 67 | 68 | queue.StartLoop() 69 | 70 | recreateConn_Signal.WaitAndExpect("not expect times", 1) 71 | 72 | p.Stop() 73 | } 74 | 75 | func TestCreateDestroyConnector(t *testing.T) { 76 | 77 | recreateConn_Signal = NewSignalTester(t) 78 | 79 | recreateConn_StartServer() 80 | 81 | runConnClose() 82 | } 83 | 84 | const recreateAcc_clientConnection = 3 85 | 86 | const recreateAcc_Address = "127.0.0.1:7711" 87 | 88 | func TestCreateDestroyAcceptor(t *testing.T) { 89 | queue := cellnet.NewEventQueue() 90 | 91 | var allAccepted sync.WaitGroup 92 | 93 | p := peer.NewGenericPeer("tcp.Acceptor", "server", recreateAcc_Address, queue) 94 | 95 | proc.BindProcessorHandler(p, "tcp.ltv", func(ev cellnet.Event) { 96 | 97 | switch ev.Message().(type) { 98 | case *cellnet.SessionAccepted: 99 | 100 | allAccepted.Done() 101 | 102 | } 103 | }) 104 | 105 | p.Start() 106 | 107 | queue.StartLoop() 108 | 109 | log.Debugln("Start connecting...") 110 | allAccepted.Add(recreateAcc_clientConnection) 111 | runMultiConnection() 112 | 113 | log.Debugln("Wait all accept...") 114 | allAccepted.Wait() 115 | 116 | log.Debugln("Close acceptor...") 117 | p.Stop() 118 | 119 | // 确认所有连接已经断开 120 | time.Sleep(time.Second) 121 | 122 | log.Debugln("Session count:", p.(cellnet.SessionAccessor).SessionCount()) 123 | 124 | p.Start() 125 | log.Debugln("Start connecting...") 126 | allAccepted.Add(recreateAcc_clientConnection) 127 | runMultiConnection() 128 | 129 | log.Debugln("Wait all accept...") 130 | allAccepted.Wait() 131 | 132 | log.Debugln("All done") 133 | } 134 | 135 | func runMultiConnection() { 136 | 137 | for i := 0; i < recreateAcc_clientConnection; i++ { 138 | 139 | p := peer.NewGenericPeer("tcp.Connector", "client.ConnClose", recreateAcc_Address, nil) 140 | 141 | proc.BindProcessorHandler(p, "tcp.ltv", func(ev cellnet.Event) { 142 | 143 | }) 144 | 145 | p.Start() 146 | 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /tests/http_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davyxu/cellnet" 6 | _ "github.com/davyxu/cellnet/codec/httpform" 7 | _ "github.com/davyxu/cellnet/codec/httpjson" 8 | "github.com/davyxu/cellnet/peer" 9 | httppeer "github.com/davyxu/cellnet/peer/http" 10 | "github.com/davyxu/cellnet/proc" 11 | _ "github.com/davyxu/cellnet/proc/http" 12 | "reflect" 13 | "testing" 14 | ) 15 | 16 | const httpTestAddr = "127.0.0.1:8081" 17 | 18 | func TestHttp(t *testing.T) { 19 | 20 | p := peer.NewGenericPeer("http.Acceptor", "httpserver", httpTestAddr, nil) 21 | 22 | proc.BindProcessorHandler(p, "http", func(raw cellnet.Event) { 23 | 24 | if matcher, ok := raw.Session().(httppeer.RequestMatcher); ok { 25 | switch { 26 | case matcher.Match("GET", "/hello_get"): 27 | 28 | // 默认返回json 29 | raw.Session().Send(&httppeer.MessageRespond{ 30 | Msg: &HttpEchoACK{ 31 | Token: "get", 32 | }, 33 | }) 34 | case matcher.Match("POST", "/hello_post"): 35 | 36 | // 默认返回json 37 | raw.Session().Send(&httppeer.MessageRespond{ 38 | Msg: &HttpEchoACK{ 39 | Token: "post", 40 | }, 41 | }) 42 | 43 | } 44 | } 45 | 46 | }) 47 | 48 | p.Start() 49 | 50 | requestThenValid(t, "GET", "/hello_get", &HttpEchoREQ{ 51 | UserName: "kitty_get", 52 | }, &HttpEchoACK{ 53 | Token: "get", 54 | }) 55 | 56 | requestThenValid(t, "POST", "/hello_post", &HttpEchoREQ{ 57 | UserName: "kitty_post", 58 | }, &HttpEchoACK{ 59 | Token: "post", 60 | }) 61 | 62 | //p.Stop() 63 | } 64 | 65 | func requestThenValid(t *testing.T, method, path string, req, expectACK interface{}) { 66 | 67 | p := peer.NewGenericPeer("http.Connector", "httpclient", httpTestAddr, nil).(cellnet.HTTPConnector) 68 | 69 | ackMsg := reflect.New(reflect.TypeOf(expectACK).Elem()).Interface() 70 | 71 | err := p.Request(method, path, &cellnet.HTTPRequest{ 72 | REQMsg: req, 73 | ACKMsg: ackMsg, 74 | }) 75 | 76 | if err != nil { 77 | t.Error(err) 78 | t.FailNow() 79 | } 80 | 81 | if !reflect.DeepEqual(ackMsg, expectACK) { 82 | t.Log("unexpect token result", err) 83 | t.FailNow() 84 | } 85 | 86 | } 87 | 88 | type HttpEchoREQ struct { 89 | UserName string 90 | } 91 | 92 | type HttpEchoACK struct { 93 | Token string 94 | Status int32 95 | } 96 | 97 | func (self *HttpEchoREQ) String() string { return fmt.Sprintf("%+v", *self) } 98 | func (self *HttpEchoACK) String() string { return fmt.Sprintf("%+v", *self) } 99 | -------------------------------------------------------------------------------- /tests/httppage_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davyxu/cellnet" 6 | "github.com/davyxu/cellnet/peer" 7 | httppeer "github.com/davyxu/cellnet/peer/http" 8 | "github.com/davyxu/cellnet/proc" 9 | "io/ioutil" 10 | "net/http" 11 | "testing" 12 | "time" 13 | ) 14 | 15 | const pageAddress = "127.0.0.1:10087" 16 | 17 | func TestPrintPage(t *testing.T) { 18 | 19 | p := peer.NewGenericPeer("http.Acceptor", "httpserver", pageAddress, nil) 20 | 21 | proc.BindProcessorHandler(p, "http", func(raw cellnet.Event) { 22 | 23 | switch { 24 | case raw.Session().(httppeer.RequestMatcher).Match("GET", "/"): 25 | 26 | raw.Session().Send(&httppeer.HTMLRespond{ 27 | StatusCode: http.StatusOK, 28 | PageTemplate: "index", 29 | TemplateModel: "world", 30 | }) 31 | } 32 | 33 | }) 34 | 35 | p.Start() 36 | 37 | validPage(t, fmt.Sprintf("http://%s", pageAddress), "

Hello world

") 38 | 39 | // p.Stop() 40 | 41 | } 42 | 43 | func validPage(t *testing.T, url, expectAck string) { 44 | c := &http.Client{ 45 | Timeout: time.Second * 5, 46 | } 47 | resp, err := c.Get(url) 48 | if err != nil { 49 | t.Log("http req failed", err) 50 | t.FailNow() 51 | } 52 | 53 | defer resp.Body.Close() 54 | bodyData, err := ioutil.ReadAll(resp.Body) 55 | if err != nil { 56 | t.Log("http response failed", err) 57 | t.FailNow() 58 | } 59 | 60 | body := string(bodyData) 61 | 62 | if body != expectAck { 63 | t.Log("unexpect result", err, body) 64 | t.FailNow() 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/index.tpl: -------------------------------------------------------------------------------- 1 |

Hello {{.}}

-------------------------------------------------------------------------------- /tests/issue_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "github.com/davyxu/cellnet/peer" 6 | "sync" 7 | "testing" 8 | ) 9 | 10 | func TestContextSet(t *testing.T) { 11 | 12 | p := peer.NewPeer("tcp.Acceptor").(cellnet.TCPAcceptor) 13 | p.(cellnet.ContextSet).SetContext("sd", nil) 14 | 15 | if v, ok := p.(cellnet.ContextSet).GetContext("sd"); ok && v == nil { 16 | 17 | } else { 18 | t.FailNow() 19 | } 20 | 21 | var connMap = new(sync.Map) 22 | if p.(cellnet.ContextSet).FetchContext("sd", &connMap) && connMap == nil { 23 | 24 | } else { 25 | t.FailNow() 26 | } 27 | } 28 | 29 | func TestAutoAllocPort(t *testing.T) { 30 | 31 | p := peer.NewGenericPeer("tcp.Acceptor", "autoacc", ":0", nil) 32 | p.Start() 33 | 34 | t.Log("auto alloc port:", p.(cellnet.TCPAcceptor).Port()) 35 | } 36 | -------------------------------------------------------------------------------- /tests/log.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "github.com/davyxu/golog" 5 | ) 6 | 7 | var log = golog.New("test") 8 | -------------------------------------------------------------------------------- /tests/proto.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davyxu/cellnet" 6 | "github.com/davyxu/cellnet/codec" 7 | _ "github.com/davyxu/cellnet/codec/binary" 8 | "github.com/davyxu/cellnet/util" 9 | "reflect" 10 | ) 11 | 12 | type TestEchoACK struct { 13 | Msg string 14 | Value int32 15 | } 16 | 17 | func (self *TestEchoACK) String() string { return fmt.Sprintf("%+v", *self) } 18 | 19 | func init() { 20 | cellnet.RegisterMessageMeta(&cellnet.MessageMeta{ 21 | Codec: codec.MustGetCodec("binary"), 22 | Type: reflect.TypeOf((*TestEchoACK)(nil)).Elem(), 23 | ID: int(util.StringHash("tests.TestEchoACK")), 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /tests/signaltester.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | type SignalTester struct { 9 | *testing.T 10 | 11 | signal chan interface{} 12 | 13 | timeout time.Duration 14 | } 15 | 16 | func (self *SignalTester) SetTimeout(du time.Duration) { 17 | self.timeout = du 18 | } 19 | 20 | func (self *SignalTester) WaitAndExpect(msg string, values ...interface{}) bool { 21 | 22 | var recvValues = map[interface{}]bool{} 23 | 24 | for _, value := range values { 25 | select { 26 | case v := <-self.signal: 27 | recvValues[v] = true 28 | 29 | case <-time.After(self.timeout): 30 | self.Errorf("signal timeout: %d %s", value, msg) 31 | self.Fail() 32 | return false 33 | } 34 | } 35 | 36 | for _, value := range values { 37 | 38 | if _, ok := recvValues[value]; !ok { 39 | self.FailNow() 40 | } 41 | 42 | } 43 | 44 | return true 45 | } 46 | 47 | func (self *SignalTester) Done(value interface{}) { 48 | self.signal <- value 49 | } 50 | 51 | func NewSignalTester(t *testing.T) *SignalTester { 52 | 53 | return &SignalTester{ 54 | T: t, 55 | timeout: 2 * time.Second, 56 | signal: make(chan interface{}), 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/timer_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/davyxu/cellnet" 8 | "github.com/davyxu/cellnet/timer" 9 | ) 10 | 11 | func TestAfterTimer(t *testing.T) { 12 | 13 | signal := NewSignalTester(t) 14 | 15 | queue := cellnet.NewEventQueue() 16 | 17 | queue.StartLoop() 18 | 19 | timer.After(queue, 100*time.Millisecond, func() { 20 | log.Debugln("after 100 ms") 21 | 22 | signal.Done(1) 23 | }, nil) 24 | 25 | timer.After(queue, 200*time.Millisecond, func(context interface{}) { 26 | 27 | if context.(string) != "context" { 28 | t.FailNow() 29 | } 30 | 31 | log.Debugln("after 200 ms") 32 | 33 | signal.Done(2) 34 | }, "context") 35 | 36 | signal.WaitAndExpect("100ms after not done", 1) 37 | 38 | signal.WaitAndExpect("200ms after not done", 2) 39 | } 40 | 41 | func TestLoopTimer(t *testing.T) { 42 | 43 | signal := NewSignalTester(t) 44 | signal.SetTimeout(60 * time.Second) 45 | 46 | queue := cellnet.NewEventQueue() 47 | 48 | // 启动消息循环 49 | queue.StartLoop() 50 | 51 | var count int 52 | 53 | // 启动计时循环 54 | timer.NewLoop(queue, time.Millisecond*10, func(ctx *timer.Loop) { 55 | 56 | log.Debugln("tick 10 ms", count) 57 | 58 | count++ 59 | 60 | if count >= 10 { 61 | signal.Done(1) 62 | ctx.Stop() 63 | } 64 | }, nil).Start() 65 | 66 | signal.WaitAndExpect("10ms * 10 times ticker not done", 1) 67 | } 68 | -------------------------------------------------------------------------------- /timer/after.go: -------------------------------------------------------------------------------- 1 | package timer 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "time" 6 | ) 7 | 8 | type AfterStopper interface { 9 | Stop() bool 10 | } 11 | 12 | // 在给定的duration持续时间后, 执行callbackObj对象类型对应的函数回调 13 | // q: 队列,在指定的队列goroutine执行, 空时,直接在当前goroutine 14 | // context: 将context上下文传递到带有context指针的函数回调中 15 | func After(q cellnet.EventQueue, duration time.Duration, callbackObj interface{}, context interface{}) AfterStopper { 16 | 17 | return time.AfterFunc(duration, func() { 18 | switch callback := callbackObj.(type) { 19 | case func(): 20 | if callback != nil { 21 | cellnet.QueuedCall(q, callback) 22 | } 23 | 24 | case func(interface{}): 25 | if callback != nil { 26 | 27 | cellnet.QueuedCall(q, func() { 28 | callback(context) 29 | }) 30 | } 31 | default: 32 | panic("timer.After: require func() or func(interface{})") 33 | } 34 | }) 35 | 36 | } 37 | -------------------------------------------------------------------------------- /timer/loop.go: -------------------------------------------------------------------------------- 1 | package timer 2 | 3 | import ( 4 | "github.com/davyxu/cellnet" 5 | "sync/atomic" 6 | "time" 7 | ) 8 | 9 | // 轻量级的持续Tick循环 10 | type Loop struct { 11 | Context interface{} 12 | Duration time.Duration 13 | notifyCallback func(*Loop) 14 | 15 | running int64 16 | 17 | Queue cellnet.EventQueue 18 | } 19 | 20 | func (self *Loop) Running() bool { 21 | return atomic.LoadInt64(&self.running) != 0 22 | } 23 | 24 | func (self *Loop) setRunning(v bool) { 25 | 26 | if v { 27 | atomic.StoreInt64(&self.running, 1) 28 | } else { 29 | atomic.StoreInt64(&self.running, 0) 30 | } 31 | 32 | } 33 | 34 | // 开始Tick 35 | func (self *Loop) Start() bool { 36 | 37 | if self.Running() { 38 | return false 39 | } 40 | 41 | atomic.StoreInt64(&self.running, 1) 42 | 43 | self.rawPost() 44 | 45 | return true 46 | } 47 | 48 | func (self *Loop) rawPost() { 49 | 50 | if self.Duration == 0 { 51 | panic("seconds can be zero in loop") 52 | } 53 | 54 | if self.Running() { 55 | After(self.Queue, self.Duration, func() { 56 | 57 | tick(self, false) 58 | }, nil) 59 | } 60 | } 61 | 62 | func (self *Loop) NextLoop() { 63 | 64 | self.Queue.Post(func() { 65 | tick(self, true) 66 | }) 67 | } 68 | 69 | func (self *Loop) Stop() { 70 | 71 | self.setRunning(false) 72 | } 73 | 74 | func (self *Loop) Resume() { 75 | 76 | self.setRunning(true) 77 | } 78 | 79 | // 马上调用一次用户回调 80 | func (self *Loop) Notify() *Loop { 81 | self.notifyCallback(self) 82 | return self 83 | } 84 | 85 | func (self *Loop) SetNotifyFunc(notifyCallback func(*Loop)) *Loop { 86 | self.notifyCallback = notifyCallback 87 | return self 88 | } 89 | 90 | func (self *Loop) NotifyFunc() func(*Loop) { 91 | return self.notifyCallback 92 | } 93 | 94 | func tick(ctx interface{}, nextLoop bool) { 95 | 96 | loop := ctx.(*Loop) 97 | 98 | if !nextLoop && loop.Running() { 99 | 100 | // 即便在Notify中发生了崩溃,也会使用defer再次继续循环 101 | defer loop.rawPost() 102 | } 103 | 104 | loop.Notify() 105 | } 106 | 107 | // 执行一个循环, 持续调用callback, 周期是duration 108 | // context: 将context上下文传递到带有context指针的函数回调中 109 | func NewLoop(q cellnet.EventQueue, duration time.Duration, notifyCallback func(*Loop), context interface{}) *Loop { 110 | 111 | self := &Loop{ 112 | Context: context, 113 | Duration: duration, 114 | notifyCallback: notifyCallback, 115 | Queue: q, 116 | } 117 | 118 | return self 119 | } 120 | -------------------------------------------------------------------------------- /timer/loop_test.go: -------------------------------------------------------------------------------- 1 | package timer 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davyxu/cellnet" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestLoopPanic(t *testing.T) { 11 | 12 | q := cellnet.NewEventQueue() 13 | q.EnableCapturePanic(true) 14 | 15 | q.StartLoop() 16 | 17 | var times = 3 18 | 19 | NewLoop(q, time.Millisecond*100, func(loop *Loop) { 20 | 21 | times-- 22 | if times == 0 { 23 | loop.Stop() 24 | q.StopLoop() 25 | } 26 | 27 | fmt.Println("before") 28 | panic("panic") 29 | fmt.Println("after") 30 | 31 | }, nil).Start() 32 | 33 | q.Wait() 34 | } 35 | -------------------------------------------------------------------------------- /util/addr_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDetectPort(t *testing.T) { 8 | DetectPort("100~200/path", func(a *Address, port int) (interface{}, error) { 9 | if port != 100 { 10 | t.FailNow() 11 | } 12 | 13 | return nil, nil 14 | }) 15 | 16 | DetectPort("scheme://host:100~200/path", func(a *Address, port int) (interface{}, error) { 17 | if port != 100 { 18 | t.FailNow() 19 | } 20 | 21 | return nil, nil 22 | }) 23 | 24 | } 25 | -------------------------------------------------------------------------------- /util/codec.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "compress/zlib" 6 | "crypto/md5" 7 | "encoding/hex" 8 | "io/ioutil" 9 | ) 10 | 11 | // 字符串转为16位整形哈希 12 | func StringHash(s string) (hash uint16) { 13 | 14 | for _, c := range s { 15 | 16 | ch := uint16(c) 17 | 18 | hash = hash + ((hash) << 5) + ch + (ch << 7) 19 | } 20 | 21 | return 22 | } 23 | 24 | // 字节计算MD5 25 | func BytesMD5(data []byte) string { 26 | m := md5.New() 27 | m.Write(data) 28 | return hex.EncodeToString(m.Sum(nil)) 29 | } 30 | 31 | // 字符串计算MD5 32 | func StringMD5(str string) string { 33 | return BytesMD5([]byte(str)) 34 | } 35 | 36 | // 压缩字节 37 | func CompressBytes(data []byte) ([]byte, error) { 38 | 39 | var buf bytes.Buffer 40 | 41 | writer := zlib.NewWriter(&buf) 42 | 43 | _, err := writer.Write(data) 44 | if err != nil { 45 | return nil, err 46 | } 47 | writer.Close() 48 | 49 | return buf.Bytes(), nil 50 | } 51 | 52 | // 解压字节 53 | func DecompressBytes(data []byte) ([]byte, error) { 54 | 55 | reader, err := zlib.NewReader(bytes.NewReader(data)) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | defer reader.Close() 61 | 62 | return ioutil.ReadAll(reader) 63 | } 64 | -------------------------------------------------------------------------------- /util/ioutil.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "net" 7 | "os" 8 | ) 9 | 10 | // 完整发送所有封包 11 | func WriteFull(writer io.Writer, buf []byte) error { 12 | 13 | total := len(buf) 14 | 15 | for pos := 0; pos < total; { 16 | 17 | n, err := writer.Write(buf[pos:]) 18 | 19 | if err != nil { 20 | return err 21 | } 22 | 23 | pos += n 24 | } 25 | 26 | return nil 27 | 28 | } 29 | 30 | // 读取文本文件的所有行 31 | func ReadFileLines(filename string, callback func(line string) bool) error { 32 | 33 | f, err := os.Open(filename) 34 | 35 | if err != nil { 36 | return err 37 | } 38 | 39 | defer f.Close() 40 | 41 | reader := bufio.NewScanner(f) 42 | 43 | reader.Split(bufio.ScanLines) 44 | for reader.Scan() { 45 | 46 | if !callback(reader.Text()) { 47 | break 48 | } 49 | } 50 | 51 | return nil 52 | } 53 | 54 | // 检查文件是否存在 55 | func FileExists(name string) bool { 56 | if _, err := os.Stat(name); err != nil { 57 | if os.IsNotExist(err) { 58 | return false 59 | } 60 | } 61 | return true 62 | } 63 | 64 | // 获取文件大小 65 | func FileSize(name string) int64 { 66 | if info, err := os.Stat(name); err == nil { 67 | return info.Size() 68 | } 69 | 70 | return 0 71 | } 72 | 73 | // 判断网络错误 74 | func IsEOFOrNetReadError(err error) bool { 75 | if err == io.EOF { 76 | return true 77 | } 78 | ne, ok := err.(*net.OpError) 79 | return ok && ne.Op == "read" 80 | } 81 | -------------------------------------------------------------------------------- /util/ioutil_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | type mywriter struct { 9 | bytes.Buffer 10 | } 11 | 12 | func (self *mywriter) Write(p []byte) (n int, err error) { 13 | 14 | if len(p) > 2 { 15 | n = 2 16 | } else { 17 | n = len(p) 18 | } 19 | 20 | self.Buffer.Write(p[0:n]) 21 | 22 | return n, nil 23 | } 24 | 25 | func TestWriteFull(t *testing.T) { 26 | 27 | var m mywriter 28 | 29 | WriteFull(&m, []byte{1, 2, 3, 4, 5}) 30 | 31 | t.Log(m.Bytes()) 32 | } 33 | 34 | func TestCompressBytes(t *testing.T) { 35 | 36 | data := []byte("hello") 37 | 38 | data, err := CompressBytes(data) 39 | if err != nil { 40 | t.FailNow() 41 | } 42 | 43 | t.Log(DecompressBytes(data)) 44 | } 45 | 46 | func TestStackToString(t *testing.T) { 47 | t.Log(StackToString(5)) 48 | } 49 | -------------------------------------------------------------------------------- /util/kvfile.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | // 读取=分割的配置文件 9 | func ReadKVFile(filename string, callback func(k, v string) bool) (ret error) { 10 | readErr := ReadFileLines(filename, func(line string) bool { 11 | 12 | line = strings.TrimSpace(line) 13 | 14 | // 注释 15 | if strings.HasPrefix(line, "#") { 16 | return true 17 | } 18 | 19 | // 等号切分KV 20 | pairs := strings.Split(line, "=") 21 | 22 | switch len(pairs) { 23 | case 1: 24 | value := strings.TrimSpace(pairs[0]) 25 | 26 | if value == "" { 27 | return true 28 | } 29 | 30 | return callback("", value) 31 | case 2: 32 | key := strings.TrimSpace(pairs[0]) 33 | value := strings.TrimSpace(pairs[1]) 34 | 35 | if key == "" { 36 | return true 37 | } 38 | 39 | return callback(key, value) 40 | default: 41 | ret = errors.New("Require '=' splite key and value") 42 | return false 43 | } 44 | 45 | }) 46 | 47 | if readErr != nil { 48 | return readErr 49 | } 50 | 51 | return 52 | } 53 | 54 | type KVPair struct { 55 | Key string 56 | Value string 57 | } 58 | 59 | // 将=分割的文件按值读回 60 | func ReadKVFileValues(filename string) (ret []KVPair, err error) { 61 | 62 | err = ReadKVFile(filename, func(k, v string) bool { 63 | 64 | ret = append(ret, KVPair{k, v}) 65 | return true 66 | }) 67 | 68 | return 69 | } 70 | -------------------------------------------------------------------------------- /util/packet.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "github.com/davyxu/cellnet" 7 | "github.com/davyxu/cellnet/codec" 8 | "io" 9 | ) 10 | 11 | var ( 12 | ErrMaxPacket = errors.New("packet over size") 13 | ErrMinPacket = errors.New("packet short size") 14 | ErrShortMsgID = errors.New("short msgid") 15 | ) 16 | 17 | const ( 18 | bodySize = 2 // 包体大小字段 19 | msgIDSize = 2 // 消息ID字段 20 | ) 21 | 22 | // 接收Length-Type-Value格式的封包流程 23 | func RecvLTVPacket(reader io.Reader, maxPacketSize int) (msg interface{}, err error) { 24 | 25 | // Size为uint16,占2字节 26 | var sizeBuffer = make([]byte, bodySize) 27 | 28 | // 持续读取Size直到读到为止 29 | _, err = io.ReadFull(reader, sizeBuffer) 30 | 31 | // 发生错误时返回 32 | if err != nil { 33 | return 34 | } 35 | 36 | if len(sizeBuffer) < bodySize { 37 | return nil, ErrMinPacket 38 | } 39 | 40 | // 用小端格式读取Size 41 | size := binary.LittleEndian.Uint16(sizeBuffer) 42 | 43 | if maxPacketSize > 0 && int(size) >= maxPacketSize { 44 | return nil, ErrMaxPacket 45 | } 46 | 47 | // 分配包体大小 48 | body := make([]byte, size) 49 | 50 | // 读取包体数据 51 | _, err = io.ReadFull(reader, body) 52 | 53 | // 发生错误时返回 54 | if err != nil { 55 | return 56 | } 57 | 58 | if len(body) < msgIDSize { 59 | return nil, ErrShortMsgID 60 | } 61 | 62 | msgid := binary.LittleEndian.Uint16(body) 63 | 64 | msgData := body[msgIDSize:] 65 | 66 | // 将字节数组和消息ID用户解出消息 67 | msg, _, err = codec.DecodeMessage(int(msgid), msgData) 68 | if err != nil { 69 | // TODO 接收错误时,返回消息 70 | return nil, err 71 | } 72 | 73 | return 74 | } 75 | 76 | // 发送Length-Type-Value格式的封包流程 77 | func SendLTVPacket(writer io.Writer, ctx cellnet.ContextSet, data interface{}) error { 78 | 79 | var ( 80 | msgData []byte 81 | msgID int 82 | meta *cellnet.MessageMeta 83 | ) 84 | 85 | switch m := data.(type) { 86 | case *cellnet.RawPacket: // 发裸包 87 | msgData = m.MsgData 88 | msgID = m.MsgID 89 | default: // 发普通编码包 90 | var err error 91 | 92 | // 将用户数据转换为字节数组和消息ID 93 | msgData, meta, err = codec.EncodeMessage(data, ctx) 94 | 95 | if err != nil { 96 | return err 97 | } 98 | 99 | msgID = meta.ID 100 | } 101 | 102 | pkt := make([]byte, bodySize+msgIDSize+len(msgData)) 103 | 104 | // Length 105 | binary.LittleEndian.PutUint16(pkt, uint16(msgIDSize+len(msgData))) 106 | 107 | // Type 108 | binary.LittleEndian.PutUint16(pkt[bodySize:], uint16(msgID)) 109 | 110 | // Value 111 | copy(pkt[bodySize+msgIDSize:], msgData) 112 | 113 | // 将数据写入Socket 114 | err := WriteFull(writer, pkt) 115 | 116 | // Codec中使用内存池时的释放位置 117 | if meta != nil { 118 | codec.FreeCodecResource(meta.Codec, msgData, ctx) 119 | } 120 | 121 | return err 122 | } 123 | -------------------------------------------------------------------------------- /util/queue.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | type Queue struct { 4 | list []interface{} 5 | } 6 | 7 | func (self *Queue) Enqueue(data interface{}) { 8 | 9 | self.list = append(self.list, data) 10 | } 11 | 12 | func (self *Queue) Count() int { 13 | return len(self.list) 14 | } 15 | 16 | func (self *Queue) Peek() interface{} { 17 | return self.list[0] 18 | } 19 | 20 | func (self *Queue) Dequeue() (ret interface{}) { 21 | 22 | if len(self.list) == 0 { 23 | return nil 24 | } 25 | 26 | ret = self.list[0] 27 | 28 | self.list = self.list[1:] 29 | 30 | return 31 | } 32 | 33 | func (self *Queue) Clear() { 34 | self.list = self.list[0:0] 35 | } 36 | 37 | func NewQueue(size int) *Queue { 38 | 39 | return &Queue{ 40 | list: make([]interface{}, 0, size), 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /util/sys.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | "runtime" 7 | "strings" 8 | ) 9 | 10 | // 给定打印层数,一般3~5覆盖你的逻辑及封装代码范围 11 | func StackToString(count int) string { 12 | 13 | const startStack = 2 14 | 15 | var sb strings.Builder 16 | 17 | for i := startStack; i < startStack+count; i++ { 18 | _, file, line, ok := runtime.Caller(i) 19 | 20 | var str string 21 | 22 | if ok { 23 | str = fmt.Sprintf("%s:%d", filepath.Base(file), line) 24 | } else { 25 | str = "??" 26 | } 27 | 28 | // 折叠?? 29 | if str != "??" { 30 | if i > startStack { 31 | sb.WriteString(" -> ") 32 | } 33 | 34 | sb.WriteString(str) 35 | } else { 36 | break 37 | } 38 | 39 | } 40 | 41 | return sb.String() 42 | } 43 | --------------------------------------------------------------------------------