├── cmd └── ismg │ ├── sgip │ └── main.go │ ├── cmpp │ ├── install.sh │ ├── start.sh │ ├── main.go │ ├── make.sh │ ├── server_test.go │ └── server.go │ └── smgp │ ├── install.sh │ ├── start.sh │ ├── main.go │ ├── make.sh │ ├── server_test.go │ └── server.go ├── shells ├── pprof_heap.sh ├── pprof_profile.sh └── pprof_goroutine.sh ├── codec ├── smgp │ ├── options_test.go │ ├── interface.go │ ├── report_test.go │ ├── exit_test.go │ ├── login_test.go │ ├── active_test.go │ ├── options.go │ ├── exit.go │ ├── deliver_test.go │ ├── active.go │ ├── report.go │ ├── submit_test.go │ ├── config.go │ ├── header.go │ ├── login.go │ ├── deliver.go │ └── submit.go └── cmpp │ ├── interface.go │ ├── terminate.go │ ├── connect_test.go │ ├── active.go │ ├── config.go │ ├── header_test.go │ ├── delivery_test.go │ ├── report.go │ ├── options.go │ ├── submit_test.go │ ├── header.go │ ├── connect.go │ ├── delivery.go │ └── submit.go ├── comm ├── cycle_sequence_test.go ├── tool_test.go ├── cycle_sequence.go ├── bcd_sequence_test.go ├── container │ └── container.go ├── snowflake │ └── snowflake.go ├── snowflake32 │ └── snowflake32.go ├── bcd_sequence.go ├── tool.go ├── logging │ └── logger.go ├── yml_config │ └── yml_config.go ├── tlv_test.go └── tlv.go ├── README.md ├── .gitignore ├── config ├── cmpp.yaml └── smgp.yaml ├── go.mod └── LICENSE /cmd/ismg/sgip/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /shells/pprof_heap.sh: -------------------------------------------------------------------------------- 1 | go tool pprof -http=:9003 http://localhost:9001/debug/pprof/heap 2 | -------------------------------------------------------------------------------- /shells/pprof_profile.sh: -------------------------------------------------------------------------------- 1 | go tool pprof -http=:9002 http://localhost:9001/debug/pprof/profile 2 | -------------------------------------------------------------------------------- /shells/pprof_goroutine.sh: -------------------------------------------------------------------------------- 1 | go tool pprof -http=:9004 http://localhost:9001/debug/pprof/goroutine 2 | -------------------------------------------------------------------------------- /codec/smgp/options_test.go: -------------------------------------------------------------------------------- 1 | package smgp 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestMtOptions(t *testing.T) { 9 | var ti time.Time 10 | t.Logf("%v", ti) 11 | t.Logf("%v", ti.Year()) 12 | 13 | var d time.Duration 14 | t.Logf("%v", d) 15 | } 16 | -------------------------------------------------------------------------------- /comm/cycle_sequence_test.go: -------------------------------------------------------------------------------- 1 | package comm 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var seq = NewCycleSequence(1, 1) 8 | 9 | func BenchmarkCycleSequence_NextVal(b *testing.B) { 10 | b.ResetTimer() 11 | for i := 0; i < b.N; i++ { 12 | seq.NextVal() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /cmd/ismg/cmpp/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | go clean 4 | go mod tidy 5 | # 编译 6 | go build -trimpath -o cmpp.ismg 7 | go test -v server_test.go -test.run TestClient -c 8 | 9 | mkdir -p ~/cmpp 10 | mv cmpp.ismg ~/cmpp/ 11 | mv main.test ~/cmpp/ 12 | cp start.sh ~/cmpp/ 13 | cp -rf ../../../config ~/cmpp/ 14 | -------------------------------------------------------------------------------- /cmd/ismg/smgp/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | go clean 4 | go mod tidy 5 | # 编译 6 | go build -trimpath -o smgp.ismg 7 | go test -v server_test.go -test.run TestClient -c 8 | 9 | mkdir -p ~/smgp 10 | mv smgp.ismg ~/smgp/ 11 | mv main.test ~/smgp/ 12 | cp start.sh ~/smgp/ 13 | cp -rf ../../../config ~/smgp/ 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sms-vgateway 2 | 3 | #### 介绍 4 | 短信虚拟网关。 5 | 基于golang网络编程库 gnet开发。 6 | 支持 SMPP, CMPP, SGIP, SMGP等协议。 7 | 8 | #### 软件架构 9 | 软件架构说明 10 | 11 | 12 | #### 安装教程 13 | 14 | 15 | 16 | #### 使用说明 17 | 18 | 19 | 20 | #### 参与贡献 21 | 22 | 1. Fork 本仓库 23 | 2. 新建 Feat_xxx 分支 24 | 3. 提交代码 25 | 4. 新建 Pull Request 26 | 27 | 28 | #### 特技 29 | -------------------------------------------------------------------------------- /codec/cmpp/interface.go: -------------------------------------------------------------------------------- 1 | package cmpp 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Codec interface { 8 | Encode() []byte 9 | Decode(header *MessageHeader, frame []byte) error 10 | } 11 | 12 | type Pdu interface { 13 | Codec 14 | fmt.Stringer 15 | ToResponse(code uint32) interface{} 16 | } 17 | 18 | type Sequence32 interface { 19 | NextVal() int32 20 | } 21 | 22 | type Sequence64 interface { 23 | NextVal() int64 24 | } 25 | -------------------------------------------------------------------------------- /codec/cmpp/terminate.go: -------------------------------------------------------------------------------- 1 | package cmpp 2 | 3 | func NewTerminate() *MessageHeader { 4 | t := MessageHeader{} 5 | t.TotalLength = 12 6 | t.SequenceId = uint32(Seq32.NextVal()) 7 | t.CommandId = CMPP_TERMINATE 8 | return &t 9 | } 10 | 11 | func NewTerminateResp(seq uint32) *MessageHeader { 12 | t := MessageHeader{} 13 | t.TotalLength = 12 14 | t.SequenceId = seq 15 | t.CommandId = CMPP_TERMINATE_RESP 16 | return &t 17 | } 18 | -------------------------------------------------------------------------------- /codec/smgp/interface.go: -------------------------------------------------------------------------------- 1 | package smgp 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Codec interface { 8 | Encode() []byte 9 | Decode(header *MessageHeader, frame []byte) error 10 | } 11 | 12 | type Pdu interface { 13 | Codec 14 | fmt.Stringer 15 | ToResponse(code uint32) interface{} 16 | } 17 | 18 | type Sequence32 interface { 19 | NextVal() int32 20 | } 21 | 22 | type Sequence80 interface { 23 | NextVal() []byte 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage comm, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | .idea 18 | test.result.txt 19 | .DS_Store 20 | *.pid 21 | *.ismg 22 | *.tar 23 | *.gz 24 | 25 | 26 | -------------------------------------------------------------------------------- /codec/smgp/report_test.go: -------------------------------------------------------------------------------- 1 | package smgp 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestReport(t *testing.T) { 10 | id := Seq80.NextVal() 11 | rpt := NewReport(id) 12 | t.Logf("rpt: %s", rpt) 13 | data := rpt.Encode() 14 | assert.True(t, len(data) == RptLen) 15 | t.Logf("value: %x", data) 16 | 17 | rpt2 := &Report{} 18 | err := rpt2.Decode(data) 19 | assert.True(t, err == nil) 20 | t.Logf("rpt2: %s", rpt2) 21 | } 22 | -------------------------------------------------------------------------------- /cmd/ismg/cmpp/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | pkill cmpp.ismg 4 | pkill cmpp.ismg 5 | 6 | # -1=debug, 0=info, 1=warn..., default to info 7 | export GNET_LOGGING_LEVEL=0 8 | export GNET_LOGGING_FILE="/Users/huangzhonghui/logs/cmpp.log" 9 | mkdir -p /Users/huangzhonghui/logs 10 | 11 | # optional args --port 1234 --multicore=false 12 | # default args --port 9000 --multicore=true 13 | nohup ./cmpp.ismg --port 9000 --multicore=true >panic.log 2>&1 & 14 | 15 | sleep 3 16 | tail -10 /Users/huangzhonghui/logs/cmpp.log 17 | sleep 7 18 | top -pid "$(cat cmpp.pid)" 19 | -------------------------------------------------------------------------------- /cmd/ismg/smgp/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | pkill smgp.ismg 4 | pkill smgp.ismg 5 | 6 | # -1=debug, 0=info, 1=warn..., default to info 7 | export GNET_LOGGING_LEVEL=0 8 | export GNET_LOGGING_FILE="/Users/huangzhonghui/logs/smgp.log" 9 | 10 | mkdir -p /Users/huangzhonghui/logs 11 | 12 | # optional args --port 1234 --multicore=false 13 | # default args --port 9100 --multicore=true 14 | nohup ./smgp.ismg --port 9100 --multicore=true >panic.log 2>&1 & 15 | 16 | sleep 3 17 | tail -10 /Users/huangzhonghui/logs/smgp.log 18 | sleep 7 19 | top -pid "$(cat smgp.pid)" 20 | -------------------------------------------------------------------------------- /codec/smgp/exit_test.go: -------------------------------------------------------------------------------- 1 | package smgp 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestExit(t *testing.T) { 8 | exit := NewExit() 9 | t.Logf("%T : %s", exit, exit) 10 | 11 | data := exit.Encode() 12 | t.Logf("%T : %x", data, data) 13 | 14 | h := &MessageHeader{} 15 | _ = h.Decode(data) 16 | t.Logf("%T : %s", h, h) 17 | 18 | e2 := &Exit{} 19 | _ = e2.Decode(h, data) 20 | t.Logf("%T : %s", e2, e2) 21 | 22 | resp := exit.ToResponse(0).(*ExitResp) 23 | t.Logf("%T : %s", resp, resp) 24 | 25 | data = resp.Encode() 26 | t.Logf("%T : %x", data, data) 27 | _ = h.Decode(data) 28 | 29 | resp2 := &ExitResp{} 30 | _ = resp2.Decode(h, data) 31 | t.Logf("%T : %s", resp2, resp2) 32 | } 33 | -------------------------------------------------------------------------------- /cmd/ismg/smgp/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | 7 | "github.com/aaronwong1989/gosms/codec/smgp" 8 | "github.com/aaronwong1989/gosms/comm" 9 | "github.com/aaronwong1989/gosms/comm/logging" 10 | "github.com/aaronwong1989/gosms/comm/yml_config" 11 | ) 12 | 13 | var log = logging.GetDefaultLogger() 14 | 15 | func main() { 16 | rand.Seed(time.Now().Unix()) // 随机种子 17 | smgp.Conf = yml_config.CreateYamlFactory("smgp.yaml") 18 | dc := smgp.Conf.GetInt("data-center-id") 19 | wk := smgp.Conf.GetInt("worker-id") 20 | smgwId := smgp.Conf.GetString("smgw-id") 21 | smgp.Seq32 = comm.NewCycleSequence(int32(dc), int32(wk)) 22 | smgp.Seq80 = comm.NewBcdSequence(smgwId) 23 | StartServer() 24 | } 25 | -------------------------------------------------------------------------------- /comm/tool_test.go: -------------------------------------------------------------------------------- 1 | package comm 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "golang.org/x/text/encoding/simplifiedchinese" 8 | "golang.org/x/text/encoding/unicode" 9 | ) 10 | 11 | func TestUcs2Encode(t *testing.T) { 12 | var s = "【中原银行】您尾号0045的0054于10月23日01:57在电费缴费100,000,000元,可用余额为23,456.00元。客服电话:95186。" 13 | t.Logf("%s", s) 14 | t.Logf("%x", s) 15 | 16 | s, _ = unicode.UTF8.NewEncoder().String(s) 17 | t.Logf("%s", s) 18 | t.Logf("%x", s) 19 | 20 | t.Logf("%x", Ucs2Encode(s)) 21 | 22 | s, _ = simplifiedchinese.GB18030.NewEncoder().String(s) 23 | t.Logf("%x", s) 24 | s, _ = simplifiedchinese.GB18030.NewDecoder().String(s) 25 | t.Logf("%s", s) 26 | t.Logf("%x", s) 27 | } 28 | 29 | func TestDiceCheck(t *testing.T) { 30 | assert.True(t, DiceCheck(0.99)) 31 | } 32 | -------------------------------------------------------------------------------- /cmd/ismg/cmpp/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | 7 | "github.com/aaronwong1989/gosms/codec/cmpp" 8 | "github.com/aaronwong1989/gosms/comm" 9 | "github.com/aaronwong1989/gosms/comm/logging" 10 | "github.com/aaronwong1989/gosms/comm/snowflake" 11 | "github.com/aaronwong1989/gosms/comm/yml_config" 12 | ) 13 | 14 | var log = logging.GetDefaultLogger() 15 | 16 | func main() { 17 | rand.Seed(time.Now().Unix()) // 随机种子 18 | cmpp.Conf = yml_config.CreateYamlFactory("cmpp.yaml") 19 | dc := cmpp.Conf.GetInt("data-center-id") 20 | wk := cmpp.Conf.GetInt("worker-id") 21 | cmpp.Seq32 = comm.NewCycleSequence(int32(dc), int32(wk)) 22 | cmpp.Seq64 = snowflake.NewSnowflake(int64(dc), int64(wk)) 23 | cmpp.ReportSeq = comm.NewCycleSequence(int32(dc), int32(wk)) 24 | StartServer() 25 | } 26 | -------------------------------------------------------------------------------- /cmd/ismg/cmpp/make.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | go clean 4 | go mod tidy 5 | 6 | # 如果你想在Windows 32位系统下运行 7 | # CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -trimpath -o cmpp.ismg 8 | 9 | # 如果你想在Windows 64位系统下运行 10 | # CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath -o cmpp.ismg 11 | 12 | # 如果你想在Linux 32位系统下运行 13 | # CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -trimpath -o cmpp.ismg 14 | 15 | # 如果你想在Linux 64位系统下运行 16 | # CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -o cmpp.ismg 17 | 18 | # 如果你想在Linux arm64系统下运行 19 | # CGO_ENABLED=0 GOOS=linux GOARM=7 GOARCH=arm64 go build -trimpath -o cmpp.ismg 20 | 21 | # 如果你想在 本机环境 运行 22 | go build -trimpath -o cmpp.ismg 23 | 24 | # 制作软件发布包 25 | chmod +x cmpp.ismg 26 | chmod +x start.sh 27 | cp -rf ../../../config ./ 28 | tar -zcvf cmpp.ismg.tar.gz cmpp.ismg start.sh config 29 | rm -rf ./config -------------------------------------------------------------------------------- /cmd/ismg/smgp/make.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | go clean 4 | go mod tidy 5 | 6 | # 如果你想在Windows 32位系统下运行 7 | # CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -trimpath -o smgp.ismg 8 | 9 | # 如果你想在Windows 64位系统下运行 10 | # CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath -o smgp.ismg 11 | 12 | # 如果你想在Linux 32位系统下运行 13 | # CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -trimpath -o smgp.ismg 14 | 15 | # 如果你想在Linux 64位系统下运行 16 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -o smgp.ismg 17 | 18 | # 如果你想在Linux arm64系统下运行 19 | # CGO_ENABLED=0 GOOS=linux GOARM=7 GOARCH=arm64 go build -trimpath -o smgp.ismg 20 | 21 | # 如果你想在 本机环境 运行 22 | # go build -trimpath -o smgp.ismg 23 | 24 | # 制作软件发布包 25 | chmod +x smgp.ismg 26 | chmod +x start.sh 27 | cp -rf ../../../config ./ 28 | tar -zcvf smgp.ismg.tar.gz smgp.ismg smgp.yaml start.sh 29 | rm -rf ./config 30 | -------------------------------------------------------------------------------- /codec/smgp/login_test.go: -------------------------------------------------------------------------------- 1 | package smgp 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestLogin_Decode(t *testing.T) { 10 | var i = 100 11 | for i > 0 { 12 | lo := NewLogin() 13 | t.Logf("login : %s", lo) 14 | assert.True(t, lo.clientID == Conf.GetString("client-id")) 15 | resp := lo.ToResponse(0).(*LoginResp) 16 | t.Logf("resp : %s", resp) 17 | assert.True(t, lo.clientID == Conf.GetString("client-id")) 18 | 19 | dt1 := lo.Encode() 20 | dt2 := resp.Encode() 21 | assert.True(t, len(dt1) == LoginLen) 22 | assert.True(t, len(dt2) == LoginRespLen) 23 | 24 | err := lo.Decode(lo.MessageHeader, dt1[12:]) 25 | assert.True(t, err == nil) 26 | t.Logf("loginDec: %s, err: %s", lo, err) 27 | err = resp.Decode(resp.MessageHeader, dt2[12:]) 28 | assert.True(t, err == nil) 29 | t.Logf("respDec : %s, err: %s", resp, err) 30 | i-- 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /config/cmpp.yaml: -------------------------------------------------------------------------------- 1 | ### 网关参数 ### 2 | # 即SourceAddr,目前仅支持模拟单一值 3 | source-addr: "123456" 4 | shared-secret: "shared secret" 5 | # 是否校验登录,如果登录如法验证通过,设置未false 6 | auth-check: false 7 | # 见CMPP协议,48表示3.0 即 0x30 = 0011 0000 8 | version: 32 9 | # 最大连接数 10 | max-cons: 10 11 | # 心跳报文发送间隔 12 | active-test-duration: 60s 13 | # 多节点部署时使用,datacenter-id 取值 [0,3] 14 | datacenter-id: 1 15 | # 多节点部署时使用,worker-id 取值 [0,8] 16 | worker-id: 1 17 | # 接收窗口大小 18 | receive-window-size: 512 19 | # 处理消息的任务线程池大小 20 | max-pool-size: 2048 21 | 22 | ### 以下为MT发送相关参数 ### 23 | sms-display-no: 95566 24 | need-report: 1 25 | default-msg-level: 9 26 | service-id: myService 27 | fee-user-type: 2 28 | fee-terminal-type: 29 | fee-terminal-id: 30 | fee-type: 05 31 | fee-code: free 32 | link-id: 33 | # 短信默认有效期,超过下面配置时长后,如果消息未发送,则不再发送 34 | default-valid-duration: 2h 35 | 36 | ### 以下是模拟网关运行情况的参数 ### 37 | # 成功率: 38 | success-rate: 0.95 39 | # Mt响应的最大与最小时间,状态报告在fix-report-resp-ms后发送 40 | min-submit-resp-ms: 1 41 | max-submit-resp-ms: 3 42 | fix-report-resp-ms: 5 -------------------------------------------------------------------------------- /codec/smgp/active_test.go: -------------------------------------------------------------------------------- 1 | package smgp 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/aaronwong1989/gosms/comm" 7 | "github.com/aaronwong1989/gosms/comm/yml_config" 8 | ) 9 | 10 | func init() { 11 | Conf = yml_config.CreateYamlFactory("smgp.yaml") 12 | dc := Conf.GetInt("data-center-id") 13 | wk := Conf.GetInt("worker-id") 14 | smgwId := Conf.GetString("smgw-id") 15 | Seq32 = comm.NewCycleSequence(int32(dc), int32(wk)) 16 | Seq80 = comm.NewBcdSequence(smgwId) 17 | } 18 | 19 | func TestActiveTest(t *testing.T) { 20 | at := NewActiveTest() 21 | t.Logf("%T : %s", at, at) 22 | 23 | data := at.Encode() 24 | t.Logf("%T : %x", data, data) 25 | 26 | h := &MessageHeader{} 27 | _ = h.Decode(data) 28 | t.Logf("%T : %s", h, h) 29 | 30 | at2 := &ActiveTest{} 31 | _ = at2.Decode(h, data) 32 | t.Logf("%T : %s", at2, at2) 33 | 34 | resp := at.ToResponse(0).(*ActiveTestResp) 35 | t.Logf("%T : %s", resp, resp) 36 | 37 | data = resp.Encode() 38 | t.Logf("%T : %x", data, data) 39 | _ = h.Decode(data) 40 | 41 | resp2 := &ActiveTestResp{} 42 | _ = resp2.Decode(h, data) 43 | t.Logf("%T : %s", resp2, resp2) 44 | } 45 | -------------------------------------------------------------------------------- /config/smgp.yaml: -------------------------------------------------------------------------------- 1 | ### 网关参数 ### 2 | # 目前仅支持模拟单一值 3 | client-id: "12345678" 4 | shared-secret: "shared secret" 5 | # 是否校验登录,如果登录如法验证通过,设置未false 6 | auth-check: false 7 | # 见SMGP协议,48表示3.0 即 0x30 = 0011 0000;19表示1.3 即 0x13 = 0001 0011;32表示2.0 即 0x20 = 0010 0000 8 | version: 48 9 | # 最大连接数 10 | max-cons: 10 11 | # 心跳报文发送间隔 12 | active-test-duration: 60s 13 | # 多节点部署时使用,datacenter-id 取值 [0,3] 14 | datacenter-id: 1 15 | # 多节点部署时使用,worker-id 取值 [0,8] 16 | worker-id: 1 17 | # SMGW代码:3字节(BCD 码,取值 6位十进制数) 18 | smgw-id: 100001 19 | # 接收窗口大小 20 | receive-window-size: 512 21 | # 处理消息的任务线程池大小 22 | max-pool-size: 2048 23 | 24 | ### 以下为MT发送相关参数 ### 25 | sms-display-no: 95566 26 | need-report: 1 27 | # 消息优先级 0-3 28 | priority: 3 29 | service-id: myService 30 | fee-type: 05 31 | fee-code: free 32 | charge-term-id: 95566 33 | fixed-fee: 34 | link-id: 35 | # 短信默认有效期,超过下面配置时长后,如果消息未发送,则不再发送 36 | default-valid-duration: 2h 37 | 38 | ### 以下是模拟网关运行情况的参数 ### 39 | # 成功率: 40 | # 95 表示 95% 41 | # 99 表示 99% 42 | # 999 表示 99.9% 43 | success-rate: 965 44 | # Mt响应的最大与最小时间,状态报告在fix-report-resp-ms后发送 45 | min-submit-resp-ms: 1 46 | max-submit-resp-ms: 3 47 | fix-report-resp-ms: 5 -------------------------------------------------------------------------------- /comm/cycle_sequence.go: -------------------------------------------------------------------------------- 1 | package comm 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // CycleSequence 生成可循环使用的序号 9 | // 构成为: 0 | datacenter 2 bit | worker 3 bit| sequence 26 bit 10 | // 最大支持32个节点,单节点2^26以内不会重复(67,108,864) 11 | type CycleSequence struct { 12 | sync.Mutex // 锁 13 | datacenter int32 // 数据中心机房id, 取值范围范围:0-4 14 | worker int32 // 工作节点, 取值范围范围:0-8 15 | sequence int32 // 序列号 26bit 16 | } 17 | 18 | const ( 19 | sequenceMask = int32(0x03ffffff) // 最大值为26个1 20 | workerBits = uint(3) // 机器id所占位数 21 | sequenceBits = uint(28) // 序列所占的位数 22 | workerShift = sequenceBits // 机器id左移位数 23 | datacenterShift = sequenceBits + workerBits // 数据中心id左移位数 24 | ) 25 | 26 | // NewCycleSequence d for datacenter-id, w for worker-id 27 | func NewCycleSequence(d int32, w int32) *CycleSequence { 28 | return &CycleSequence{datacenter: d, worker: w} 29 | } 30 | 31 | func (s *CycleSequence) NextVal() int32 { 32 | s.Lock() 33 | defer s.Unlock() 34 | s.sequence = (s.sequence + 1) & sequenceMask 35 | r := (s.datacenter << datacenterShift) | (s.worker << workerShift) | (s.sequence) 36 | return r 37 | } 38 | 39 | func (s *CycleSequence) String() string { 40 | return fmt.Sprintf("%d:%d:%d", s.datacenter, s.worker, s.sequence) 41 | } 42 | -------------------------------------------------------------------------------- /comm/bcd_sequence_test.go: -------------------------------------------------------------------------------- 1 | package comm 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func Test_BcdToString(t *testing.T) { 9 | bcd := []byte{0x01, 0x23, 0x45, 0x67, 0x8a} 10 | str := BcdToString(bcd) 11 | 12 | t.Logf("bcd: %x, len: %d", bcd, len(bcd)) 13 | t.Logf("str: %s", str) 14 | } 15 | 16 | func Test_StoBcd(t *testing.T) { 17 | str := "012345678a" 18 | bcd := StoBcd(str) 19 | t.Logf("str: %s", str) 20 | t.Logf("bcd: %x, len: %d", bcd, len(bcd)) 21 | } 22 | 23 | var bcdSeq = NewBcdSequence("000001") 24 | 25 | func BenchmarkBcdSequence_NextSeq(b *testing.B) { 26 | b.ResetTimer() 27 | for i := 0; i < b.N; i++ { 28 | bcdSeq.NextVal() 29 | } 30 | } 31 | 32 | func BenchmarkBcdSequence_BcdToString(b *testing.B) { 33 | b.ResetTimer() 34 | for i := 0; i < b.N; i++ { 35 | BcdToString(bcdSeq.NextVal()) 36 | } 37 | } 38 | 39 | func BenchmarkBcdSequence_FmtString(b *testing.B) { 40 | b.ResetTimer() 41 | for i := 0; i < b.N; i++ { 42 | _ = fmt.Sprintf("%x", bcdSeq.NextVal()) 43 | } 44 | } 45 | 46 | func BenchmarkBcdSequence_BcdToStringParallel(b *testing.B) { 47 | b.RunParallel(func(pb *testing.PB) { 48 | for pb.Next() { 49 | BcdToString(bcdSeq.NextVal()) 50 | } 51 | }) 52 | } 53 | 54 | func BenchmarkBcdSequence_FmtStringParallel(b *testing.B) { 55 | b.RunParallel(func(pb *testing.PB) { 56 | for pb.Next() { 57 | _ = fmt.Sprintf("%x", bcdSeq.NextVal()) 58 | } 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aaronwong1989/gosms 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/fsnotify/fsnotify v1.5.4 7 | github.com/panjf2000/ants/v2 v2.4.8 8 | github.com/panjf2000/gnet/v2 v2.1.0 9 | github.com/spf13/viper v1.12.0 10 | github.com/stretchr/testify v1.7.2 11 | go.uber.org/zap v1.21.0 12 | golang.org/x/text v0.3.7 13 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 14 | ) 15 | 16 | require ( 17 | github.com/davecgh/go-spew v1.1.1 // indirect 18 | github.com/hashicorp/hcl v1.0.0 // indirect 19 | github.com/magiconair/properties v1.8.6 // indirect 20 | github.com/mitchellh/mapstructure v1.5.0 // indirect 21 | github.com/pelletier/go-toml v1.9.5 // indirect 22 | github.com/pelletier/go-toml/v2 v2.0.1 // indirect 23 | github.com/pmezard/go-difflib v1.0.0 // indirect 24 | github.com/rogpeppe/go-internal v1.8.0 // indirect 25 | github.com/spf13/afero v1.8.2 // indirect 26 | github.com/spf13/cast v1.5.0 // indirect 27 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 28 | github.com/spf13/pflag v1.0.5 // indirect 29 | github.com/subosito/gotenv v1.3.0 // indirect 30 | go.uber.org/atomic v1.9.0 // indirect 31 | go.uber.org/multierr v1.7.0 // indirect 32 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect 33 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 34 | gopkg.in/ini.v1 v1.66.4 // indirect 35 | gopkg.in/yaml.v2 v2.4.0 // indirect 36 | gopkg.in/yaml.v3 v3.0.1 // indirect 37 | ) 38 | -------------------------------------------------------------------------------- /comm/container/container.go: -------------------------------------------------------------------------------- 1 | package container 2 | 3 | import ( 4 | "strings" 5 | "sync" 6 | 7 | "github.com/aaronwong1989/gosms/comm/logging" 8 | ) 9 | 10 | var log = logging.GetDefaultLogger() 11 | 12 | // 定义一个全局键值对存储容器 13 | var sMap sync.Map 14 | 15 | // CreateContainersFactory 创建一个容器工厂 16 | func CreateContainersFactory() *containers { 17 | return &containers{} 18 | } 19 | 20 | // 定义一个容器结构体 21 | type containers struct { 22 | } 23 | 24 | // Set 1.以键值对的形式将代码注册到容器 25 | func (c *containers) Set(key string, value interface{}) (res bool) { 26 | 27 | if _, exists := c.KeyIsExists(key); exists == false { 28 | sMap.Store(key, value) 29 | res = true 30 | } else { 31 | log.Warnf("容器中已存在键:" + key) 32 | } 33 | return 34 | } 35 | 36 | // Delete 2.删除 37 | func (c *containers) Delete(key string) { 38 | sMap.Delete(key) 39 | } 40 | 41 | // Get 3.传递键,从容器获取值 42 | func (c *containers) Get(key string) interface{} { 43 | if value, exists := c.KeyIsExists(key); exists { 44 | return value 45 | } 46 | return nil 47 | } 48 | 49 | // KeyIsExists 4. 判断键是否被注册 50 | func (c *containers) KeyIsExists(key string) (interface{}, bool) { 51 | return sMap.Load(key) 52 | } 53 | 54 | // FuzzyDelete 按照键的前缀模糊删除容器中注册的内容 55 | func (c *containers) FuzzyDelete(keyPre string) { 56 | sMap.Range(func(key, value interface{}) bool { 57 | if keyName, ok := key.(string); ok { 58 | if strings.HasPrefix(keyName, keyPre) { 59 | sMap.Delete(keyName) 60 | } 61 | } 62 | return true 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /codec/smgp/options.go: -------------------------------------------------------------------------------- 1 | package smgp 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/aaronwong1989/gosms/comm" 7 | ) 8 | 9 | type MtOptions struct { 10 | NeedReport byte // SP是否要求返回状态报告 11 | Priority byte // 短消息发送优先级,0-3 12 | ServiceID string // 业务代码 13 | AtTime time.Time // 短消息定时发送时间 14 | ValidDuration time.Duration // 短消息有效时长 15 | SrcTermID string // 会拼接到配置文件的sms-display-no后面 16 | } 17 | 18 | func (s *Submit) SetOptions(options MtOptions) { 19 | s.needReport = byte(Conf.GetInt("need-report")) 20 | // 有点小bug,不能通过传参的方式设置未变量的"零值" 21 | if options.NeedReport != 0 { 22 | s.needReport = options.NeedReport 23 | } 24 | 25 | s.priority = byte(Conf.GetInt("Priority")) 26 | // 有点小bug,不能通过传参的方式设置未变量的"零值" 27 | if options.Priority != 0 { 28 | s.priority = options.Priority 29 | } 30 | 31 | s.serviceID = Conf.GetString("service-id") 32 | if options.ServiceID != "" { 33 | s.serviceID = options.ServiceID 34 | } 35 | 36 | if options.AtTime.Year() != 1 { 37 | s.atTime = comm.FormatTime(options.AtTime) 38 | } else { 39 | s.atTime = comm.FormatTime(time.Now()) 40 | } 41 | 42 | vt := time.Now() 43 | if options.ValidDuration != 0 { 44 | vt.Add(options.ValidDuration) 45 | } else { 46 | vt.Add(Conf.GetDuration("default-valid-duration")) 47 | } 48 | s.validTime = comm.FormatTime(vt) 49 | 50 | s.srcTermID = Conf.GetString("sms-display-no") 51 | if options.SrcTermID != "" { 52 | s.srcTermID += options.SrcTermID 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /codec/cmpp/connect_test.go: -------------------------------------------------------------------------------- 1 | package cmpp 2 | 3 | import ( 4 | "crypto/md5" 5 | "fmt" 6 | "strconv" 7 | "testing" 8 | "time" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestCmppConnect_Encode(t *testing.T) { 14 | header := MessageHeader{ 15 | TotalLength: 39, 16 | CommandId: CMPP_CONNECT, 17 | SequenceId: uint32(Seq32.NextVal()), 18 | } 19 | 20 | connect := &Connect{MessageHeader: &header} 21 | connect.sourceAddr = "123456" 22 | connect.version = 0x20 23 | connect.timestamp = uint32(1001235010) 24 | md5str := reqAuthMd5(connect) 25 | connect.authenticatorSource = md5str[:] 26 | t.Logf("%s", connect) 27 | 28 | frame := connect.Encode() 29 | t.Logf("Connect: %x", frame) 30 | assert.Equal(t, uint32(0), connect.Check()) 31 | 32 | resp := connect.ToResponse(0).(*ConnectResp) 33 | t.Logf("Connect: %v", resp) 34 | t.Logf("ConnectResp: %x", resp.Encode()) 35 | } 36 | 37 | func TestTime(t *testing.T) { 38 | ti := time.Now() 39 | // 2016-01-02 15:04:05 40 | s := ti.Format("0102150405") 41 | ts, _ := strconv.ParseUint(s, 10, 32) 42 | t.Logf("%T,%v", ts, ts) 43 | ts32 := uint32(ts) 44 | t.Logf("%T,%v", ts32, ts32) 45 | } 46 | 47 | func TestAuthStr(t *testing.T) { 48 | ti := time.Now() 49 | s := ti.Format("0102150405") 50 | ts, _ := strconv.ParseUint(s, 10, 32) 51 | t.Logf("%T, %v, %s", ts, ts, fmt.Sprintf("%010d", ts)) 52 | 53 | authDt := make([]byte, 0, 64) 54 | authDt = append(authDt, "901234"...) 55 | authDt = append(authDt, 0, 0, 0, 0, 0, 0, 0, 0, 0) 56 | authDt = append(authDt, "1234"...) 57 | authDt = append(authDt, "0706104024"...) 58 | authMd5 := md5.Sum(authDt) 59 | 60 | t.Logf("%x", authMd5) 61 | } 62 | -------------------------------------------------------------------------------- /codec/smgp/exit.go: -------------------------------------------------------------------------------- 1 | package smgp 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Exit MessageHeader 8 | type ExitResp MessageHeader 9 | 10 | func NewExit() *Exit { 11 | at := &Exit{PacketLength: HeadLength, RequestId: CmdExit, SequenceId: uint32(Seq32.NextVal())} 12 | return at 13 | } 14 | 15 | func NewExitResp(seq uint32) *ExitResp { 16 | at := &ExitResp{PacketLength: HeadLength, RequestId: CmdExitResp, SequenceId: seq} 17 | return at 18 | } 19 | 20 | func (at *Exit) Encode() []byte { 21 | return (*MessageHeader)(at).Encode() 22 | } 23 | 24 | func (at *Exit) Decode(header *MessageHeader, _ []byte) error { 25 | at.PacketLength = header.PacketLength 26 | at.RequestId = header.RequestId 27 | at.SequenceId = header.SequenceId 28 | return nil 29 | } 30 | 31 | func (at *Exit) ToResponse(_ uint32) interface{} { 32 | resp := ExitResp{} 33 | resp.PacketLength = at.PacketLength 34 | resp.RequestId = CmdExitResp 35 | resp.SequenceId = at.SequenceId 36 | return &resp 37 | } 38 | 39 | func (at *Exit) String() string { 40 | return fmt.Sprintf("{ PacketLength: %d, RequestId: %s, SequenceId: %d }", at.PacketLength, CommandMap[CmdExit], at.SequenceId) 41 | } 42 | 43 | func (resp *ExitResp) Encode() []byte { 44 | return (*MessageHeader)(resp).Encode() 45 | } 46 | 47 | func (resp *ExitResp) Decode(header *MessageHeader, _ []byte) error { 48 | resp.PacketLength = header.PacketLength 49 | resp.RequestId = header.RequestId 50 | resp.SequenceId = header.SequenceId 51 | return nil 52 | } 53 | 54 | func (resp *ExitResp) String() string { 55 | return fmt.Sprintf("{ PacketLength: %d, RequestId: %s, SequenceId: %d}", resp.PacketLength, CommandMap[CmdExitResp], resp.SequenceId) 56 | } 57 | -------------------------------------------------------------------------------- /codec/smgp/deliver_test.go: -------------------------------------------------------------------------------- 1 | package smgp 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestDeliver_Decode(t *testing.T) { 10 | dlv := NewDeliver("123", "95535", "TD:123456") 11 | t.Logf("dlv: %s", dlv) 12 | testDeliver(t, dlv) 13 | } 14 | 15 | func TestDeliver_ReportDecode(t *testing.T) { 16 | mts := NewSubmit([]string{"17011113333"}, "hello world,世界", MtOptions{}) 17 | mt := mts[0] 18 | msp := mt.ToResponse(0).(*SubmitResp) 19 | rpt := NewDeliveryReport(mt, msp.msgId) 20 | t.Logf("dlv: %s", rpt) 21 | testDeliver(t, rpt) 22 | } 23 | 24 | func testDeliver(t *testing.T, dlv *Deliver) { 25 | resp := dlv.ToResponse(0).(*DeliverResp) 26 | t.Logf("resp: %s", resp) 27 | 28 | // 测试Deliver Encode 29 | dt := dlv.Encode() 30 | assert.True(t, int(dlv.PacketLength) == len(dt)) 31 | t.Logf("dlv_encode: %x", dt) 32 | // 测试Deliver Decode 33 | h := &MessageHeader{} 34 | err := h.Decode(dt) 35 | assert.True(t, err == nil) 36 | dlvDec := &Deliver{} 37 | err = dlvDec.Decode(h, dt[12:]) 38 | assert.True(t, err == nil) 39 | assert.True(t, dlvDec.MessageHeader.SequenceId == dlv.MessageHeader.SequenceId) 40 | t.Logf("dlv_decode: %s", dlvDec) 41 | 42 | // 测试DeliverResp Encode 43 | dt = resp.Encode() 44 | assert.True(t, int(resp.PacketLength) == len(dt)) 45 | t.Logf("resp_encode: %x", dt) 46 | // 测试Deliver Decode 47 | h = &MessageHeader{} 48 | err = h.Decode(dt) 49 | assert.True(t, err == nil) 50 | respDec := &DeliverResp{} 51 | err = respDec.Decode(h, dt[12:]) 52 | assert.True(t, err == nil) 53 | assert.True(t, respDec.MessageHeader.SequenceId == respDec.MessageHeader.SequenceId) 54 | t.Logf("resp_decode: %s", dlvDec) 55 | } 56 | -------------------------------------------------------------------------------- /codec/smgp/active.go: -------------------------------------------------------------------------------- 1 | package smgp 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type ActiveTest MessageHeader 8 | type ActiveTestResp MessageHeader 9 | 10 | func NewActiveTest() *ActiveTest { 11 | at := &ActiveTest{PacketLength: HeadLength, RequestId: CmdActiveTest, SequenceId: uint32(Seq32.NextVal())} 12 | return at 13 | } 14 | 15 | func NewActiveTestResp(seq uint32) *ActiveTestResp { 16 | at := &ActiveTestResp{PacketLength: HeadLength, RequestId: CmdActiveTest, SequenceId: seq} 17 | return at 18 | } 19 | 20 | func (at *ActiveTest) Encode() []byte { 21 | return (*MessageHeader)(at).Encode() 22 | } 23 | 24 | func (at *ActiveTest) Decode(header *MessageHeader, _ []byte) error { 25 | at.PacketLength = header.PacketLength 26 | at.RequestId = header.RequestId 27 | at.SequenceId = header.SequenceId 28 | return nil 29 | } 30 | 31 | func (at *ActiveTest) ToResponse(_ uint32) interface{} { 32 | resp := ActiveTestResp{} 33 | resp.PacketLength = at.PacketLength 34 | resp.RequestId = CmdActiveTestResp 35 | resp.SequenceId = at.SequenceId 36 | return &resp 37 | } 38 | 39 | func (at *ActiveTest) String() string { 40 | return fmt.Sprintf("{ PacketLength: %d, RequestId: %s, SequenceId: %d }", at.PacketLength, CommandMap[CmdActiveTest], at.SequenceId) 41 | } 42 | 43 | func (resp *ActiveTestResp) Encode() []byte { 44 | return (*MessageHeader)(resp).Encode() 45 | } 46 | 47 | func (resp *ActiveTestResp) Decode(header *MessageHeader, _ []byte) error { 48 | resp.PacketLength = header.PacketLength 49 | resp.RequestId = header.RequestId 50 | resp.SequenceId = header.SequenceId 51 | return nil 52 | } 53 | 54 | func (resp *ActiveTestResp) String() string { 55 | return fmt.Sprintf("{ PacketLength: %d, RequestId: %s, SequenceId: %d}", resp.PacketLength, CommandMap[CmdActiveTestResp], resp.SequenceId) 56 | } 57 | -------------------------------------------------------------------------------- /codec/cmpp/active.go: -------------------------------------------------------------------------------- 1 | package cmpp 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type ActiveTest struct { 8 | *MessageHeader 9 | } 10 | 11 | func NewActiveTest() *ActiveTest { 12 | header := &MessageHeader{TotalLength: HeadLength, CommandId: CMPP_ACTIVE_TEST, SequenceId: uint32(Seq32.NextVal())} 13 | return &ActiveTest{header} 14 | } 15 | 16 | func (at *ActiveTest) Encode() []byte { 17 | return at.MessageHeader.Encode() 18 | } 19 | 20 | func (at *ActiveTest) Decode(header *MessageHeader, frame []byte) error { 21 | if header == nil || header.CommandId != CMPP_ACTIVE_TEST || frame != nil { 22 | return ErrorPacket 23 | } 24 | at.MessageHeader = header 25 | return nil 26 | } 27 | 28 | func (at *ActiveTest) ToResponse(_ uint32) interface{} { 29 | header := &MessageHeader{TotalLength: HeadLength + 1, CommandId: CMPP_ACTIVE_TEST_RESP, SequenceId: at.SequenceId} 30 | atr := ActiveTestResp{MessageHeader: header, reserved: 0} 31 | return &atr 32 | } 33 | 34 | func (at *ActiveTest) String() string { 35 | return fmt.Sprintf("{ TotalLength: %d, CommandId: CMPP_ACTIVE_TEST, SequenceId: %d }", at.TotalLength, at.SequenceId) 36 | } 37 | 38 | type ActiveTestResp struct { 39 | *MessageHeader 40 | reserved byte 41 | } 42 | 43 | func (at *ActiveTestResp) Encode() []byte { 44 | return at.MessageHeader.Encode() 45 | } 46 | 47 | func (at *ActiveTestResp) Decode(header *MessageHeader, frame []byte) error { 48 | if header == nil || header.CommandId != CMPP_ACTIVE_TEST_RESP || len(frame) < (13-HeadLength) { 49 | return ErrorPacket 50 | } 51 | at.MessageHeader = header 52 | at.reserved = frame[0] 53 | return nil 54 | } 55 | 56 | func (at *ActiveTestResp) String() string { 57 | return fmt.Sprintf("{ TotalLength: %d, CommandId: CMPP_ACTIVE_TEST_RESP, SequenceId: %d, reserved: %d }", at.TotalLength, at.SequenceId, at.reserved) 58 | } 59 | -------------------------------------------------------------------------------- /codec/cmpp/config.go: -------------------------------------------------------------------------------- 1 | package cmpp 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/aaronwong1989/gosms/comm/logging" 7 | "github.com/aaronwong1989/gosms/comm/yml_config" 8 | ) 9 | 10 | var log = logging.GetDefaultLogger() 11 | var ErrorPacket = errors.New("error packet") 12 | var Conf yml_config.YmlConfig 13 | var Seq32 Sequence32 14 | var Seq64 Sequence64 15 | 16 | // type Config struct { 17 | // // 公共参数 18 | // SourceAddr string `yaml:"source-addr"` 19 | // SharedSecret string `yaml:"shared-secret"` 20 | // AuthCheck bool `yaml:"auth-check"` 21 | // Version uint8 `yaml:"version"` 22 | // MaxCons int `yaml:"max-cons"` 23 | // ActiveTestDuration time.Duration `yaml:"active-test-duration"` 24 | // DataCenterId int32 `yaml:"datacenter-id"` 25 | // WorkerId int32 `yaml:"worker-id"` 26 | // ReceiveWindowSize int `yaml:"receive-window-size"` 27 | // MaxPoolSize int `yaml:"max-pool-size"` 28 | // 29 | // // MT消息相关 30 | // RegisteredDel uint8 `yaml:"need-report"` 31 | // MsgLevel uint8 `yaml:"default-msg-level"` 32 | // FeeUsertype uint8 `yaml:"fee-user-type"` 33 | // FeeTerminalType uint8 `yaml:"c"` 34 | // SrcId string `yaml:"sms-display-no"` 35 | // ServiceId string `yaml:"service-id"` 36 | // FeeTerminalId string `yaml:"fee-terminal-id"` 37 | // FeeType string `yaml:"fee-type"` 38 | // FeeCode string `yaml:"fee-code"` 39 | // LinkID string `yaml:"link-id"` 40 | // ValidDuration time.Duration `yaml:"default-valid-duration"` 41 | // 42 | // // 模拟网关相关参数 43 | // SuccessRate int32 `yaml:"success-rate"` 44 | // MinSubmitRespMs int32 `yaml:"min-submit-resp-ms"` 45 | // MaxSubmitRespMs int32 `yaml:"max-submit-resp-ms"` 46 | // FixReportRespMs int32 `yaml:"fix-report-resp-ms"` 47 | // } 48 | -------------------------------------------------------------------------------- /codec/cmpp/header_test.go: -------------------------------------------------------------------------------- 1 | package cmpp 2 | 3 | import ( 4 | "encoding/binary" 5 | "math/rand" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/assert" 10 | 11 | "github.com/aaronwong1989/gosms/comm" 12 | "github.com/aaronwong1989/gosms/comm/snowflake" 13 | "github.com/aaronwong1989/gosms/comm/yml_config" 14 | ) 15 | 16 | func init() { 17 | rand.Seed(time.Now().Unix()) // 随机种子 18 | Conf = yml_config.CreateYamlFactory("cmpp.yaml") 19 | dc := Conf.GetInt("data-center-id") 20 | wk := Conf.GetInt("worker-id") 21 | Seq32 = comm.NewCycleSequence(int32(dc), int32(wk)) 22 | Seq64 = snowflake.NewSnowflake(int64(dc), int64(wk)) 23 | ReportSeq = comm.NewCycleSequence(int32(dc), int32(wk)) 24 | } 25 | 26 | func TestMessageHeader_Encode(t *testing.T) { 27 | header := MessageHeader{ 28 | TotalLength: 16, 29 | CommandId: CMPP_CONNECT, 30 | SequenceId: uint32(Seq32.NextVal()), 31 | } 32 | t.Logf("%s", header.String()) 33 | t.Logf("%d", header.Encode()) 34 | 35 | connect := Connect{MessageHeader: &header} 36 | 37 | connect.Encode() 38 | 39 | assert.Equal(t, int(Conf.GetInt("version"))&0xf0, 0x20) 40 | 41 | } 42 | 43 | func TestMessageHeader_Decode(t *testing.T) { 44 | frame := make([]byte, 16) 45 | binary.BigEndian.PutUint32(frame[0:4], 16) 46 | binary.BigEndian.PutUint32(frame[4:8], CMPP_CONNECT) 47 | binary.BigEndian.PutUint32(frame[8:12], 1) 48 | copy(frame[12:16], "1234") 49 | 50 | header := MessageHeader{} 51 | _ = header.Decode(frame) 52 | t.Logf("%v", header) 53 | t.Logf("%s", frame[12:16]) 54 | t.Logf("% x", frame[12:16]) 55 | } 56 | 57 | func TestStringRune(t *testing.T) { 58 | str := "中国人hello" 59 | bts := []byte(str) 60 | chars := []rune(str) 61 | 62 | t.Logf("len(str)=%d,len(bts)=%d,len(chars)=%d", len(str), len(bts), len(chars)) 63 | 64 | t.Logf("bts: %x", bts) 65 | t.Logf("chars: %c", chars) 66 | } 67 | 68 | func TestTrimStr(t *testing.T) { 69 | bts := []byte{'a', 'b', 'c', 'd', 0, 0, 0} 70 | t.Logf("%s", TrimStr(bts)) 71 | } 72 | -------------------------------------------------------------------------------- /comm/snowflake/snowflake.go: -------------------------------------------------------------------------------- 1 | package snowflake 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/panjf2000/gnet/v2/pkg/logging" 8 | ) 9 | 10 | const ( 11 | epoch = int64(1577808000000) // 设置起始时间(时间戳/毫秒):2020-01-01 00:00:00,有效期69年 12 | timestampBits = uint(41) // 时间戳占用位数 13 | datacenterBits = uint(2) // 数据中心id所占位数 14 | workerBits = uint(7) // 机器id所占位数 15 | sequenceBits = uint(12) // 序列所占的位数 16 | timestampMax = int64(-1 ^ (-1 << timestampBits)) // 时间戳最大值 17 | sequenceMask = int64(-1 ^ (-1 << sequenceBits)) // 支持的最大序列id数量 18 | workerShift = sequenceBits // 机器id左移位数 19 | datacenterShift = sequenceBits + workerBits // 数据中心id左移位数 20 | timestampShift = sequenceBits + workerBits + datacenterBits // 时间戳左移位数 21 | ) 22 | 23 | var log = logging.GetDefaultLogger() 24 | 25 | type Snowflake struct { 26 | sync.Mutex // 锁 27 | timestamp int64 // 时间戳 ,毫秒 28 | workerId int64 // 工作节点 29 | datacenterId int64 // 数据中心机房id 30 | sequence int64 // 序列号 31 | } 32 | 33 | func NewSnowflake(d int64, w int64) *Snowflake { 34 | return &Snowflake{datacenterId: d, workerId: w} 35 | } 36 | func (s *Snowflake) NextVal() int64 { 37 | s.Lock() 38 | defer s.Unlock() 39 | now := time.Now().UnixNano() / 1000000 // 转毫秒 40 | if s.timestamp == now { 41 | // 当同一时间戳(精度:毫秒)下多次生成id会增加序列号 42 | s.sequence = (s.sequence + 1) & sequenceMask 43 | if s.sequence == 0 { 44 | // 如果当前序列超出12bit长度,则需要等待下一毫秒 45 | // 下一毫秒将使用sequence:0 46 | for now <= s.timestamp { 47 | time.Sleep(time.Nanosecond) 48 | now = time.Now().UnixNano() / 1000000 49 | } 50 | } 51 | } else { 52 | // 不同时间戳(精度:毫秒)下直接使用序列号:0 53 | s.sequence = 0 54 | } 55 | t := now - epoch 56 | if t > timestampMax { 57 | log.Errorf("epoch must be between 0 and %d", timestampMax-1) 58 | return 0 59 | } 60 | s.timestamp = now 61 | r := (t << timestampShift) | (s.datacenterId << datacenterShift) | (s.workerId << workerShift) | (s.sequence) 62 | return r 63 | } 64 | -------------------------------------------------------------------------------- /codec/cmpp/delivery_test.go: -------------------------------------------------------------------------------- 1 | package cmpp 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestNewDelivery(t *testing.T) { 10 | cases := []string{ 11 | "hello world", 12 | "你好,世界。 hello world", 13 | "中华人民共和国", 14 | Poem, 15 | Poem2, 16 | } 17 | 18 | for _, msg := range cases { 19 | testcase(t, msg) 20 | } 21 | } 22 | 23 | func testcase(t *testing.T, msg string) { 24 | d := NewDelivery("17011110000", msg, "", "") 25 | t.Logf("%v", d) 26 | bts := d.Encode() 27 | t.Logf("len: %d, data: %x", len(bts), bts) 28 | assert.Equal(t, uint32(len(bts)), d.TotalLength) 29 | 30 | l := 0 31 | if len(msg) == len([]rune(msg)) { 32 | l += len(msg) 33 | } else { 34 | l += 2 * len([]rune(msg)) 35 | } 36 | if d.msgFmt == 8 && len([]rune(msg)) > 70 { 37 | l = 140 38 | } 39 | if d.msgFmt != 8 && len([]rune(msg)) > 160 { 40 | l = 160 41 | } 42 | 43 | assert.Equal(t, d.msgLength, uint8(l)) 44 | assert.Equal(t, d.destId, Conf.GetString("sms-display-no")) 45 | assert.Equal(t, d.serviceId, Conf.GetString("service-id")) 46 | } 47 | 48 | const Poem2 = "Will drink\n" + 49 | "Don't you see the water of the Yellow River coming up from the sky, rushing to the sea and never returning.\n" + 50 | "Don't you see the bright mirror of the high hall mourning white hair, like green silk in the morning and snow in the evening.\n" + 51 | "When you are happy in life, don't make the golden cup empty to the moon.\n" + 52 | "I'm born to be useful, but I'll come back after all the money is gone.\n" + 53 | "Cooking sheep and slaughtering cattle is fun, and you will have to drink 300 cups a day.\n" + 54 | "Master Cen, Dan Qiusheng, don't stop drinking.\n" + 55 | "Sing a song with you, please listen to it for me.\n" + 56 | "Bells, drums, and dishes are not expensive. I hope I'll be drunk for a long time and won't wake up.\n" + 57 | "In ancient times, saints and sages were lonely, and only drinkers kept their names.\n" + 58 | "The king of Chen used to enjoy banquets and drink ten thousand wine.\n" + 59 | "Why does the master say less money? He must sell and drink to you.\n" + 60 | "Five flower horses, thousands of gold fur, hu er will exchange wine, and sell eternal sorrow with you." 61 | -------------------------------------------------------------------------------- /comm/snowflake32/snowflake32.go: -------------------------------------------------------------------------------- 1 | package snowflake32 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // Snowflake 24小时内不会重复的雪花序号生成器 10 | // 构成为: 0 | seconds 17 bit | datacenter 2 bit | worker 3 bit| sequence 9 bit 11 | // 最大支持32个节点,单节点TPS不超过512,超过则会阻塞程序到下一秒再返回序号,仅能用于特殊场景 12 | // seconds占用17bits是因为一天86400秒占用17bits 13 | type Snowflake struct { 14 | sync.Mutex // 锁 15 | seconds int32 // 时间戳 ,截止到午夜0点的秒数 16 | datacenter int32 // 数据中心机房id, 取值范围范围:0-4 17 | worker int32 // 工作节点, 取值范围范围:0-8 18 | sequence int32 // 序列号 19 | } 20 | 21 | const ( 22 | sequenceMask = int32(0x01ff) // 最大值为9个1 23 | datacenterBits = uint(2) // 数据中心id所占位数 24 | workerBits = uint(3) // 机器id所占位数 25 | sequenceBits = uint(9) // 序列所占的位数 26 | workerShift = sequenceBits // 机器id左移位数 27 | datacenterShift = sequenceBits + workerBits // 数据中心id左移位数 28 | timestampShift = sequenceBits + workerBits + datacenterBits // 时间戳左移位数 29 | ) 30 | 31 | // NewSnowflake d for datacenter-id, w for worker-id 32 | func NewSnowflake(d int32, w int32) *Snowflake { 33 | return &Snowflake{datacenter: d, worker: w} 34 | } 35 | 36 | func (s *Snowflake) NextVal() int32 { 37 | s.Lock() 38 | defer s.Unlock() 39 | now := passedSeconds() // 获得当前秒 40 | if s.seconds == now { 41 | // 当同一时间戳(精度:秒)下次生成id会增加序列号 42 | s.sequence = (s.sequence + 1) & sequenceMask 43 | if s.sequence == 0 { 44 | // 如果当前序列超出9bit长度,则需要等待下一秒 45 | // 下一秒将使用sequence:0 46 | for now <= s.seconds { 47 | time.Sleep(time.Microsecond) 48 | now = passedSeconds() 49 | } 50 | } 51 | } else { 52 | // 不同时间戳(精度:秒)下直接使用序列号:0 53 | s.sequence = 0 54 | } 55 | s.seconds = now 56 | r := (s.seconds << timestampShift) | (s.datacenter << datacenterShift) | (s.worker << workerShift) | (s.sequence) 57 | return r 58 | } 59 | 60 | func (s *Snowflake) String() string { 61 | return fmt.Sprintf("%d:%d:%d:%d", s.seconds, s.datacenter, s.worker, s.sequence) 62 | } 63 | 64 | func passedSeconds() int32 { 65 | t := time.Now() 66 | return int32(t.Hour()*3600 + t.Minute()*60 + t.Second()) 67 | } 68 | -------------------------------------------------------------------------------- /codec/cmpp/report.go: -------------------------------------------------------------------------------- 1 | package cmpp 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | ) 7 | 8 | var ReportSeq Sequence32 9 | 10 | type Report struct { 11 | msgId uint64 // 信息标识,SP提交短信(CMPP_SUBMIT)操作时,与SP相连的ISMG产生的 Msg_Id。【8字节】 12 | stat string // 发送短信的应答结果。【7字节】 13 | submitTime string // yyMMddHHmm 【10字节】 14 | doneTime string // yyMMddHHmm 【10字节】 15 | destTerminalId string // SP 发送 CMPP_SUBMIT 消息的目标终端 【21字节】 16 | smscSequence uint32 // 取自SMSC发送状态报告的消息体中的消息标识。【4字节】 17 | } 18 | 19 | func NewReport(msgId uint64, destTerminalId string, submitTime string, doneTime string) *Report { 20 | report := &Report{msgId: msgId, submitTime: submitTime, doneTime: doneTime, destTerminalId: destTerminalId} 21 | report.smscSequence = uint32(ReportSeq.NextVal()) 22 | // 判断序号的时间戳部分 23 | switch (report.smscSequence >> 14) % 100 { 24 | case 99: 25 | report.stat = "REJECTD" 26 | case 88: 27 | report.stat = "UNKNOWN" 28 | case 77: 29 | report.stat = "ACCEPTD" 30 | case 66: 31 | report.stat = "UNDELIV" 32 | case 55: 33 | report.stat = "DELETED" 34 | case 44: 35 | report.stat = "EXPIRED" 36 | case 33: 37 | report.stat = "MA:0000" 38 | case 22: 39 | report.stat = "MB:0000" 40 | case 11: 41 | report.stat = "CA:0000" 42 | case 10: 43 | report.stat = "CB:0000" 44 | default: 45 | report.stat = "DELIVRD" 46 | } 47 | return report 48 | } 49 | 50 | func (rt *Report) Encode() []byte { 51 | frame := make([]byte, 60) 52 | binary.BigEndian.PutUint64(frame[0:8], rt.msgId) 53 | copy(frame[8:15], rt.stat) 54 | copy(frame[15:25], rt.submitTime) 55 | copy(frame[25:35], rt.doneTime) 56 | copy(frame[35:56], rt.destTerminalId) 57 | binary.BigEndian.PutUint32(frame[56:60], rt.smscSequence) 58 | return frame 59 | } 60 | 61 | func (rt *Report) Decode(frame []byte) error { 62 | if len(frame) < 60 { 63 | return ErrorPacket 64 | } 65 | rt.msgId = binary.BigEndian.Uint64(frame[0:8]) 66 | rt.stat = TrimStr(frame[8:15]) 67 | rt.submitTime = TrimStr(frame[15:25]) 68 | rt.doneTime = TrimStr(frame[25:35]) 69 | rt.destTerminalId = TrimStr(frame[35:56]) 70 | rt.smscSequence = binary.BigEndian.Uint32(frame[56:60]) 71 | return nil 72 | } 73 | 74 | func (rt *Report) String() string { 75 | return fmt.Sprintf("{ msgId: %d, stat: %s, submitTime: %s, doneTime: %s, destTerminalId: %s, smscSequence: %d }", 76 | rt.msgId, rt.stat, rt.submitTime, rt.doneTime, rt.destTerminalId, rt.smscSequence) 77 | } 78 | -------------------------------------------------------------------------------- /comm/bcd_sequence.go: -------------------------------------------------------------------------------- 1 | package comm 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | // BcdSequence 电信MsgId序号生成器,支持每分钟产生100万个不重复序号,超出后序号会重复 11 | // BCD 4bit编码,用4bit表示0-9的数字 12 | type BcdSequence struct { 13 | sync.Mutex // 锁 14 | worker []byte // SMGW代码:3 字节( BCD 码),6位十进制数字的字符串 15 | timestamp string // 时间:4 字节( BCD 码),格式为 MMDDHHMM(月日时分) 16 | sequence int32 // 序列号:3 字节( BCD 码),取值范围为 000000 999999 ,从 0 开始,顺序累加,步长为 1 循环使用。 17 | } 18 | 19 | const telSeqMax = 1000000 20 | 21 | func NewBcdSequence(w string) *BcdSequence { 22 | // check 23 | for _, s := range w { 24 | if byte(s) > '9' || byte(s) < '0' { 25 | w = "000000" 26 | break 27 | } 28 | } 29 | w = "000000" + w 30 | w = w[len(w)-6:] 31 | 32 | ret := &BcdSequence{} 33 | ret.worker = StoBcd(w) 34 | return ret 35 | } 36 | 37 | func (tf *BcdSequence) NextVal() []byte { 38 | tf.Lock() 39 | defer tf.Unlock() 40 | mi := time.Now().Format("01021504") 41 | if tf.timestamp == mi { 42 | // 超过每分钟telSeqMax后序号会重复 43 | tf.sequence = (tf.sequence + 1) % telSeqMax 44 | } else { 45 | tf.sequence = 0 46 | } 47 | tf.timestamp = mi 48 | seq := make([]byte, 10) 49 | copy(seq[0:3], tf.worker) 50 | copy(seq[3:7], StoBcd(tf.timestamp)) 51 | copy(seq[7:10], StoBcd(IntToFixStr(int64(tf.sequence), 6))) 52 | return seq 53 | } 54 | 55 | func IntToFixStr(i int64, l int) string { 56 | si := strconv.FormatInt(i, 10) 57 | if len(si) == l { 58 | return si 59 | } else { 60 | var sb strings.Builder 61 | sb.Grow(l) 62 | for i := 0; i < l; i++ { 63 | sb.WriteByte('0') 64 | } 65 | sb.WriteString(si) 66 | si = sb.String() 67 | return si[len(si)-l:] 68 | } 69 | } 70 | 71 | func StoBcd(w string) []byte { 72 | var wb []byte 73 | var h, l byte 74 | for i, c := range []byte(w) { 75 | // index 为偶数的作为高4bit 76 | if i&0x1 == 0 { 77 | h = c - '0' 78 | if h > 9 { 79 | h = 9 80 | } 81 | } else { 82 | // index 为奇数的作为低4bit 83 | l = c - '0' 84 | if l > 9 { 85 | l = 9 86 | } 87 | } 88 | // 每两个字符构成一个字节 89 | if i&0x1 == 1 { 90 | wb = append(wb, h<<4|l) 91 | h, l = 0, 0 92 | } 93 | } 94 | if h != 0 { 95 | wb = append(wb, h<<4) 96 | } 97 | return wb 98 | } 99 | 100 | func BcdToString(bcd []byte) string { 101 | var sb strings.Builder 102 | sb.Grow(2 * len(bcd)) 103 | for _, b := range bcd { 104 | c := b >> 4 105 | if c > 9 { 106 | c = 9 107 | } 108 | sb.WriteByte(c + '0') 109 | c = b & 0x0f 110 | if c > 9 { 111 | c = 9 112 | } 113 | sb.WriteByte(c + '0') 114 | } 115 | return sb.String() 116 | } 117 | -------------------------------------------------------------------------------- /codec/cmpp/options.go: -------------------------------------------------------------------------------- 1 | package cmpp 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type Option func(mtOps *MtOptions) 8 | 9 | func loadOptions(options ...Option) *MtOptions { 10 | opts := &MtOptions{ 11 | RegisteredDel: uint8(0xf), 12 | MsgLevel: uint8(0xf), 13 | FeeUsertype: uint8(0xf), 14 | FeeTerminalType: uint8(0xf), 15 | } 16 | for _, option := range options { 17 | option(opts) 18 | } 19 | return opts 20 | } 21 | 22 | type MtOptions struct { 23 | RegisteredDel uint8 24 | MsgLevel uint8 25 | FeeUsertype uint8 26 | FeeTerminalType uint8 27 | ServiceId string 28 | FeeTerminalId string 29 | FeeType string 30 | FeeCode string 31 | ValidTime string 32 | AtTime string 33 | SrcId string 34 | LinkID string 35 | } 36 | 37 | // WithOptions 设置配置项 38 | func WithOptions(opt *MtOptions) Option { 39 | return func(mtOps *MtOptions) { 40 | mtOps = opt 41 | } 42 | } 43 | 44 | // MtFeeTerminalType 被计费用户的号码类型,0:真实号码;1:伪码 45 | func MtFeeTerminalType(t uint8) Option { 46 | if t != 0 && t != 1 { 47 | t = uint8(0xf) 48 | } 49 | return func(opts *MtOptions) { 50 | opts.FeeTerminalType = t 51 | } 52 | } 53 | 54 | // MtFeeUsertype 计费用户类型字段 55 | // 0:对目的终端MSISDN计费; 56 | // 1:对源终端MSISDN计费; 57 | // 2:对SP计费; 58 | // 3:表示本字段无效,对谁计费参见Fee_terminal_Id 字段。 59 | func MtFeeUsertype(t uint8) Option { 60 | if t != 0 && t != 1 && t != 2 && t != 3 { 61 | t = uint8(0xf) 62 | } 63 | return func(opts *MtOptions) { 64 | opts.FeeUsertype = t 65 | } 66 | } 67 | 68 | // MtLinkID 点播业务使用的LinkID,非点播类业务的MT流程不使用该字段 69 | func MtLinkID(s string) Option { 70 | return func(opts *MtOptions) { 71 | opts.LinkID = s 72 | } 73 | } 74 | 75 | // MtSrcId SP的服务代码或前缀为服务代码的长号码, 网关将该号码完整的填到SMPP协议Submit_SM消息相应的source_addr字段,该号码最终在用户手机上显示为短消息的主叫号码 76 | func MtSrcId(s string) Option { 77 | return func(opts *MtOptions) { 78 | opts.SrcId = s 79 | } 80 | } 81 | 82 | // MtAtTime 定时发送时间,格式遵循SMPP3.3协议 83 | func MtAtTime(t time.Time) Option { 84 | return func(opts *MtOptions) { 85 | s := t.Format("060102150405") 86 | opts.AtTime = s + "032+" 87 | } 88 | } 89 | 90 | // MtAtTimeStr 定时发送时间,格式:yyMMddHHmmss 91 | func MtAtTimeStr(s string) Option { 92 | return func(opts *MtOptions) { 93 | if len(s) > 12 { 94 | s = s[:12] 95 | } 96 | opts.AtTime = s + "032+" 97 | } 98 | } 99 | 100 | // MtValidTime 存活有效期,格式遵循SMPP3.3协议 101 | func MtValidTime(s string) Option { 102 | return func(opts *MtOptions) { 103 | opts.ValidTime = s 104 | } 105 | } 106 | 107 | // MtFeeCode 资费代码(以分为单位) 108 | func MtFeeCode(s string) Option { 109 | return func(opts *MtOptions) { 110 | opts.FeeCode = s 111 | } 112 | } 113 | 114 | // MtFeeType 资费类别 115 | // 01:对“计费用户号码”免费 116 | // 02:对“计费用户号码”按条计信息费 117 | // 03:对“计费用户号码”按包月收取信息费 118 | // 04:对“计费用户号码”的信息费封顶 119 | // 05:对“计费用户号码”的收费是由SP实现 120 | func MtFeeType(s string) Option { 121 | if s != "01" && s != "02" && s != "03" && s != "04" && s != "05" { 122 | s = "" 123 | } 124 | return func(opts *MtOptions) { 125 | opts.FeeType = s 126 | } 127 | } 128 | 129 | // MtFeeTerminalId 计费号码与FeeTerminalType配合使用 130 | func MtFeeTerminalId(id string) Option { 131 | return func(opts *MtOptions) { 132 | opts.FeeTerminalId = id 133 | } 134 | } 135 | 136 | // MtServiceId 业务标识,是数字、字母和符号的组合 137 | func MtServiceId(id string) Option { 138 | return func(opts *MtOptions) { 139 | opts.ServiceId = id 140 | } 141 | } 142 | 143 | // MtRegisteredDel 是否需状态报告 144 | func MtRegisteredDel(tf uint8) Option { 145 | if tf != 0 && tf != 1 { 146 | tf = uint8(0xf) 147 | } 148 | return func(opts *MtOptions) { 149 | opts.RegisteredDel = tf 150 | } 151 | } 152 | 153 | // MtMsgLevel 消息优先级 154 | func MtMsgLevel(l uint8) Option { 155 | if l > 9 { 156 | l = uint8(0xf) 157 | } 158 | return func(opts *MtOptions) { 159 | opts.MsgLevel = l 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /codec/smgp/report.go: -------------------------------------------------------------------------------- 1 | package smgp 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | type Report struct { 9 | id []byte // 【10字节】状态报告对应原短消息的MsgID 10 | sub string // 【3字节】取缺省值001 11 | dlvrd string // 【3字节】取缺省值001 12 | submitDate string // 【10字节】短消息提交时间(格式:年年月月日日时时分分,例如010331200000) 13 | doneDate string // 【10字节】短消息提交时间(格式:年年月月日日时时分分,例如010331200000) 14 | stat string // 【7字节】短消息的最终状态 15 | err string // 【3字节】短消息的最终状态 16 | txt string // 【20字节】前3个字节,表示短消息长度(用ASCII码表示),后17个字节表示短消息的内容 17 | } 18 | 19 | const RptLen = 10 + 3 + 3 + 10 + 10 + 7 + 3 + 20 + len("id: sub: dlvrd: submit date: done date: stat: err: text:") 20 | 21 | func NewReport(id []byte) *Report { 22 | report := &Report{id: id} 23 | report.sub = "001" 24 | report.dlvrd = "001" 25 | report.submitDate = time.Now().Format("0601021504") 26 | report.doneDate = time.Now().Add(time.Minute).Format("0601021504") 27 | report.txt = "" 28 | // 判断序号的时间戳部分 29 | switch time.Now().Unix() % 1000 { 30 | case 1: 31 | report.err = "001" 32 | report.stat = reportStatMap["001"] 33 | case 2: 34 | report.err = "002" 35 | report.stat = reportStatMap["002"] 36 | case 3: 37 | report.err = "003" 38 | report.stat = reportStatMap["003"] 39 | case 4: 40 | report.err = "004" 41 | report.stat = reportStatMap["004"] 42 | case 5: 43 | report.err = "005" 44 | report.stat = reportStatMap["005"] 45 | case 6: 46 | report.err = "006" 47 | report.stat = reportStatMap["006"] 48 | case 7: 49 | report.err = "007" 50 | report.stat = reportStatMap["007"] 51 | case 8: 52 | report.err = "008" 53 | report.stat = reportStatMap["008"] 54 | case 9: 55 | report.err = "009" 56 | report.stat = reportStatMap["009"] 57 | case 10: 58 | report.err = "010" 59 | report.stat = reportStatMap["010"] 60 | default: 61 | report.err = "000" 62 | report.stat = reportStatMap["000"] 63 | } 64 | return report 65 | } 66 | 67 | func (rt *Report) String() string { 68 | return fmt.Sprintf("id:%x sub:%s dlvrd:%s submit date:%s done date:%s stat:%s err:%s text:%x", 69 | rt.id, rt.sub, rt.dlvrd, rt.submitDate, rt.doneDate, rt.stat, rt.err, rt.txt) 70 | } 71 | 72 | func (rt *Report) Encode() []byte { 73 | data := make([]byte, RptLen) 74 | index := 0 75 | copy(data[index:index+3], "id:") 76 | index += 3 77 | copy(data[index:index+10], rt.id) 78 | index += 10 79 | str := rt.String() // 不含text的值 80 | start := 3 + 20 // "id:%x" 81 | copy(data[index:RptLen-20], str[start:]) 82 | return data 83 | } 84 | 85 | func (rt *Report) Decode(frame []byte) error { 86 | // check 87 | if len(frame) < RptLen { 88 | return ErrorPacket 89 | } 90 | index := 3 // skip "id:" 91 | rt.id = frame[index : index+10] 92 | index += 10 93 | 94 | index += 5 // skip " sub:" 95 | rt.sub = string(frame[index : index+3]) 96 | index += 3 97 | 98 | index += 7 // skip " dlvrd:" 99 | rt.dlvrd = string(frame[index : index+3]) 100 | index += 3 101 | 102 | index += 13 // skip " submit date:" 103 | rt.submitDate = string(frame[index : index+10]) 104 | index += 10 105 | 106 | index += 11 // skip " done date:" 107 | rt.doneDate = string(frame[index : index+10]) 108 | index += 10 109 | 110 | index += 6 // skip " stat:" 111 | rt.stat = string(frame[index : index+7]) 112 | index += 7 113 | 114 | index += 5 // skip " err:" 115 | rt.err = string(frame[index : index+3]) 116 | return nil 117 | } 118 | 119 | var reportStatMap = map[string]string{ 120 | "000": "DELIVRD", // 成功 121 | "001": "EXPIRED", // 用户不能通信 122 | "002": "EXPIRED", // 用户忙 123 | "003": "UNDELIV", // 终端无此部件号 124 | "004": "UNDELIV", // 非法用户 125 | "005": "UNDELIV", // 用户在黑名单内 126 | "006": "UNDELIV", // 系统错误 127 | "007": "EXPIRED", // 用户内存满 128 | "008": "UNDELIV", // 非信息终端 129 | "009": "UNDELIV", // 数据错误 130 | "010": "UNDELIV", // 数据丢失 131 | "999": "UNKNOWN", // 未知错误 132 | } 133 | -------------------------------------------------------------------------------- /codec/smgp/submit_test.go: -------------------------------------------------------------------------------- 1 | package smgp 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestNewSubmit(t *testing.T) { 12 | subs := NewSubmit([]string{"17600001111", "17700001111"}, Poem, MtOptions{AtTime: time.Now().Add(time.Minute)}) 13 | assert.True(t, len(subs) == 4) 14 | 15 | for i, sub := range subs { 16 | t.Logf("%+v", sub) 17 | if i < 3 { 18 | assert.True(t, int(sub.PacketLength) == 328) 19 | assert.True(t, int(sub.msgLength) == 140) 20 | assert.True(t, int(sub.msgLength) == len(sub.msgBytes)) 21 | } else { 22 | assert.True(t, int(sub.msgLength) <= 140) 23 | assert.True(t, int(sub.PacketLength) > 147) 24 | } 25 | } 26 | } 27 | 28 | func TestSubmit_Encode(t *testing.T) { 29 | encode(t, []string{"17600001111", "17600002222"}, Poem, 4) 30 | encode(t, []string{"17600001111"}, "hello world 世界,你好!", 1) 31 | } 32 | 33 | func encode(t *testing.T, phones []string, txt string, l int) { 34 | subs := NewSubmit(phones, txt, MtOptions{AtTime: time.Now().Add(time.Minute)}) 35 | assert.True(t, len(subs) == l) 36 | 37 | for _, sub := range subs { 38 | t.Logf("%+v", sub) 39 | dt := sub.Encode() 40 | assert.True(t, int(sub.PacketLength) == len(dt)) 41 | t.Logf("%v: %x", int(sub.PacketLength) == len(dt), dt) 42 | resp := sub.ToResponse(0).(*SubmitResp) 43 | t.Logf("%s", resp) 44 | dt = resp.Encode() 45 | t.Logf("%v: %x", int(resp.PacketLength) == len(dt), dt) 46 | } 47 | } 48 | 49 | func TestSubmit_Decode(t *testing.T) { 50 | decode(t, []string{"17600001111", "17600002222"}, Poem, 4) 51 | decode(t, []string{"17600001111"}, "hello world 世界,你好!", 1) 52 | } 53 | 54 | func decode(t *testing.T, phones []string, txt string, l int) { 55 | subs := NewSubmit(phones, txt, MtOptions{AtTime: time.Now().Add(time.Minute)}) 56 | assert.True(t, len(subs) == l) 57 | 58 | for _, sub := range subs { 59 | dt := sub.Encode() 60 | assert.True(t, int(sub.PacketLength) == len(dt)) 61 | t.Logf("%s", sub) 62 | t.Logf("%v: %x", int(sub.PacketLength) == len(dt), dt) 63 | 64 | head := MessageHeader{} 65 | err := head.Decode(dt) 66 | if err != nil { 67 | t.Fail() 68 | continue 69 | } 70 | subDec := &Submit{} 71 | err = subDec.Decode(&head, dt[12:]) 72 | if err != nil { 73 | t.Fail() 74 | continue 75 | } 76 | dt2 := subDec.Encode() 77 | t.Logf("%s", subDec) 78 | t.Logf("%v: %x", int(subDec.PacketLength) == len(dt2), dt2) 79 | assert.True(t, 0 == bytes.Compare(dt, dt2)) 80 | 81 | resp := subDec.ToResponse(0).(*SubmitResp) 82 | t.Logf("%s", resp) 83 | dt = resp.Encode() 84 | assert.True(t, int(resp.PacketLength) == len(dt)) 85 | t.Logf("%v: %x", int(resp.PacketLength) == len(dt), dt) 86 | head = MessageHeader{} 87 | err = head.Decode(dt) 88 | if err != nil { 89 | t.Fail() 90 | continue 91 | } 92 | respDec := &SubmitResp{} 93 | err = respDec.Decode(&head, dt[12:]) 94 | if err != nil { 95 | t.Fail() 96 | continue 97 | } 98 | t.Logf("%s", respDec) 99 | assert.True(t, int(respDec.PacketLength) == len(dt)) 100 | t.Logf("%v: %x", int(respDec.PacketLength) == len(dt), dt) 101 | assert.True(t, 0 == bytes.Compare(respDec.msgId, resp.msgId)) 102 | } 103 | } 104 | 105 | func TestGbk(t *testing.T) { 106 | gb, _ := GbEncoder.String(Poem) 107 | gbDec, _ := GbDecoder.String(gb) 108 | t.Logf("Origin: %s", Poem) 109 | t.Logf("GbStr : %s", gbDec) 110 | t.Logf("Origin Hex: %x", Poem) 111 | t.Logf("GbStr Hex: %x", gb) 112 | } 113 | 114 | const Poem = "将进酒\n" + 115 | "君不见黄河之水天上来,奔流到海不复回。\n" + 116 | "君不见高堂明镜悲白发,朝如青丝暮成雪。\n" + 117 | "人生得意须尽欢,莫使金樽空对月。\n" + 118 | "天生我材必有用,千金散尽还复来。\n" + 119 | "烹羊宰牛且为乐,会须一饮三百杯。\n" + 120 | "岑夫子,丹丘生,将进酒,杯莫停。\n" + 121 | "与君歌一曲,请君为我倾耳听。\n" + 122 | "钟鼓馔玉不足贵,但愿长醉不愿醒。\n" + 123 | "古来圣贤皆寂寞,惟有饮者留其名。\n" + 124 | "陈王昔时宴平乐,斗酒十千恣欢谑。\n" + 125 | "主人何为言少钱,径须沽取对君酌。\n" + 126 | "五花马、千金裘,呼儿将出换美酒,与尔同销万古愁。" 127 | -------------------------------------------------------------------------------- /codec/cmpp/submit_test.go: -------------------------------------------------------------------------------- 1 | package cmpp 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | 10 | "github.com/aaronwong1989/gosms/comm" 11 | ) 12 | 13 | func TestEncode(t *testing.T) { 14 | type args struct { 15 | s string 16 | } 17 | tests := []struct { 18 | name string 19 | args args 20 | want []byte 21 | }{ 22 | { 23 | "testcase1", 24 | args{"1"}, 25 | []byte{0x00, 0x31}, 26 | }, 27 | { 28 | "testcase2", 29 | args{"hello world"}, 30 | []byte{0x00, 0x68, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x6c, 0x00, 0x64}, 31 | }, 32 | {"testcase3", 33 | args{"Great 中国"}, 34 | []byte{0x00, 0x47, 0x00, 0x72, 0x00, 0x65, 0x00, 0x61, 0x00, 0x74, 0x00, 0x20, 0x4e, 0x2d, 0x56, 0xfd}, 35 | }, 36 | } 37 | for _, tt := range tests { 38 | t.Run(tt.name, func(t *testing.T) { 39 | if got := comm.Ucs2Encode(tt.args.s); !reflect.DeepEqual(got, tt.want) { 40 | t.Errorf("Ucs2Encode() = %v, want %v", got, tt.want) 41 | } 42 | }) 43 | } 44 | } 45 | 46 | func TestDecode(t *testing.T) { 47 | type args struct { 48 | ucs2 []byte 49 | } 50 | tests := []struct { 51 | name string 52 | args args 53 | want string 54 | }{ 55 | { 56 | "testcase1", 57 | args{[]byte{0x00, 0x31}}, 58 | "1", 59 | }, 60 | { 61 | "testcase2", 62 | args{[]byte{0x00, 0x68, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x6c, 0x00, 0x64}}, 63 | "hello world", 64 | }, 65 | {"testcase3", 66 | args{[]byte{0x00, 0x47, 0x00, 0x72, 0x00, 0x65, 0x00, 0x61, 0x00, 0x74, 0x00, 0x20, 0x4e, 0x2d, 0x56, 0xfd}}, 67 | "Great 中国", 68 | }, 69 | } 70 | for _, tt := range tests { 71 | t.Run(tt.name, func(t *testing.T) { 72 | if got := comm.Ucs2Decode(tt.args.ucs2); got != tt.want { 73 | t.Errorf("Ucs2Decode() = %v, want %v", got, tt.want) 74 | } 75 | }) 76 | } 77 | } 78 | 79 | func TestNewSubmit(t *testing.T) { 80 | phones := []string{"17011112222", "17500002222"} 81 | // 160 bytes 82 | content := "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" 83 | mts := NewSubmit(phones, content) 84 | for _, mt := range mts { 85 | t.Logf(">>> %s", mt) 86 | t.Logf("<<< %s", mt.ToResponse(0)) 87 | } 88 | 89 | content2 := content + "hello world" 90 | mts = NewSubmit(phones, content2) 91 | for _, mt := range mts { 92 | t.Logf(">>> %s", mt) 93 | t.Logf("<<< %s", mt.ToResponse(0)) 94 | } 95 | 96 | content3 := "强大的祖国" 97 | mts = NewSubmit(phones, content3) 98 | for _, mt := range mts { 99 | t.Logf(">>> %s", mt) 100 | bts := mt.Encode() 101 | t.Logf("mt.Encode(): %#x", bts) 102 | t.Logf("<<< %s", mt.ToResponse(0)) 103 | } 104 | 105 | content4 := "强大的祖国" + content 106 | mts = NewSubmit(phones, content4) 107 | for _, mt := range mts { 108 | t.Logf(">>> %s", mt) 109 | t.Logf("<<< %s", mt.ToResponse(0)) 110 | } 111 | } 112 | 113 | func TestSubmit_Encode(t *testing.T) { 114 | phones := []string{"17011112222"} 115 | mts := NewSubmit(phones, Poem, MtAtTime(time.Now().Add(5*time.Minute))) 116 | 117 | for _, mt := range mts { 118 | t.Logf("mt.String() : %s", mt) 119 | resp := mt.ToResponse(0).(*SubmitResp) 120 | t.Logf("resp.String(): %s", resp) 121 | 122 | enc := mt.Encode() 123 | t.Logf("Hex MT: %#x", enc) 124 | header := &MessageHeader{} 125 | err := header.Decode(enc[:12]) 126 | if err != nil { 127 | return 128 | } 129 | decMt := &Submit{} 130 | err = decMt.Decode(header, enc[12:]) 131 | if err != nil { 132 | return 133 | } 134 | 135 | t.Logf("decMt.String() : %s", decMt) 136 | assert.Equal(t, mt.msgBytes[6:], comm.Ucs2Encode(decMt.msgContent)) 137 | } 138 | } 139 | 140 | const Poem = "将进酒\n" + 141 | "君不见黄河之水天上来,奔流到海不复回。\n" + 142 | "君不见高堂明镜悲白发,朝如青丝暮成雪。\n" + 143 | "人生得意须尽欢,莫使金樽空对月。\n" + 144 | "天生我材必有用,千金散尽还复来。\n" + 145 | "烹羊宰牛且为乐,会须一饮三百杯。\n" + 146 | "岑夫子,丹丘生,将进酒,杯莫停。\n" + 147 | "与君歌一曲,请君为我倾耳听。\n" + 148 | "钟鼓馔玉不足贵,但愿长醉不愿醒。\n" + 149 | "古来圣贤皆寂寞,惟有饮者留其名。\n" + 150 | "陈王昔时宴平乐,斗酒十千恣欢谑。\n" + 151 | "主人何为言少钱,径须沽取对君酌。\n" + 152 | "五花马、千金裘,呼儿将出换美酒,与尔同销万古愁。" 153 | -------------------------------------------------------------------------------- /codec/smgp/config.go: -------------------------------------------------------------------------------- 1 | package smgp 2 | 3 | import ( 4 | "errors" 5 | 6 | "golang.org/x/text/encoding/simplifiedchinese" 7 | 8 | "github.com/aaronwong1989/gosms/comm/logging" 9 | "github.com/aaronwong1989/gosms/comm/yml_config" 10 | ) 11 | 12 | var log = logging.GetDefaultLogger() 13 | var ErrorPacket = errors.New("error packet") 14 | var GbEncoder = simplifiedchinese.GB18030.NewEncoder() 15 | var GbDecoder = simplifiedchinese.GB18030.NewDecoder() 16 | var Conf yml_config.YmlConfig 17 | var Seq32 Sequence32 18 | var Seq80 Sequence80 19 | 20 | // type Config struct { 21 | // // 公共参数 22 | // ClientId string `yaml:"client-id"` 23 | // SharedSecret string `yaml:"shared-secret"` 24 | // AuthCheck bool `yaml:"auth-check"` 25 | // Version byte `yaml:"version"` 26 | // MaxCons int `yaml:"max-cons"` 27 | // ActiveTestDuration time.Duration `yaml:"active-test-duration"` 28 | // DataCenterId int32 `yaml:"datacenter-id"` 29 | // WorkerId int32 `yaml:"worker-id"` 30 | // SmgwId string `yaml:"smgw-id"` 31 | // ReceiveWindowSize int `yaml:"receive-window-size"` 32 | // MaxPoolSize int `yaml:"max-pool-size"` 33 | // 34 | // // MT消息相关 35 | // NeedReport byte `yaml:"need-report"` 36 | // Priority byte `yaml:"Priority "` 37 | // DisplayNo string `yaml:"sms-display-no"` 38 | // ServiceId string `yaml:"service-id"` 39 | // FeeType string `yaml:"fee-type"` 40 | // FeeCode string `yaml:"fee-code"` 41 | // ChargeTermID string `yaml:"charge-term-id"` 42 | // FixedFee string `yaml:"fixed-fee"` 43 | // LinkID string `yaml:"link-id"` 44 | // ValidDuration time.Duration `yaml:"default-valid-duration"` 45 | // 46 | // // 模拟网关相关参数 47 | // SuccessRate float64 `yaml:"success-rate"` 48 | // MinSubmitRespMs int32 `yaml:"min-submit-resp-ms"` 49 | // MaxSubmitRespMs int32 `yaml:"max-submit-resp-ms"` 50 | // FixReportRespMs int32 `yaml:"fix-report-resp-ms"` 51 | // } 52 | 53 | const ( 54 | TP_pid = uint16(0x0001) 55 | TP_udhi = uint16(0x0002) 56 | PkTotal = uint16(0x0009) 57 | PkNumber = uint16(0x000A) 58 | LinkID = uint16(0x0003) 59 | ChargeUserType = uint16(0x0004) 60 | ChargeTermType = uint16(0x0005) 61 | ChargeTermPseudo = uint16(0x0006) 62 | DestTermType = uint16(0x0007) 63 | DestTermPseudo = uint16(0x0008) 64 | SubmitMsgType = uint16(0x000B) 65 | SPDealReslt = uint16(0x000C) 66 | SrcTermType = uint16(0x000D) 67 | SrcTermPseudo = uint16(0x000E) 68 | NodesCount = uint16(0x000F) 69 | MsgSrc = uint16(0x0010) 70 | SrcType = uint16(0x0011) 71 | MServiceID = uint16(0x0012) 72 | ) 73 | 74 | var StatMap = map[uint32]string{ 75 | 0: "成功", 76 | 1: "系统忙", 77 | 2: "超过最大连接数", 78 | 10: "消息结构错", 79 | 11: "命令字错", 80 | 12: "序列号重复", 81 | 20: "IP地址错", 82 | 21: "认证错", 83 | 22: "版本太高", 84 | 30: "非法消息类型(MsgType)", 85 | 31: "非法优先级(Priority)", 86 | 32: "非法资费类型(FeeType)", 87 | 33: "非法资费代码(FeeCode)", 88 | 34: "非法短消息格式(MsgFormat)", 89 | 35: "非法时间格式", 90 | 36: "非法短消息长度(MsgLength)", 91 | 37: "有效期已过", 92 | 38: "非法查询类别(QueryType)", 93 | 39: "路由错误", 94 | 40: "非法包月费/封顶费(FixedFee)", 95 | 41: "非法更新类型(UpdateType)", 96 | 42: "非法路由编号(RouteId)", 97 | 43: "非法服务代码(ServiceId)", 98 | 44: "非法有效期(ValidTime)", 99 | 45: "非法定时发送时间(AtTime)", 100 | 46: "非法发送用户号码(SrcTermId)", 101 | 47: "非法接收用户号码(DestTermId)", 102 | 48: "非法计费用户号码(ChargeTermId)", 103 | 49: "非法SP服务代码(SPCode)", 104 | 56: "非法源网关代码(SrcGatewayID)", 105 | 57: "非法查询号码(QueryTermID)", 106 | 58: "没有匹配路由", 107 | 59: "非法SP类型(SPType)", 108 | 60: "非法上一条路由编号(LastRouteID)", 109 | 61: "非法路由类型(RouteType)", 110 | 62: "非法目标网关代码(DestGatewayID)", 111 | 63: "非法目标网关IP(DestGatewayIP)", 112 | 64: "非法目标网关端口(DestGatewayPort)", 113 | 65: "非法路由号码段(TermRangeID)", 114 | 66: "非法终端所属省代码(ProvinceCode)", 115 | 67: "非法用户类型(UserType)", 116 | 68: "本节点不支持路由更新", 117 | 69: "非法SP企业代码(SPID)", 118 | 70: "非法SP接入类型(SPAccessType)", 119 | 71: "路由信息更新失败", 120 | 72: "非法时间戳(Time)", 121 | 73: "非法业务代码(MServiceID)", 122 | 74: "SP禁止下发时段", 123 | 75: "SP发送超过日流量", 124 | 76: "SP帐号过有效期", 125 | } 126 | -------------------------------------------------------------------------------- /codec/cmpp/header.go: -------------------------------------------------------------------------------- 1 | package cmpp 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "unsafe" 7 | ) 8 | 9 | type MessageHeader struct { 10 | TotalLength uint32 11 | CommandId uint32 12 | SequenceId uint32 13 | } 14 | 15 | func (header *MessageHeader) Encode() []byte { 16 | if header.TotalLength < HeadLength { 17 | header.TotalLength = HeadLength 18 | } 19 | frame := make([]byte, header.TotalLength) 20 | binary.BigEndian.PutUint32(frame[0:4], header.TotalLength) 21 | binary.BigEndian.PutUint32(frame[4:8], header.CommandId) 22 | binary.BigEndian.PutUint32(frame[8:12], header.SequenceId) 23 | return frame 24 | } 25 | 26 | func (header *MessageHeader) Decode(frame []byte) error { 27 | if len(frame) < HeadLength { 28 | return ErrorPacket 29 | } 30 | header.TotalLength = binary.BigEndian.Uint32(frame[0:4]) 31 | header.CommandId = binary.BigEndian.Uint32(frame[4:8]) 32 | header.SequenceId = binary.BigEndian.Uint32(frame[8:12]) 33 | return nil 34 | } 35 | 36 | func (header *MessageHeader) String() string { 37 | return fmt.Sprintf("{ PacketLength: %d, RequestId: %s, SequenceId: %d }", header.TotalLength, CommandMap[header.CommandId], header.SequenceId) 38 | } 39 | 40 | func TrimStr(bts []byte) string { 41 | var i = 0 42 | for ; i < len(bts); i++ { 43 | if bts[i] == 0 { 44 | break 45 | } 46 | } 47 | ns := bts[:i] 48 | return *(*string)(unsafe.Pointer(&ns)) 49 | } 50 | 51 | func V3() bool { 52 | return int(Conf.GetInt("version"))&0xf0 == 0x30 53 | } 54 | 55 | const ( 56 | HeadLength = 12 // 报文头长度 57 | CMPP_CONNECT = uint32(0x00000001) // 请求连接 58 | CMPP_CONNECT_RESP = uint32(0x80000001) // 请求连接应答 59 | CMPP_TERMINATE = uint32(0x00000002) // 终止连接 60 | CMPP_TERMINATE_RESP = uint32(0x80000002) // 终止连接应答 61 | CMPP_SUBMIT = uint32(0x00000004) // 提交短信 62 | CMPP_SUBMIT_RESP = uint32(0x80000004) // 提交短信应答 63 | CMPP_DELIVER = uint32(0x00000005) // 短信下发 64 | CMPP_DELIVER_RESP = uint32(0x80000005) // 下发短信应答 65 | CMPP_ACTIVE_TEST = uint32(0x00000008) // 激活测试 66 | CMPP_ACTIVE_TEST_RESP = uint32(0x80000008) // 激活测试应答 67 | // CMPP_QUERY = uint32(0x00000006) // 发送短信状态查询 68 | // CMPP_QUERY_RESP = uint32(0x80000006) // 发送短信状态查询应答 69 | // CMPP_CANCEL = uint32(0x00000007) // 删除短信 70 | // CMPP_CANCEL_RESP = uint32(0x80000007) // 删除短信应答 71 | // CMPP_FWD = uint32(0x00000009) // 消息前转 72 | // CMPP_FWD_RESP = uint32(0x80000009) // 消息前转应答 73 | // CMPP_MT_ROUTE = uint32(0x00000010) // MT 路由请求 74 | // CMPP_MT_ROUTE_RESP = uint32(0x80000010) // MT 路由请求应答 75 | // CMPP_MO_ROUTE = uint32(0x00000011) // MO 路由请求 76 | // CMPP_MO_ROUTE_RESP = uint32(0x80000011) // MO 路由请求应答 77 | // CMPP_GET_MT_ROUTE = uint32(0x00000012) // 获取 MT 路由请求 78 | // CMPP_GET_MT_ROUTE_RESP = uint32(0x80000012) // 获取 MT 路由请求应答 79 | // CMPP_MT_ROUTE_UPDATE = uint32(0x00000013) // MT 路由更新 80 | // CMPP_MT_ROUTE_UPDATE_RESP = uint32(0x80000013) // MT 路由更新应答 81 | // CMPP_MO_ROUTE_UPDATE = uint32(0x00000014) // MO 路由更新 82 | // CMPP_MO_ROUTE_UPDATE_RESP = uint32(0x80000014) // MO 路由更新应答 83 | // CMPP_PUSH_MT_ROUTE_UPDATE = uint32(0x00000015) // MT 路由更新 84 | // CMPP_PUSH_MT_ROUTE_UPDATE_RESP = uint32(0x80000015) // MT 路由更新应答 85 | // CMPP_PUSH_MO_ROUTE_UPDATE = uint32(0x00000016) // MO 路由更新 86 | // CMPP_PUSH_MO_ROUTE_UPDATE_RESP = uint32(0x80000016) // MO 路由更新应答 87 | // CMPP_GET_MO_ROUTE = uint32(0x00000017) // 获取 MO 路由请求 88 | // CMPP_GET_MO_ROUTE_RESP = uint32(0x80000017) // 获取 MO 路由请求应答 89 | ) 90 | 91 | var CommandMap = make(map[uint32]string) 92 | 93 | func init() { 94 | CommandMap[CMPP_CONNECT] = "CMPP_CONNECT" 95 | CommandMap[CMPP_CONNECT_RESP] = "CMPP_CONNECT_RESP" 96 | CommandMap[CMPP_TERMINATE] = "CMPP_TERMINATE" 97 | CommandMap[CMPP_TERMINATE_RESP] = "CMPP_TERMINATE_RESP" 98 | CommandMap[CMPP_SUBMIT] = "CMPP_SUBMIT" 99 | CommandMap[CMPP_SUBMIT_RESP] = "CMPP_SUBMIT_RESP" 100 | CommandMap[CMPP_DELIVER] = "CMPP_DELIVER" 101 | CommandMap[CMPP_DELIVER_RESP] = "CMPP_DELIVER_RESP" 102 | CommandMap[CMPP_ACTIVE_TEST] = "CMPP_ACTIVE_TEST" 103 | CommandMap[CMPP_ACTIVE_TEST_RESP] = "CMPP_ACTIVE_TEST_RESP" 104 | } 105 | -------------------------------------------------------------------------------- /comm/tool.go: -------------------------------------------------------------------------------- 1 | package comm 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "math/rand" 7 | "net/http" 8 | "os" 9 | "strconv" 10 | "time" 11 | "unsafe" 12 | 13 | "github.com/panjf2000/gnet/v2" 14 | "golang.org/x/text/encoding/unicode" 15 | "golang.org/x/text/transform" 16 | 17 | "github.com/aaronwong1989/gosms/comm/logging" 18 | ) 19 | 20 | var log = logging.GetDefaultLogger() 21 | 22 | func TrimStr(bts []byte) string { 23 | var i = 0 24 | for ; i < len(bts); i++ { 25 | if bts[i] == 0 { 26 | break 27 | } 28 | } 29 | ns := bts[:i] 30 | return *(*string)(unsafe.Pointer(&ns)) 31 | } 32 | 33 | func CopyStr(dest []byte, src string, index int, len int) int { 34 | copy(dest[index:index+len], src) 35 | index += len 36 | return index 37 | } 38 | 39 | func CopyByte(dest []byte, src byte, index int) int { 40 | dest[index] = src 41 | index++ 42 | return index 43 | } 44 | 45 | func FormatTime(time time.Time) string { 46 | s := time.Format("060102150405") 47 | return s + "032+" 48 | } 49 | 50 | // ToTPUDHISlices 拆分为长短信切片 51 | // 纯ASCII内容的拆分 pkgLen = 160 52 | // 含中文内容的拆分 pkgLen = 140 53 | func ToTPUDHISlices(content []byte, pkgLen int) (rt [][]byte) { 54 | if len(content) < pkgLen { 55 | return [][]byte{content} 56 | } 57 | 58 | headLen := 6 59 | bodyLen := pkgLen - headLen 60 | parts := len(content) / bodyLen 61 | tailLen := len(content) % bodyLen 62 | if tailLen != 0 { 63 | parts++ 64 | } 65 | // 分片消息组的标识,用于收集组装消息 66 | groupId := byte(time.Now().UnixNano() & 0xff) 67 | var part []byte 68 | for i := 0; i < parts; i++ { 69 | if i != parts-1 { 70 | part = make([]byte, pkgLen) 71 | } else { 72 | // 最后一片 73 | part = make([]byte, 6+tailLen) 74 | } 75 | part[0], part[1], part[2] = 0x05, 0x00, 0x03 76 | part[3] = groupId 77 | part[4], part[5] = byte(parts), byte(i+1) 78 | if i != parts-1 { 79 | copy(part[6:pkgLen], content[bodyLen*i:bodyLen*(i+1)]) 80 | } else { 81 | copy(part[6:], content[0:tailLen]) 82 | } 83 | rt = append(rt, part) 84 | } 85 | return rt 86 | } 87 | 88 | // TakeBytes 消费一定字节数的数据 89 | func TakeBytes(c gnet.Conn, bytes int) []byte { 90 | if c.InboundBuffered() < bytes { 91 | return nil 92 | } 93 | frame, err := c.Peek(bytes) 94 | if err != nil { 95 | log.Errorf("[%-9s] decode error: %v", "OnTraffic", err) 96 | return nil 97 | } 98 | _, err = c.Discard(bytes) 99 | if err != nil { 100 | log.Errorf("[%-9s] decode error: %v", "OnTraffic", err) 101 | return nil 102 | } 103 | return frame 104 | } 105 | 106 | // Ucs2Encode Encode to UCS2. 107 | func Ucs2Encode(s string) []byte { 108 | e := unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM) 109 | ucs, _, err := transform.Bytes(e.NewEncoder(), []byte(s)) 110 | if err != nil { 111 | return nil 112 | } 113 | return ucs 114 | } 115 | 116 | // Ucs2Decode Decode from UCS2. 117 | func Ucs2Decode(ucs2 []byte) string { 118 | e := unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM) 119 | bts, _, err := transform.Bytes(e.NewDecoder(), ucs2) 120 | if err != nil { 121 | return "" 122 | } 123 | return TrimStr(bts) 124 | } 125 | 126 | func LogHex(level logging.Level, model string, bts []byte) { 127 | msg := fmt.Sprintf("[OnTraffic] Hex %s: %x", model, bts) 128 | if level == logging.DebugLevel { 129 | log.Debugf(msg) 130 | } else if level == logging.ErrorLevel { 131 | log.Errorf(msg) 132 | } else if level == logging.WarnLevel { 133 | log.Warnf(msg) 134 | } else { 135 | log.Infof(msg) 136 | } 137 | } 138 | 139 | func RandNum(min, max int32) int { 140 | return rand.Intn(int(max-min)) + int(min) 141 | } 142 | 143 | // DiceCheck 得到结果比给定数字小则返回true,否则返回false 144 | func DiceCheck(prob float64) bool { 145 | return float64(rand.Intn(10000))/10000.0 < prob 146 | } 147 | 148 | // SavePid 在程序执行的当前目录生成pid文件 149 | func SavePid(f string) string { 150 | file, err := os.OpenFile(f, os.O_WRONLY|os.O_CREATE, 0600) 151 | if err != nil { 152 | log.Errorf("%v", err) 153 | } 154 | pid := fmt.Sprintf("%d", os.Getpid()) 155 | 156 | writer := bufio.NewWriter(file) 157 | _, _ = writer.WriteString(pid) 158 | defer func(file *os.File, writer *bufio.Writer) { 159 | _ = writer.Flush() 160 | _ = file.Close() 161 | }(file, writer) 162 | 163 | return pid 164 | } 165 | 166 | // StartMonitor 开启pprof,监听请求 167 | func StartMonitor(port int) { 168 | go func() { 169 | addr := strconv.Itoa(port + 1) 170 | log.Infof("[Pprof ] http://localhost:%s/debug/pprof/", addr) 171 | if err := http.ListenAndServe(":"+addr, nil); err != nil { 172 | log.Infof("start pprof failed on %s", addr) 173 | } 174 | }() 175 | } 176 | -------------------------------------------------------------------------------- /comm/logging/logger.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "strconv" 7 | 8 | "go.uber.org/zap" 9 | "go.uber.org/zap/zapcore" 10 | "gopkg.in/natefinch/lumberjack.v2" 11 | ) 12 | 13 | var ( 14 | flushLogs func() error 15 | defaultLogger Logger 16 | defaultLoggingLevel Level 17 | ) 18 | 19 | // Level is the alias of zapcore.Level. 20 | type Level = zapcore.Level 21 | 22 | const ( 23 | // DebugLevel logs are typically voluminous, and are usually disabled in 24 | // production. 25 | DebugLevel Level = iota - 1 26 | // InfoLevel is the default logging priority. 27 | InfoLevel 28 | // WarnLevel logs are more important than Info, but don't need individual 29 | // human review. 30 | WarnLevel 31 | // ErrorLevel logs are high-priority. If an application is running smoothly, 32 | // it shouldn't generate any error-level logs. 33 | ErrorLevel 34 | // DPanicLevel logs are particularly important errors. In development the 35 | // logger panics after writing the message. 36 | DPanicLevel 37 | // PanicLevel logs a message, then panics. 38 | PanicLevel 39 | // FatalLevel logs a message, then calls os.Exit(1). 40 | FatalLevel 41 | ) 42 | 43 | func init() { 44 | lvl := os.Getenv("GNET_LOGGING_LEVEL") 45 | if len(lvl) > 0 { 46 | loggingLevel, err := strconv.ParseInt(lvl, 10, 8) 47 | if err != nil { 48 | panic("invalid GNET_LOGGING_LEVEL, " + err.Error()) 49 | } 50 | defaultLoggingLevel = Level(loggingLevel) 51 | } 52 | 53 | // Initializes the inside default logger of gnet. 54 | fileName := os.Getenv("GNET_LOGGING_FILE") 55 | if len(fileName) > 0 { 56 | var err error 57 | defaultLogger, flushLogs, err = CreateLoggerAsLocalFile(fileName, defaultLoggingLevel) 58 | if err != nil { 59 | panic("invalid GNET_LOGGING_FILE, " + err.Error()) 60 | } 61 | } else { 62 | cfg := zap.NewDevelopmentConfig() 63 | cfg.Level = zap.NewAtomicLevelAt(defaultLoggingLevel) 64 | cfg.EncoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02T15:04:05.000") 65 | zapLogger, _ := cfg.Build() 66 | defaultLogger = zapLogger.Sugar() 67 | } 68 | } 69 | 70 | func getEncoder() zapcore.Encoder { 71 | encoderConfig := zap.NewProductionEncoderConfig() 72 | encoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02T15:04:05.000") 73 | encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder 74 | return zapcore.NewConsoleEncoder(encoderConfig) 75 | } 76 | 77 | // GetDefaultLogger returns the default logger. 78 | func GetDefaultLogger() Logger { 79 | return defaultLogger 80 | } 81 | 82 | // LogLevel tells what the default logging level is. 83 | func LogLevel() string { 84 | return defaultLoggingLevel.String() 85 | } 86 | 87 | // CreateLoggerAsLocalFile setups the logger by local file path. 88 | func CreateLoggerAsLocalFile(localFilePath string, logLevel Level) (logger Logger, flush func() error, err error) { 89 | if len(localFilePath) == 0 { 90 | return nil, nil, errors.New("invalid local logger path") 91 | } 92 | 93 | // lumberjack.Logger is already safe for concurrent use, so we don't need to lock it. 94 | lumberJackLogger := &lumberjack.Logger{ 95 | Filename: localFilePath, 96 | MaxSize: 100, // megabytes 97 | MaxBackups: 100, 98 | MaxAge: 15, // days 99 | } 100 | 101 | encoder := getEncoder() 102 | ws := zapcore.AddSync(lumberJackLogger) 103 | zapcore.Lock(ws) 104 | 105 | levelEnabler := zap.LevelEnablerFunc(func(level Level) bool { 106 | return level >= logLevel 107 | }) 108 | core := zapcore.NewCore(encoder, ws, levelEnabler) 109 | zapLogger := zap.New(core, zap.AddCaller()) 110 | logger = zapLogger.Sugar() 111 | flush = zapLogger.Sync 112 | return 113 | } 114 | 115 | // Cleanup does something windup for logger, like closing, flushing, etc. 116 | func Cleanup() { 117 | if flushLogs != nil { 118 | _ = flushLogs() 119 | } 120 | } 121 | 122 | // Logger is used for logging formatted messages. 123 | type Logger interface { 124 | // Debugf logs messages at DEBUG level. 125 | Debugf(format string, args ...interface{}) 126 | // Infof logs messages at INFO level. 127 | Infof(format string, args ...interface{}) 128 | // Warnf logs messages at WARN level. 129 | Warnf(format string, args ...interface{}) 130 | // Errorf logs messages at ERROR level. 131 | Errorf(format string, args ...interface{}) 132 | // Fatalf logs messages at FATAL level. 133 | Fatalf(format string, args ...interface{}) 134 | } 135 | 136 | // Error prints err if it's not nil. 137 | func Error(err error) { 138 | if err != nil { 139 | defaultLogger.Errorf("error occurs during runtime, %v", err) 140 | } 141 | } 142 | 143 | // Debugf logs messages at DEBUG level. 144 | func Debugf(format string, args ...interface{}) { 145 | defaultLogger.Debugf(format, args...) 146 | } 147 | 148 | // Infof logs messages at INFO level. 149 | func Infof(format string, args ...interface{}) { 150 | defaultLogger.Infof(format, args...) 151 | } 152 | 153 | // Warnf logs messages at WARN level. 154 | func Warnf(format string, args ...interface{}) { 155 | defaultLogger.Warnf(format, args...) 156 | } 157 | 158 | // Errorf logs messages at ERROR level. 159 | func Errorf(format string, args ...interface{}) { 160 | defaultLogger.Errorf(format, args...) 161 | } 162 | 163 | // Fatalf logs messages at FATAL level. 164 | func Fatalf(format string, args ...interface{}) { 165 | defaultLogger.Fatalf(format, args...) 166 | } 167 | -------------------------------------------------------------------------------- /codec/smgp/header.go: -------------------------------------------------------------------------------- 1 | package smgp 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | ) 7 | 8 | type MessageHeader struct { 9 | PacketLength uint32 10 | RequestId uint32 11 | SequenceId uint32 12 | } 13 | 14 | func (header *MessageHeader) Encode() []byte { 15 | if header.PacketLength < HeadLength { 16 | header.PacketLength = HeadLength 17 | } 18 | frame := make([]byte, header.PacketLength) 19 | binary.BigEndian.PutUint32(frame[0:4], header.PacketLength) 20 | binary.BigEndian.PutUint32(frame[4:8], header.RequestId) 21 | binary.BigEndian.PutUint32(frame[8:12], header.SequenceId) 22 | return frame 23 | } 24 | 25 | func (header *MessageHeader) Decode(frame []byte) error { 26 | if len(frame) < HeadLength { 27 | return ErrorPacket 28 | } 29 | header.PacketLength = binary.BigEndian.Uint32(frame[0:4]) 30 | header.RequestId = binary.BigEndian.Uint32(frame[4:8]) 31 | header.SequenceId = binary.BigEndian.Uint32(frame[8:12]) 32 | return nil 33 | } 34 | 35 | func (header *MessageHeader) String() string { 36 | return fmt.Sprintf("{ PacketLength: %d, RequestId: %s, SequenceId: %d }", header.PacketLength, CommandMap[header.RequestId], header.SequenceId) 37 | } 38 | 39 | const ( 40 | HeadLength = 12 // 报文头长度 41 | CmdLogin = uint32(0x00000001) // 客户端登录 42 | CmdLoginResp = uint32(0x80000001) // 客户端登录应答 43 | CmdSubmit = uint32(0x00000002) // 提交短消息 44 | CmdSubmitResp = uint32(0x80000002) // 提交短消息应答 45 | CmdDeliver = uint32(0x00000003) // 下发短消息 46 | CmdDeliverResp = uint32(0x80000003) // 下发短消息应答 47 | CmdActiveTest = uint32(0x00000004) // 链路检测 48 | CmdActiveTestResp = uint32(0x80000004) // 链路检测应答 49 | CmdExit = uint32(0x00000006) // 退出请求 50 | CmdExitResp = uint32(0x80000006) // 退出应答 51 | // Forward = uint32(0x00000005) // 短消息前转 52 | // Forward_Resp = uint32(0x80000005) // 短消息前转应答 53 | // Query = uint32(0x00000007) // SP统计查询 54 | // Query_Resp = uint32(0x80000007) // SP统计查询应答 55 | // Query_TE_Route = uint32(0x00000008) // 查询TE路由 56 | // Query_TE_Route_Resp = uint32(0x80000008) // 查询TE路由应答 57 | // Query_SP_Route = uint32(0x00000009) // 查询SP路由 58 | // Query_SP_Route_Resp = uint32(0x80000009) // 查询SP路由应答 59 | // Payment_Request = uint32(0x0000000A) // 扣款请求(用于预付费系统,参见增值业务计费方案) 60 | // Payment_Request_Resp = uint32(0x8000000A) // 扣款请求响应(用于预付费系统,参见增值业务计费方案,下同) 61 | // Payment_Affirm = uint32(0x0000000B) // 扣款确认(用于预付费系统,参见增值业务计费方案) 62 | // Payment_Affirm_Resp = uint32(0x8000000B) // 扣款确认响应(用于预付费系统,参见增值业务计费方案) 63 | // Query_UserState = uint32(0x0000000C) // 查询用户状态(用于预付费系统,参见增值业务计费方案) 64 | // Query_UserState_Resp = uint32(0x8000000C) // 查询用户状态响应(用于预付费系统,参见增值业务计费方案) 65 | // Get_All_TE_Route = uint32(0x0000000D) // 获取所有终端路由 66 | // Get_All_TE_Route_Resp = uint32(0x8000000D) // 获取所有终端路由应答 67 | // Get_All_SP_Route = uint32(0x0000000E) // 获取所有SP路由 68 | // Get_All_SP_Route_Resp = uint32(0x8000000E) // 获取所有SP路由应答 69 | // Update_TE_Route = uint32(0x0000000F) // SMGW向GNS更新终端路由 70 | // Update_TE_Route_Resp = uint32(0x8000000F) // SMGW向GNS更新终端路由应答 71 | // Update_SP_Route = uint32(0x00000010) // SMGW向GNS更新SP路由 72 | // Update_SP_Route_Resp = uint32(0x80000010) // SMGW向GNS更新SP路由应答 73 | // Push_Update_TE_Route = uint32(0x00000011) // GNS向SMGW更新终端路由 74 | // Push_Update_TE_Route_Resp = uint32(0x80000011) // GNS向SMGW更新终端路由应答 75 | // Push_Update_SP_Route = uint32(0x00000012) // GNS向SMGW更新SP路由 76 | // Push_Update_SP_Route_Resp = uint32(0x80000012) // GNS向SMGW更新SP路由应答 77 | ) 78 | 79 | var CommandMap = make(map[uint32]string) 80 | 81 | func init() { 82 | CommandMap[CmdLogin] = "Login" 83 | CommandMap[CmdLoginResp] = "Login_Resp" 84 | CommandMap[CmdSubmit] = "Submit" 85 | CommandMap[CmdSubmitResp] = "Submit_Resp" 86 | CommandMap[CmdDeliver] = "Deliver" 87 | CommandMap[CmdDeliverResp] = "Deliver_Resp" 88 | CommandMap[CmdActiveTest] = "Active_Test" 89 | CommandMap[CmdActiveTestResp] = "Active_Test_Resp" 90 | CommandMap[CmdExit] = "Exit" 91 | CommandMap[CmdExitResp] = "Exit_Resp" 92 | // CommandMap[Forward] = "Forward" 93 | // CommandMap[Forward_Resp] = "Forward_Resp" 94 | // CommandMap[Query] = "Query" 95 | // CommandMap[Query_Resp] = "Query_Resp" 96 | // CommandMap[Query_TE_Route] = "Query_TE_Route" 97 | // CommandMap[Query_TE_Route_Resp] = "Query_TE_Route_Resp" 98 | // CommandMap[Query_SP_Route] = "Query_SP_Route" 99 | // CommandMap[Query_SP_Route_Resp] = "Query_SP_Route_Resp" 100 | // CommandMap[Payment_Request] = "Payment_Request" 101 | // CommandMap[Payment_Request_Resp] = "Payment_Request_Resp" 102 | // CommandMap[Payment_Affirm] = "Payment_Affirm" 103 | // CommandMap[Payment_Affirm_Resp] = "Payment_Affirm_Resp" 104 | // CommandMap[Query_UserState] = "Query_UserState" 105 | // CommandMap[Query_UserState_Resp] = "Query_UserState_Resp" 106 | // CommandMap[Get_All_TE_Route] = "Get_All_TE_Route" 107 | // CommandMap[Get_All_TE_Route_Resp] = "Get_All_TE_Route_Resp" 108 | // CommandMap[Get_All_SP_Route] = "Get_All_SP_Route" 109 | // CommandMap[Get_All_SP_Route_Resp] = "Get_All_SP_Route_Resp" 110 | // CommandMap[Update_TE_Route] = "Update_TE_Route" 111 | // CommandMap[Update_TE_Route_Resp] = "Update_TE_Route_Resp" 112 | // CommandMap[Update_SP_Route] = "Update_SP_Route" 113 | // CommandMap[Update_SP_Route_Resp] = "Update_SP_Route_Resp" 114 | // CommandMap[Push_Update_TE_Route] = "Push_Update_TE_Route" 115 | // CommandMap[Push_Update_TE_Route_Resp] = "Push_Update_TE_Route_Resp" 116 | // CommandMap[Push_Update_SP_Route] = "Push_Update_SP_Route" 117 | // CommandMap[Push_Update_SP_Route_Resp] = "Push_Update_SP_Route_Resp" 118 | } 119 | -------------------------------------------------------------------------------- /codec/smgp/login.go: -------------------------------------------------------------------------------- 1 | package smgp 2 | 3 | import ( 4 | "bytes" 5 | "crypto/md5" 6 | "encoding/binary" 7 | "fmt" 8 | "strconv" 9 | "time" 10 | ) 11 | 12 | type Login struct { 13 | *MessageHeader // 【12字节】消息头 14 | clientID string // 【8字节】客户端用来登录服务器端的用户账号。 15 | authenticatorClient []byte // 【16字节】客户端认证码,用来鉴别客户端的合法性。 16 | loginMode byte // 【1字节】客户端用来登录服务器端的登录类型。 17 | timestamp uint32 // 【4字节】时间戳 18 | version byte // 【1字节】客户端支持的协议版本号 19 | } 20 | type LoginResp struct { 21 | *MessageHeader // 协议头, 12字节 22 | status uint32 // 状态码,4字节 23 | authenticatorServer []byte // 认证串,16字节 24 | version byte // 版本,1字节 25 | } 26 | 27 | const ( 28 | LoginLen = 42 29 | LoginRespLen = 33 30 | ) 31 | 32 | func NewLogin() *Login { 33 | lo := &Login{} 34 | header := &MessageHeader{} 35 | header.PacketLength = LoginLen 36 | header.RequestId = CmdLogin 37 | header.SequenceId = uint32(Seq32.NextVal()) 38 | lo.MessageHeader = header 39 | lo.clientID = Conf.GetString("client-id") 40 | lo.loginMode = 2 41 | ts, _ := strconv.ParseUint(time.Now().Format("0102150405"), 10, 32) 42 | lo.timestamp = uint32(ts) 43 | // TODO TEST ONLY 44 | // lo.timestamp = uint32(705192634) 45 | ss := reqAuthMd5(lo) 46 | lo.authenticatorClient = ss[:] 47 | lo.version = byte(Conf.GetInt("version")) 48 | return lo 49 | } 50 | 51 | func (lo *Login) Encode() []byte { 52 | frame := lo.MessageHeader.Encode() 53 | if len(frame) == LoginLen && lo.PacketLength == LoginLen { 54 | copy(frame[12:20], lo.clientID) 55 | copy(frame[20:36], lo.authenticatorClient) 56 | frame[36] = lo.loginMode 57 | binary.BigEndian.PutUint32(frame[37:41], lo.timestamp) 58 | frame[41] = lo.version 59 | } 60 | return frame 61 | } 62 | 63 | func (lo *Login) Decode(header *MessageHeader, frame []byte) error { 64 | // check 65 | if header == nil || header.RequestId != CmdLogin || len(frame) < (LoginLen-HeadLength) { 66 | return ErrorPacket 67 | } 68 | lo.MessageHeader = header 69 | lo.clientID = string(frame[0:8]) 70 | lo.authenticatorClient = frame[8:24] 71 | lo.loginMode = frame[24] 72 | lo.timestamp = binary.BigEndian.Uint32(frame[25:29]) 73 | lo.version = frame[29] 74 | return nil 75 | } 76 | 77 | func (lo *Login) String() string { 78 | return fmt.Sprintf("{ Header: %s, clientID: %s, authenticatorClient: %x, logoinMode: %x, timestamp: %010d, version: %#x }", 79 | lo.MessageHeader, lo.clientID, lo.authenticatorClient, lo.loginMode, lo.timestamp, lo.version) 80 | } 81 | 82 | func (lo *Login) Check() uint32 { 83 | // 大版本不匹配 84 | if lo.version&0xf0 != byte(Conf.GetInt("version"))&0xf0 { 85 | return 22 86 | } 87 | 88 | authSource := lo.authenticatorClient 89 | authMd5 := reqAuthMd5(lo) 90 | log.Debugf("[AuthCheck] input : %x", authSource) 91 | log.Debugf("[AuthCheck] compute: %x", authMd5) 92 | i := bytes.Compare(authSource, authMd5[:]) 93 | // 配置不做校验或校验通过时返回0 94 | if !Conf.GetBool("auth-check") || i == 0 { 95 | return 0 96 | } 97 | return 21 98 | } 99 | 100 | func (lo *Login) ToResponse(code uint32) interface{} { 101 | response := &LoginResp{} 102 | header := &MessageHeader{} 103 | header.PacketLength = LoginRespLen 104 | header.RequestId = CmdLoginResp 105 | header.SequenceId = lo.SequenceId 106 | response.MessageHeader = header 107 | if code == 0 { 108 | response.status = lo.Check() 109 | } else { 110 | response.status = code 111 | } 112 | authDt := make([]byte, 0, 64) 113 | authDt = append(authDt, fmt.Sprintf("%d", response.status)...) 114 | authDt = append(authDt, lo.authenticatorClient...) 115 | authDt = append(authDt, Conf.GetString("shared-secret")...) 116 | auth := md5.Sum(authDt) 117 | response.authenticatorServer = auth[:] 118 | response.version = byte(Conf.GetInt("version")) 119 | return response 120 | } 121 | 122 | func reqAuthMd5(connect *Login) [16]byte { 123 | authDt := make([]byte, 0, 64) 124 | authDt = append(authDt, Conf.GetString("client-id")...) 125 | authDt = append(authDt, 0, 0, 0, 0, 0, 0, 0) 126 | authDt = append(authDt, Conf.GetString("shared-secret")...) 127 | authDt = append(authDt, fmt.Sprintf("%010d", connect.timestamp)...) 128 | log.Debugf("[AuthCheck] auth data: %x", authDt) 129 | authMd5 := md5.Sum(authDt) 130 | return authMd5 131 | } 132 | 133 | func (resp *LoginResp) Encode() []byte { 134 | frame := resp.MessageHeader.Encode() 135 | var index int 136 | if len(frame) == int(resp.PacketLength) { 137 | index = 12 138 | binary.BigEndian.PutUint32(frame[index:index+4], resp.status) 139 | index += 4 140 | copy(frame[index:index+16], resp.authenticatorServer) 141 | index += 16 142 | frame[index] = resp.version 143 | } 144 | return frame 145 | } 146 | 147 | func (resp *LoginResp) Decode(header *MessageHeader, frame []byte) error { 148 | // check 149 | if header == nil || header.RequestId != CmdLoginResp || len(frame) < (LoginRespLen-HeadLength) { 150 | return ErrorPacket 151 | } 152 | var index int 153 | resp.MessageHeader = header 154 | resp.status = binary.BigEndian.Uint32(frame[0 : index+4]) 155 | index = 4 156 | resp.authenticatorServer = frame[index : index+16] 157 | index += 16 158 | resp.version = frame[index] 159 | return nil 160 | } 161 | 162 | func (resp *LoginResp) String() string { 163 | return fmt.Sprintf("{ Header: %s, status: {%d: %s}, authenticatorISMG: %x, version: %#x }", 164 | resp.MessageHeader, resp.status, ConnectStatusMap[resp.status], resp.authenticatorServer, resp.version) 165 | } 166 | 167 | func (resp *LoginResp) Status() uint32 { 168 | return resp.status 169 | } 170 | 171 | var ConnectStatusMap = map[uint32]string{ 172 | 0: "成功", 173 | 1: "系统忙", 174 | 2: "超过最大连接数", 175 | 10: "消息结构错", 176 | 11: "命令字错", 177 | 12: "序列号重复", 178 | 20: "IP地址错", 179 | 21: "认证错", 180 | 22: "版本太高", 181 | 30: "非法消息类型(MsgType)", 182 | 31: "非法优先级(Priority)", 183 | 32: "非法资费类型(FeeType)", 184 | 33: "非法资费代码(FeeCode)", 185 | } 186 | -------------------------------------------------------------------------------- /codec/smgp/deliver.go: -------------------------------------------------------------------------------- 1 | package smgp 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "strings" 7 | "time" 8 | 9 | "github.com/aaronwong1989/gosms/comm" 10 | ) 11 | 12 | type Deliver struct { 13 | *MessageHeader 14 | msgId []byte // 【10字节】短消息流水号 15 | isReport byte // 【1字节】是否为状态报告 16 | msgFormat byte // 【1字节】短消息格式 17 | recvTime string // 【14字节】短消息定时发送时间 18 | srcTermID string // 【21字节】短信息发送方号码 19 | destTermID string // 【21】短消息接收号码 20 | msgLength byte // 【1字节】短消息长度 21 | msgContent string // 【MsgLength字节】短消息内容 22 | msgBytes []byte // 消息内容按照Msg_Fmt编码后的数据 23 | report *Report // 状态报告 24 | reserve string // 【8字节】保留 25 | tlvList *comm.TlvList // 【TLV】可选项参数 26 | } 27 | 28 | type DeliverResp struct { 29 | *MessageHeader 30 | msgId []byte // 【10字节】短消息流水号 31 | status uint32 32 | } 33 | 34 | func NewDeliver(srcNo string, destNo string, txt string) *Deliver { 35 | baseLen := uint32(89) 36 | head := &MessageHeader{PacketLength: baseLen, RequestId: CmdDeliver, SequenceId: uint32(Seq32.NextVal())} 37 | dlv := &Deliver{MessageHeader: head} 38 | dlv.msgId = Seq80.NextVal() 39 | dlv.isReport = 0 40 | dlv.msgFormat = 15 41 | dlv.recvTime = time.Now().Format("20060102150405") 42 | dlv.srcTermID = srcNo 43 | dlv.destTermID = Conf.GetString("sms-display-no") + destNo 44 | // 上行最长70字符 45 | subTxt := txt 46 | rs := []rune(txt) 47 | if len(rs) > 70 { 48 | rs = rs[:70] 49 | subTxt = string(rs) 50 | } 51 | gbs, _ := GbEncoder.String(subTxt) 52 | msg := []byte(gbs) 53 | dlv.msgBytes = msg 54 | dlv.msgLength = byte(len(msg)) 55 | dlv.msgContent = subTxt 56 | dlv.PacketLength = baseLen + uint32(dlv.msgLength) 57 | return dlv 58 | } 59 | 60 | func NewDeliveryReport(mt *Submit, msgId []byte) *Deliver { 61 | baseLen := uint32(89) 62 | head := &MessageHeader{PacketLength: baseLen, RequestId: CmdDeliver, SequenceId: uint32(Seq32.NextVal())} 63 | dlv := &Deliver{MessageHeader: head} 64 | rpt := NewReport(msgId) 65 | dlv.msgId = Seq80.NextVal() 66 | dlv.report = rpt 67 | dlv.msgLength = 115 68 | dlv.isReport = 1 69 | dlv.msgFormat = 0 70 | dlv.recvTime = time.Now().Format("20060102150405") 71 | dlv.srcTermID = mt.destTermID[0] 72 | dlv.destTermID = mt.srcTermID 73 | dlv.PacketLength = baseLen + uint32(RptLen) 74 | return dlv 75 | } 76 | 77 | func (dlv *Deliver) Encode() []byte { 78 | frame := dlv.MessageHeader.Encode() 79 | index := 12 80 | copy(frame[index:index+10], dlv.msgId) 81 | index += 10 82 | index = comm.CopyByte(frame, dlv.isReport, index) 83 | index = comm.CopyByte(frame, dlv.msgFormat, index) 84 | index = comm.CopyStr(frame, dlv.recvTime, index, 14) 85 | index = comm.CopyStr(frame, dlv.srcTermID, index, 21) 86 | index = comm.CopyStr(frame, dlv.destTermID, index, 21) 87 | index = comm.CopyByte(frame, dlv.msgLength, index) 88 | if dlv.IsReport() && dlv.report != nil { 89 | rts := dlv.report.Encode() 90 | copy(frame[index:index+RptLen], rts) 91 | index += RptLen 92 | } else { 93 | copy(frame[index:index+int(dlv.msgLength)], dlv.msgBytes) 94 | index += int(dlv.msgLength) 95 | } 96 | index = comm.CopyStr(frame, dlv.reserve, index, 8) 97 | return frame 98 | } 99 | 100 | func (dlv *Deliver) Decode(header *MessageHeader, frame []byte) error { 101 | // check 102 | if header == nil || header.RequestId != CmdDeliver || uint32(len(frame)) < (header.PacketLength-HeadLength) { 103 | return ErrorPacket 104 | } 105 | dlv.MessageHeader = header 106 | var index int 107 | dlv.msgId = frame[index : index+10] 108 | index += 10 109 | dlv.isReport = frame[index] 110 | index += 1 111 | dlv.msgFormat = frame[index] 112 | index += 1 113 | dlv.recvTime = comm.TrimStr(frame[index : index+14]) 114 | index += 14 115 | dlv.srcTermID = comm.TrimStr(frame[index : index+21]) 116 | index += 21 117 | dlv.destTermID = comm.TrimStr(frame[index : index+21]) 118 | index += 21 119 | dlv.msgLength = frame[index] 120 | index += 1 121 | if dlv.IsReport() { 122 | dlv.report = &Report{} 123 | err := dlv.report.Decode(frame[index : index+RptLen]) 124 | if err != nil { 125 | return err 126 | } 127 | } else { 128 | bytes, err := GbDecoder.Bytes(frame[index : index+int(dlv.msgLength)]) 129 | if err != nil { 130 | return err 131 | } 132 | dlv.msgContent = string(bytes) 133 | } 134 | // 后续字节不解析了 135 | return nil 136 | } 137 | 138 | func (dlv *Deliver) ToResponse(code uint32) interface{} { 139 | header := *dlv.MessageHeader 140 | header.RequestId = CmdDeliverResp 141 | header.PacketLength = 26 142 | resp := &DeliverResp{MessageHeader: &header} 143 | resp.status = code 144 | resp.msgId = Seq80.NextVal() 145 | return resp 146 | } 147 | 148 | func (dlv *Deliver) String() string { 149 | content := "" 150 | if dlv.IsReport() { 151 | content = dlv.report.String() 152 | } else { 153 | content = strings.ReplaceAll(dlv.msgContent, "\n", " ") 154 | } 155 | return fmt.Sprintf("{ header: %v, msgId: %x, isReport: %v, msgFormat: %v, recvTime: %v,"+ 156 | " SrcTermID: %v, destTermID: %v, msgLength: %v, "+ 157 | "msgContent: \"%s\", reserve: %v, tlv: %v }", 158 | dlv.MessageHeader, dlv.msgId, dlv.isReport, dlv.msgFormat, dlv.recvTime, 159 | dlv.srcTermID, dlv.destTermID, dlv.msgLength, 160 | content, dlv.reserve, dlv.tlvList, 161 | ) 162 | } 163 | 164 | func (dlv *Deliver) IsReport() bool { 165 | return dlv.isReport == 1 166 | } 167 | 168 | func (r *DeliverResp) Encode() []byte { 169 | frame := r.MessageHeader.Encode() 170 | index := 12 171 | copy(frame[index:index+10], r.msgId) 172 | index += 10 173 | binary.BigEndian.PutUint32(frame[index:index+4], r.status) 174 | return frame 175 | } 176 | 177 | func (r *DeliverResp) Decode(header *MessageHeader, frame []byte) error { 178 | // check 179 | if header == nil || header.RequestId != CmdDeliverResp || uint32(len(frame)) < (header.PacketLength-HeadLength) { 180 | return ErrorPacket 181 | } 182 | r.MessageHeader = header 183 | r.msgId = make([]byte, 10) 184 | copy(r.msgId, frame[0:10]) 185 | r.status = binary.BigEndian.Uint32(frame[10:14]) 186 | return nil 187 | } 188 | 189 | func (r *DeliverResp) String() string { 190 | return fmt.Sprintf("{ header: %s, msgId: %x, status: {%d:%s} }", r.MessageHeader, r.msgId, r.status, StatMap[r.status]) 191 | } 192 | 193 | func (r *DeliverResp) MsgId() string { 194 | return fmt.Sprintf("%x", r.msgId) 195 | } 196 | -------------------------------------------------------------------------------- /codec/cmpp/connect.go: -------------------------------------------------------------------------------- 1 | package cmpp 2 | 3 | import ( 4 | "bytes" 5 | "crypto/md5" 6 | "encoding/binary" 7 | "fmt" 8 | "strconv" 9 | "time" 10 | ) 11 | 12 | type Connect struct { 13 | *MessageHeader // +12 = 12:消息头 14 | sourceAddr string // +6 = 18:源地址,此处为 SP_Id 15 | authenticatorSource []byte // +16 = 34: 用于鉴别源地址。其值通过单向 MD5 hash 计算得出,表示如下: authenticatorSource = MD5(Source_Addr+9 字节的 0 +shared secret+timestamp) Shared secret 由中国移动与源地址实 体事先商定,timestamp 格式为: MMDDHHMMSS,即月日时分秒,10 位。 16 | version uint8 // +1 = 35:双方协商的版本号(高位 4bit 表示主 版本号,低位 4bit 表示次版本号),对 于3.0的版本,高4bit为3,低4位为 0 17 | timestamp uint32 // +4 = 39:时间戳的明文,由客户端产生,格式为 MMDDHHMMSS,即月日时分秒,10 位数字的整型,右对齐。 18 | } 19 | 20 | type ConnectResp struct { 21 | *MessageHeader // 协议头, 12字节 22 | status uint32 // 状态码,3.0版本4字节,2.0版本1字节 23 | authenticatorISMG []byte // 认证串,16字节 24 | version uint8 // 版本,1字节 25 | } 26 | 27 | func NewConnect() *Connect { 28 | con := &Connect{} 29 | header := &MessageHeader{} 30 | header.TotalLength = 39 31 | header.CommandId = CMPP_CONNECT 32 | header.SequenceId = uint32(Seq32.NextVal()) 33 | con.MessageHeader = header 34 | con.version = byte(Conf.GetInt("version")) 35 | con.sourceAddr = Conf.GetString("source-addr") 36 | ts, _ := strconv.ParseUint(time.Now().Format("0102150405"), 10, 32) 37 | con.timestamp = uint32(ts) 38 | // TODO TEST ONLY 39 | // con.timestamp = uint32(705192634) 40 | ss := reqAuthMd5(con) 41 | con.authenticatorSource = ss[:] 42 | return con 43 | } 44 | 45 | func (connect *Connect) Encode() []byte { 46 | frame := connect.MessageHeader.Encode() 47 | if len(frame) == 39 && connect.TotalLength == 39 { 48 | copy(frame[12:18], connect.sourceAddr) 49 | copy(frame[18:34], connect.authenticatorSource) 50 | frame[34] = connect.version 51 | binary.BigEndian.PutUint32(frame[35:39], connect.timestamp) 52 | } 53 | return frame 54 | } 55 | 56 | func (connect *Connect) Decode(header *MessageHeader, frame []byte) error { 57 | // check 58 | if header == nil || header.CommandId != CMPP_CONNECT || len(frame) < (39-HeadLength) { 59 | return ErrorPacket 60 | } 61 | connect.MessageHeader = header 62 | connect.sourceAddr = string(frame[0:6]) 63 | connect.authenticatorSource = frame[6:22] 64 | connect.version = frame[22] 65 | connect.timestamp = binary.BigEndian.Uint32(frame[23:27]) 66 | return nil 67 | } 68 | 69 | func (connect *Connect) String() string { 70 | return fmt.Sprintf("{ Header: %s, sourceAddr: %s, authenticatorSource: %x, version: %#x, timestamp: %010d }", 71 | connect.MessageHeader, connect.sourceAddr, connect.authenticatorSource, connect.version, connect.timestamp) 72 | } 73 | 74 | func (connect *Connect) Check() uint32 { 75 | if connect.version&0xf0 != byte(Conf.GetInt("version"))&0xf0 { 76 | return 4 77 | } 78 | 79 | authSource := connect.authenticatorSource 80 | authMd5 := reqAuthMd5(connect) 81 | log.Debugf("[AuthCheck] input : %x", authSource) 82 | log.Debugf("[AuthCheck] compute: %x", authMd5) 83 | i := bytes.Compare(authSource, authMd5[:]) 84 | // 配置不做校验或校验通过时返回0 85 | if !Conf.GetBool("auth-check") || i == 0 { 86 | return 0 87 | } 88 | return 3 89 | } 90 | 91 | func (connect *Connect) ToResponse(code uint32) interface{} { 92 | response := &ConnectResp{} 93 | header := &MessageHeader{} 94 | // 3.x 与 2.x Status长度不同 95 | if V3() { 96 | header.TotalLength = 33 97 | } else { 98 | header.TotalLength = 30 99 | } 100 | header.CommandId = CMPP_CONNECT_RESP 101 | header.SequenceId = connect.SequenceId 102 | response.MessageHeader = header 103 | if code == 0 { 104 | response.status = connect.Check() 105 | } else { 106 | response.status = code 107 | } 108 | // authenticatorISMG =MD5 ( status+authenticatorSource+shar ed secret) 109 | authDt := make([]byte, 0, 64) 110 | authDt = append(authDt, fmt.Sprintf("%d", response.status)...) 111 | authDt = append(authDt, connect.authenticatorSource...) 112 | authDt = append(authDt, Conf.GetString("shared-secret")...) 113 | auth := md5.Sum(authDt) 114 | response.authenticatorISMG = auth[:] 115 | response.version = byte(Conf.GetInt("version")) 116 | return response 117 | } 118 | 119 | func reqAuthMd5(connect *Connect) [16]byte { 120 | // authenticatorSource = MD5(Source_Addr+9 字节的 0 +shared secret+timestamp) 121 | // timestamp 格式为: MMDDHHMMSS,即月日时分秒,10 位。 122 | authDt := make([]byte, 0, 64) 123 | authDt = append(authDt, Conf.GetString("source-addr")...) 124 | authDt = append(authDt, 0, 0, 0, 0, 0, 0, 0, 0, 0) 125 | authDt = append(authDt, Conf.GetString("shared-secret")...) 126 | authDt = append(authDt, fmt.Sprintf("%010d", connect.timestamp)...) 127 | log.Debugf("[AuthCheck] auth data: %x", authDt) 128 | authMd5 := md5.Sum(authDt) 129 | return authMd5 130 | } 131 | 132 | func (resp *ConnectResp) Encode() []byte { 133 | frame := resp.MessageHeader.Encode() 134 | var index int 135 | if len(frame) == int(resp.TotalLength) { 136 | index = 12 137 | if V3() { 138 | binary.BigEndian.PutUint32(frame[index:index+4], resp.status) 139 | index += 4 140 | } else { 141 | frame[index] = byte(resp.status) 142 | index++ 143 | } 144 | copy(frame[index:index+16], resp.authenticatorISMG) 145 | index += 16 146 | frame[index] = resp.version 147 | } 148 | return frame 149 | } 150 | 151 | func (resp *ConnectResp) Decode(header *MessageHeader, frame []byte) error { 152 | bodyLen := 30 153 | if V3() { 154 | bodyLen = 33 155 | } 156 | // check 157 | if header == nil || header.CommandId != CMPP_CONNECT_RESP || len(frame) < (bodyLen-HeadLength) { 158 | return ErrorPacket 159 | } 160 | var index int 161 | resp.MessageHeader = header 162 | if V3() { 163 | resp.status = binary.BigEndian.Uint32(frame[0 : index+4]) 164 | index = 4 165 | } else { 166 | resp.status = uint32(frame[0]) 167 | index = 1 168 | } 169 | resp.authenticatorISMG = frame[index : index+16] 170 | index += 16 171 | resp.version = frame[index] 172 | return nil 173 | } 174 | 175 | func (resp *ConnectResp) String() string { 176 | return fmt.Sprintf("{ Header: %s, status: {%d: %s}, authenticatorISMG: %x, version: %#x }", 177 | resp.MessageHeader, resp.status, ConnectStatusMap[resp.status], resp.authenticatorISMG, resp.version) 178 | } 179 | 180 | func (resp *ConnectResp) Status() uint32 { 181 | return resp.status 182 | } 183 | 184 | var ConnectStatusMap = map[uint32]string{ 185 | 0: "成功", 186 | 1: "消息结构错", 187 | 2: "非法源地址", 188 | 3: "认证错", 189 | 4: "版本太高", 190 | 5: "其他错误", 191 | } 192 | -------------------------------------------------------------------------------- /codec/cmpp/delivery.go: -------------------------------------------------------------------------------- 1 | package cmpp 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | // Delivery 上行短信或状态报告,不支持长短信 10 | type Delivery struct { 11 | *MessageHeader 12 | 13 | msgId uint64 // 信息标识 14 | destId string // 目的号码 21 15 | serviceId string // 业务标识,是数字、字母和符号的组合。 10 16 | tpPid uint8 // 见Submit 17 | tpUdhi uint8 // 见Submit 18 | msgFmt uint8 // 见Submit 19 | srcTerminalId string // 源终端MSISDN号码(状态报告时填为CMPP_SUBMIT消息的目的终端号码) 20 | srcTerminalType uint8 // 源终端号码类型,0:真实号码;1:伪码 21 | registeredDelivery uint8 // 是否为状态报告 22 | msgLength uint8 // 消息长度 23 | msgContent string // 非状态报告的消息内容 24 | report *Report // 状态报告的消息内容 25 | linkID string // 点播业务使用的LinkID,非点播类业务的MT流程不使用该字段 26 | } 27 | 28 | func NewDelivery(phone string, msg string, dest string, serviceId string) *Delivery { 29 | dly := &Delivery{} 30 | dly.srcTerminalId = phone 31 | dly.srcTerminalType = 0 32 | setMsgContent(dly, msg) 33 | 34 | if dest != "" { 35 | dly.destId = dest 36 | } else { 37 | dly.destId = Conf.GetString("sms-display-no") 38 | } 39 | if serviceId != "" { 40 | dly.serviceId = serviceId 41 | } else { 42 | dly.serviceId = Conf.GetString("service-id") 43 | } 44 | baseLen := uint32(85) 45 | if V3() { 46 | baseLen = 109 47 | } 48 | header := MessageHeader{ 49 | TotalLength: baseLen + uint32(dly.msgLength), 50 | CommandId: CMPP_DELIVER, 51 | SequenceId: uint32(Seq32.NextVal())} 52 | dly.MessageHeader = &header 53 | return dly 54 | } 55 | 56 | func (d *Delivery) Encode() []byte { 57 | frame := d.MessageHeader.Encode() 58 | binary.BigEndian.PutUint64(frame[12:20], d.msgId) 59 | copy(frame[20:41], d.destId) 60 | copy(frame[41:51], d.serviceId) 61 | frame[51] = d.tpPid 62 | frame[52] = d.tpUdhi 63 | frame[53] = d.msgFmt 64 | index := 54 65 | if V3() { 66 | copy(frame[index:index+32], d.srcTerminalId) 67 | index += 32 68 | frame[index] = d.srcTerminalType 69 | index++ 70 | } else { 71 | copy(frame[index:index+21], d.srcTerminalId) 72 | index += 21 73 | } 74 | frame[index] = d.registeredDelivery 75 | index++ 76 | frame[index] = d.msgLength 77 | index++ 78 | l := int(d.msgLength) 79 | if d.registeredDelivery == 1 { 80 | // 状态报告 81 | copy(frame[index:index+l], d.report.Encode()) 82 | } else { 83 | // 上行短信,不支持长短信,固定选用第一片 (New时需处理) 84 | slices := MsgSlices(d.msgFmt, d.msgContent) 85 | // 不支持长短信,固定选用第一片 86 | content := slices[0] 87 | copy(frame[index:index+l], content) 88 | } 89 | index += l 90 | if V3() { 91 | copy(frame[index:index+20], d.linkID) 92 | } 93 | 94 | return frame 95 | } 96 | 97 | func (d *Delivery) Decode(header *MessageHeader, frame []byte) error { 98 | if header == nil || header.CommandId != CMPP_DELIVER || uint32(len(frame)) < (header.TotalLength-HeadLength) { 99 | return ErrorPacket 100 | } 101 | d.MessageHeader = header 102 | d.msgId = binary.BigEndian.Uint64(frame[0:8]) 103 | d.destId = TrimStr(frame[8:29]) 104 | d.destId = TrimStr(frame[29:39]) 105 | d.tpPid = frame[39] 106 | d.tpUdhi = frame[40] 107 | d.msgFmt = frame[41] 108 | index := 42 109 | if V3() { 110 | d.srcTerminalId = TrimStr(frame[index : index+32]) 111 | index += 32 112 | d.srcTerminalType = frame[index] 113 | index++ 114 | } else { 115 | d.srcTerminalId = TrimStr(frame[index : index+21]) 116 | index += 21 117 | } 118 | d.registeredDelivery = frame[index] 119 | index++ 120 | d.msgLength = frame[index] 121 | index++ 122 | l := int(d.msgLength) 123 | if d.registeredDelivery == 1 { 124 | rpt := &Report{} 125 | err := rpt.Decode(frame[index : index+l]) 126 | if err != nil { 127 | return err 128 | } 129 | d.report = rpt 130 | } else { 131 | d.msgContent = TrimStr(frame[index : index+l]) 132 | } 133 | index += l 134 | if V3() { 135 | d.linkID = TrimStr(frame[index : index+20]) 136 | } 137 | return nil 138 | } 139 | 140 | func (d *Delivery) ToResponse(code uint32) interface{} { 141 | header := *d.MessageHeader 142 | dr := &DeliveryResp{} 143 | dr.MessageHeader = &header 144 | dr.TotalLength = HeadLength + 9 145 | if V3() { 146 | dr.TotalLength = HeadLength + 12 147 | } 148 | dr.CommandId = CMPP_DELIVER_RESP 149 | dr.msgId = d.msgId 150 | dr.result = code 151 | return dr 152 | } 153 | 154 | func (d *Delivery) String() string { 155 | var content string 156 | if d.registeredDelivery == 1 { 157 | content = d.report.String() 158 | } else { 159 | content = strings.ReplaceAll(d.msgContent, "\n", " ") 160 | } 161 | 162 | return fmt.Sprintf("{ header:%s, msgId: %d, destId: %v, serviceId: %v, tpPid: %d, tpUdhi: %d, msgFmt: %d, "+ 163 | "srcTerminalId: %v, srcTerminalType: %d, registeredDelivery: %d, "+ 164 | "msgLength: %d, setMsgContent: %v, linkID: %v }", 165 | d.MessageHeader, 166 | d.msgId, d.destId, d.serviceId, d.tpPid, d.tpUdhi, d.msgFmt, 167 | d.srcTerminalId, d.srcTerminalType, d.registeredDelivery, 168 | d.msgLength, content, d.linkID, 169 | ) 170 | } 171 | 172 | func setMsgContent(dly *Delivery, msg string) { 173 | dly.msgFmt = MsgFmt(msg) 174 | var l int 175 | if dly.msgFmt == 8 { 176 | l = 2 * len([]rune(msg)) 177 | if l > 140 { 178 | // 只取前70个字符 179 | rs := []rune(msg) 180 | msg = string(rs[:70]) 181 | l = 140 182 | } 183 | } else { 184 | l = len(msg) 185 | if l > 160 { 186 | // 只取前160个字符 187 | msg = msg[:160] 188 | l = 160 189 | } 190 | } 191 | dly.msgLength = uint8(l) 192 | dly.msgContent = msg 193 | } 194 | 195 | func (d *Delivery) RegisteredDelivery() uint8 { 196 | return d.registeredDelivery 197 | } 198 | 199 | type DeliveryResp struct { 200 | *MessageHeader 201 | msgId uint64 // 消息标识,来自CMPP_DELIVERY 202 | result uint32 // 结果 203 | } 204 | 205 | func (r *DeliveryResp) Encode() []byte { 206 | frame := r.MessageHeader.Encode() 207 | binary.BigEndian.PutUint64(frame[12:20], r.msgId) 208 | if V3() { 209 | binary.BigEndian.PutUint32(frame[20:24], r.result) 210 | } else { 211 | frame[20] = byte(r.result) 212 | } 213 | return frame 214 | } 215 | 216 | func (r *DeliveryResp) Decode(header *MessageHeader, frame []byte) error { 217 | if header == nil || header.CommandId != CMPP_DELIVER_RESP || uint32(len(frame)) < (header.TotalLength-HeadLength) { 218 | return ErrorPacket 219 | } 220 | r.msgId = binary.BigEndian.Uint64(frame[0:8]) 221 | if V3() { 222 | r.result = binary.BigEndian.Uint32(frame[8:12]) 223 | } else { 224 | r.result = uint32(frame[8]) 225 | } 226 | return nil 227 | } 228 | 229 | func (r *DeliveryResp) String() string { 230 | return fmt.Sprintf("{ header: %v, msgId: %d, result: {%d: %s} }", r.MessageHeader, r.msgId, r.result, DeliveryResultMap[r.result]) 231 | } 232 | 233 | func (r *DeliveryResp) SetResult(result uint32) { 234 | r.result = result 235 | } 236 | 237 | var DeliveryResultMap = map[uint32]string{ 238 | 0: "正确", 239 | 1: "消息结构错", 240 | 2: "命令字错", 241 | 3: "消息序号重复", 242 | 4: "消息长度错", 243 | 5: "资费代码错", 244 | 6: "超过最大信息长", 245 | 7: "业务代码错", 246 | 8: "流量控制错", 247 | 9: "未知错误", 248 | } 249 | -------------------------------------------------------------------------------- /comm/yml_config/yml_config.go: -------------------------------------------------------------------------------- 1 | package yml_config 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | "sync" 7 | "time" 8 | 9 | "github.com/fsnotify/fsnotify" 10 | "github.com/spf13/viper" 11 | "go.uber.org/zap" 12 | 13 | "github.com/aaronwong1989/gosms/comm/container" 14 | "github.com/aaronwong1989/gosms/comm/logging" 15 | ) 16 | 17 | // 由于 vipver 包本身对于文件的变化事件有一个bug,相关事件会被回调两次 18 | // 常年未彻底解决,相关的 issue 清单:https://github.com/spf13/viper/issues?q=OnConfigChange 19 | // 设置一个内部全局变量,记录配置文件变化时的时间点,如果两次回调事件事件差小于1秒,我们认为是第二次回调事件,而不是人工修改配置文件 20 | // 这样就避免了 viper 包的这个bug 21 | 22 | var lastChangeTime time.Time 23 | var log = logging.GetDefaultLogger() 24 | var containerFactory = container.CreateContainersFactory() 25 | 26 | const ConfigKeyPrefix = "_config_key_prefix_" 27 | 28 | type YmlConfig interface { 29 | ConfigFileChangeListen() 30 | Clone(fileName string) YmlConfig 31 | Get(keyName string) interface{} 32 | GetString(keyName string) string 33 | GetBool(keyName string) bool 34 | GetInt(keyName string) int 35 | GetInt32(keyName string) int32 36 | GetInt64(keyName string) int64 37 | GetFloat64(keyName string) float64 38 | GetDuration(keyName string) time.Duration 39 | GetStringSlice(keyName string) []string 40 | } 41 | 42 | func init() { 43 | lastChangeTime = time.Now() 44 | } 45 | 46 | // CreateYamlFactory 创建一个yaml配置文件工厂 47 | // 参数设置为可变参数的文件名,这样参数就可以不需要传递,如果传递了多个,我们只取第一个参数作为配置文件名 48 | func CreateYamlFactory(fileName ...string) YmlConfig { 49 | 50 | yamlConfig := viper.New() 51 | // 配置文件所在目录 52 | yamlConfig.AddConfigPath(BasePath() + "/config") 53 | // 需要读取的文件名,默认为:config 54 | if len(fileName) == 0 { 55 | yamlConfig.SetConfigName("config") 56 | } else { 57 | yamlConfig.SetConfigName(fileName[0]) 58 | } 59 | // 设置配置文件类型(后缀)为 yml 60 | yamlConfig.SetConfigType("yaml") 61 | 62 | if err := yamlConfig.ReadInConfig(); err != nil { 63 | log.Fatalf("配置文件初始化失败:", err.Error()) 64 | } 65 | 66 | conf := &ymlLoader{ 67 | viper: yamlConfig, 68 | mu: new(sync.Mutex), 69 | } 70 | conf.ConfigFileChangeListen() 71 | 72 | return conf 73 | } 74 | 75 | type ymlLoader struct { 76 | viper *viper.Viper 77 | mu *sync.Mutex 78 | } 79 | 80 | // ConfigFileChangeListen 监听文件变化 81 | func (y *ymlLoader) ConfigFileChangeListen() { 82 | y.viper.OnConfigChange(func(changeEvent fsnotify.Event) { 83 | if time.Now().Sub(lastChangeTime).Seconds() >= 1 { 84 | if changeEvent.Op.String() == "WRITE" { 85 | y.clearCache() 86 | lastChangeTime = time.Now() 87 | log.Infof("[%-9s] config file changed, reload!", "Config") 88 | } 89 | } 90 | }) 91 | y.viper.WatchConfig() 92 | } 93 | 94 | // keyIsCache 判断相关键是否已经缓存 95 | func (y *ymlLoader) keyIsCache(keyName string) bool { 96 | if _, exists := containerFactory.KeyIsExists(ConfigKeyPrefix + keyName); exists { 97 | return true 98 | } else { 99 | return false 100 | } 101 | } 102 | 103 | // 对键值进行缓存 104 | func (y *ymlLoader) cache(keyName string, value interface{}) bool { 105 | // 避免瞬间缓存键、值时,程序提示键名已经被注册的日志输出 106 | y.mu.Lock() 107 | defer y.mu.Unlock() 108 | if _, exists := containerFactory.KeyIsExists(ConfigKeyPrefix + keyName); exists { 109 | return true 110 | } 111 | return containerFactory.Set(ConfigKeyPrefix+keyName, value) 112 | } 113 | 114 | // 通过键获取缓存的值 115 | func (y *ymlLoader) getValueFromCache(keyName string) interface{} { 116 | return containerFactory.Get(ConfigKeyPrefix + keyName) 117 | } 118 | 119 | // 清空已经缓存的配置项信息 120 | func (y *ymlLoader) clearCache() { 121 | containerFactory.FuzzyDelete(ConfigKeyPrefix) 122 | } 123 | 124 | // Clone 允许 clone 一个相同功能的结构体 125 | func (y *ymlLoader) Clone(fileName string) YmlConfig { 126 | // 这里存在一个深拷贝,需要注意,避免拷贝的结构体操作对原始结构体造成影响 127 | var ymlC = *y 128 | var ymlConfViper = *(y.viper) 129 | (&ymlC).viper = &ymlConfViper 130 | 131 | (&ymlC).viper.SetConfigName(fileName) 132 | if err := (&ymlC).viper.ReadInConfig(); err != nil { 133 | log.Fatalf("配置文件Clone失败:", zap.Error(err)) 134 | } 135 | return &ymlC 136 | } 137 | 138 | // Get 一个原始值 139 | func (y *ymlLoader) Get(keyName string) interface{} { 140 | if y.keyIsCache(keyName) { 141 | return y.getValueFromCache(keyName) 142 | } else { 143 | value := y.viper.Get(keyName) 144 | y.cache(keyName, value) 145 | return value 146 | } 147 | } 148 | 149 | // GetString 字符串格式返回值 150 | func (y *ymlLoader) GetString(keyName string) string { 151 | if y.keyIsCache(keyName) { 152 | return y.getValueFromCache(keyName).(string) 153 | } else { 154 | value := y.viper.GetString(keyName) 155 | y.cache(keyName, value) 156 | return value 157 | } 158 | 159 | } 160 | 161 | // GetBool 布尔格式返回值 162 | func (y *ymlLoader) GetBool(keyName string) bool { 163 | if y.keyIsCache(keyName) { 164 | return y.getValueFromCache(keyName).(bool) 165 | } else { 166 | value := y.viper.GetBool(keyName) 167 | y.cache(keyName, value) 168 | return value 169 | } 170 | } 171 | 172 | // GetInt 整数格式返回值 173 | func (y *ymlLoader) GetInt(keyName string) int { 174 | if y.keyIsCache(keyName) { 175 | return y.getValueFromCache(keyName).(int) 176 | } else { 177 | value := y.viper.GetInt(keyName) 178 | y.cache(keyName, value) 179 | return value 180 | } 181 | } 182 | 183 | // GetInt32 整数格式返回值 184 | func (y *ymlLoader) GetInt32(keyName string) int32 { 185 | if y.keyIsCache(keyName) { 186 | return y.getValueFromCache(keyName).(int32) 187 | } else { 188 | value := y.viper.GetInt32(keyName) 189 | y.cache(keyName, value) 190 | return value 191 | } 192 | } 193 | 194 | // GetInt64 整数格式返回值 195 | func (y *ymlLoader) GetInt64(keyName string) int64 { 196 | if y.keyIsCache(keyName) { 197 | return y.getValueFromCache(keyName).(int64) 198 | } else { 199 | value := y.viper.GetInt64(keyName) 200 | y.cache(keyName, value) 201 | return value 202 | } 203 | } 204 | 205 | // GetFloat64 小数格式返回值 206 | func (y *ymlLoader) GetFloat64(keyName string) float64 { 207 | if y.keyIsCache(keyName) { 208 | return y.getValueFromCache(keyName).(float64) 209 | } else { 210 | value := y.viper.GetFloat64(keyName) 211 | y.cache(keyName, value) 212 | return value 213 | } 214 | } 215 | 216 | // GetDuration 时间单位格式返回值 217 | func (y *ymlLoader) GetDuration(keyName string) time.Duration { 218 | if y.keyIsCache(keyName) { 219 | return y.getValueFromCache(keyName).(time.Duration) 220 | } else { 221 | value := y.viper.GetDuration(keyName) 222 | y.cache(keyName, value) 223 | return value 224 | } 225 | } 226 | 227 | // GetStringSlice 字符串切片数格式返回值 228 | func (y *ymlLoader) GetStringSlice(keyName string) []string { 229 | if y.keyIsCache(keyName) { 230 | return y.getValueFromCache(keyName).([]string) 231 | } else { 232 | value := y.viper.GetStringSlice(keyName) 233 | y.cache(keyName, value) 234 | return value 235 | } 236 | } 237 | 238 | var basePath string 239 | 240 | func BasePath() string { 241 | if basePath != "" { 242 | return basePath 243 | } 244 | if curPath, err := os.Getwd(); err == nil { 245 | // 路径进行处理,兼容单元测试程序程序启动时的奇怪路径 246 | if len(os.Args) > 1 && strings.HasPrefix(os.Args[1], "-test") { 247 | i := strings.Index(curPath, "gosms") 248 | basePath = curPath[:i] + "gosms" 249 | } else { 250 | basePath = curPath 251 | } 252 | return basePath 253 | } else { 254 | log.Fatalf("程序运行目录无权限,请检查!") 255 | } 256 | return "" 257 | } 258 | -------------------------------------------------------------------------------- /comm/tlv_test.go: -------------------------------------------------------------------------------- 1 | package comm 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | const ( 11 | TypeTest1 = iota 12 | TypeTest2 13 | TypeTest3 14 | TypeTest4 15 | TypeTest5 16 | TypeTest6 17 | ) 18 | 19 | func FailWithError(t *testing.T, name string, err error) { 20 | fmt.Printf("[!] %s failed: %s\n", name, err.Error()) 21 | t.FailNow() 22 | } 23 | 24 | var errNoMatch = fmt.Errorf("TLV records don't match") 25 | 26 | func TestTLVRead(t *testing.T) { 27 | descr := []byte("This is a test description.") 28 | tlv := New(TypeTest1, descr) 29 | 30 | tmpFile, err := ioutil.TempFile("", "metakey_test_") 31 | if err != nil { 32 | FailWithError(t, "TestTLVRead", err) 33 | } 34 | tmpName := tmpFile.Name() 35 | err = WriteObject(tlv, tmpFile) 36 | if err != nil { 37 | FailWithError(t, "TestTLVRead", err) 38 | } 39 | tmpFile.Close() 40 | defer os.Remove(tmpName) 41 | 42 | tlvRaw, err := ioutil.ReadFile(tmpName) 43 | if err != nil { 44 | FailWithError(t, "TestTLVRead", err) 45 | } 46 | tmpTLV, err := FromBytes(tlvRaw) 47 | if err != nil { 48 | FailWithError(t, "TestTLVRead", err) 49 | } 50 | 51 | if !Equal(tlv, tmpTLV) { 52 | FailWithError(t, "TestTLVRead", errNoMatch) 53 | } 54 | 55 | tmpFile, err = os.Open(tmpName) 56 | if err != nil { 57 | FailWithError(t, "TestTLVRead", err) 58 | } 59 | tmpTLV, err = ReadObject(tmpFile) 60 | if err != nil { 61 | FailWithError(t, "TestTLVRead", err) 62 | } else if !Equal(tlv, tmpTLV) { 63 | FailWithError(t, "TestTLVRead", errNoMatch) 64 | } 65 | 66 | } 67 | 68 | func TestTLVListAdd(t *testing.T) { 69 | tlvl := NewTlvList() 70 | 71 | tlv1 := New(TypeTest1, []byte("foo bar")) 72 | tlv2 := New(TypeTest2, []byte("baz quux")) 73 | tlvl.Add(TypeTest1, []byte("foo bar")) 74 | tlvl.Add(TypeTest2, []byte("baz quux")) 75 | 76 | if tlvl.Length() != 2 { 77 | err := fmt.Errorf("records not added") 78 | FailWithError(t, "TestTLVListAdd", err) 79 | } 80 | 81 | tmpTLV, err := tlvl.Get(TypeTest1) 82 | if err != nil { 83 | FailWithError(t, "TestTLVListAdd", errNoMatch) 84 | } else if !Equal(tmpTLV, tlv1) { 85 | FailWithError(t, "TestTLVListAdd", errNoMatch) 86 | } 87 | 88 | tmpTLV, err = tlvl.Get(TypeTest2) 89 | if err != nil { 90 | FailWithError(t, "TestTLVListAdd", errNoMatch) 91 | } else if !Equal(tmpTLV, tlv2) { 92 | FailWithError(t, "TestTLVListAdd", errNoMatch) 93 | } 94 | } 95 | 96 | func TestTLVListRemove(t *testing.T) { 97 | tlvl := NewTlvList() 98 | tlvl.Add(TypeTest1, []byte("foo bar")) 99 | 100 | if tlvl.Length() != 1 { 101 | err := fmt.Errorf("records not added") 102 | FailWithError(t, "TestTLVListAdd", err) 103 | } 104 | 105 | if 1 != tlvl.Remove(TypeTest1) { 106 | FailWithError(t, "TestTLVListRemove", 107 | fmt.Errorf("record not removed")) 108 | } 109 | 110 | if _, err := tlvl.Get(TypeTest1); err != ErrTypeNotFound { 111 | FailWithError(t, "TestTLVListRemove", 112 | fmt.Errorf("record should be removed")) 113 | } 114 | } 115 | 116 | func TestTLVListRemoveRecord(t *testing.T) { 117 | tlvl := NewTlvList() 118 | tlv1 := New(TypeTest1, []byte("foo bar")) 119 | tlvl.Add(TypeTest1, []byte("foo bar")) 120 | 121 | if tlvl.Length() != 1 { 122 | err := fmt.Errorf("records not added") 123 | FailWithError(t, "TestTLVListAdd", err) 124 | } 125 | 126 | if 1 != tlvl.RemoveObject(tlv1) { 127 | FailWithError(t, "TestTLVListRemove", 128 | fmt.Errorf("record not removed")) 129 | } 130 | 131 | if _, err := tlvl.Get(TypeTest1); err != ErrTypeNotFound { 132 | FailWithError(t, "TestTLVListRemove", 133 | fmt.Errorf("record should be removed")) 134 | } 135 | } 136 | 137 | func TestTLVListRemoveRecords(t *testing.T) { 138 | tlvl := NewTlvList() 139 | tlv1 := New(TypeTest1, []byte("foo bar")) 140 | tlv2 := New(TypeTest2, []byte("baz quux")) 141 | tlv3 := New(TypeTest1, []byte("goodbye, cruel world")) 142 | tlvl.AddObject(tlv1) 143 | tlvl.AddObject(tlv2) 144 | tlvl.AddObject(tlv3) 145 | 146 | if tlvl.Length() != 3 { 147 | err := fmt.Errorf("records not added") 148 | FailWithError(t, "TestTLVRemoveRecords", err) 149 | } 150 | 151 | if tlvs := tlvl.GetAll(TypeTest1); len(tlvs) != 2 { 152 | fmt.Printf("%d TypeTest1 records, expected %d\n", 153 | len(tlvs), 2) 154 | FailWithError(t, "TestTLVListRemoveRecords", 155 | fmt.Errorf("records not added")) 156 | } 157 | 158 | if n := tlvl.Remove(TypeTest1); n != 2 { 159 | fmt.Printf("only %d records removed\n", n) 160 | FailWithError(t, "TestTLVListRemoveRecords", 161 | fmt.Errorf("record not removed")) 162 | } 163 | 164 | if _, err := tlvl.Get(TypeTest1); err != ErrTypeNotFound { 165 | FailWithError(t, "TestTLVListRemove", 166 | fmt.Errorf("record should be removed")) 167 | } 168 | } 169 | 170 | func TestTLVListReadWrite(t *testing.T) { 171 | tlvl := NewTlvList() 172 | 173 | tlv1 := New(TypeTest1, []byte("foo bar")) 174 | tlv2 := New(TypeTest2, []byte("baz quux")) 175 | tlv3 := New(TypeTest3, []byte("gophers are everywhere!")) 176 | tlv4 := New(TypeTest4, []byte{53, 139, 142, 31, 142, 157, 225, 31, 177 | 13, 253, 8, 22, 204, 168, 197, 37, 178 | 102, 99, 63, 217, 89, 167, 63, 120, 179 | 219, 154, 148, 175, 195, 24, 35, 55}) 180 | tlv5 := New(TypeTest5, []byte{79, 74, 170, 235, 57, 206, 46, 164, 181 | 152, 26, 5, 55, 128, 176, 50, 93, 219, 182 | 190, 120, 11, 11, 172, 145, 81, 153, 183 | 174, 192, 120, 56, 207, 84, 180, 71, 184 | 252, 199, 98, 13, 142, 149, 150, 159, 185 | 80, 9, 239, 5, 36, 50, 82, 128, 216, 186 | 217, 247, 180, 53, 215, 187, 101, 78, 187 | 124, 79, 201, 36, 200, 55}) 188 | tlv6 := New(TypeTest6, []byte{61, 138, 6, 151, 196, 225, 46, 32, 31, 189 | 227, 35, 47, 85, 196, 155, 82, 98, 190 | 113, 221, 48, 119, 34, 126, 70, 183, 191 | 222, 185, 125, 65, 249, 167, 101, 98, 192 | 182, 112, 159, 3, 139, 66, 104, 55, 193 | 108, 161, 146, 175, 89, 70, 97, 70, 194 | 168, 83, 95, 217, 179, 28, 35, 168, 195 | 115, 101, 123, 222, 60, 175, 185, 171, 196 | 166, 192, 74, 131, 105, 235, 245, 102, 197 | 245, 176, 113, 10, 148, 176, 216, 174, 198 | 72, 138, 159, 238, 133, 239, 0, 18, 199 | 221, 96, 20, 216, 63, 77, 246, 85, 248, 200 | 169, 230, 234, 48, 80, 175, 225, 175, 201 | 109, 95, 192, 127, 215, 110, 30, 69, 202 | 186, 205, 50, 207, 228, 168, 13, 186, 203 | 104, 73, 142, 158, 114, 152}) 204 | tlvs := []TLV{tlv1, tlv2, tlv3, tlv4, tlv5, tlv6} 205 | 206 | tlvl.Add(tlv1.Type(), tlv1.Value()) 207 | tlvl.Add(tlv2.Type(), tlv2.Value()) 208 | tlvl.Add(tlv3.Type(), tlv3.Value()) 209 | tlvl.Add(tlv4.Type(), tlv4.Value()) 210 | tlvl.Add(tlv5.Type(), tlv5.Value()) 211 | tlvl.Add(tlv6.Type(), tlv6.Value()) 212 | 213 | tmpFile, err := ioutil.TempFile("", "metakey_test_") 214 | if err != nil { 215 | FailWithError(t, "TestTLVListReadWrite", err) 216 | } 217 | tmpName := tmpFile.Name() 218 | defer os.Remove(tmpName) 219 | 220 | err = tlvl.Write(tmpFile) 221 | if err != nil { 222 | FailWithError(t, "TestTLVListReadWrite", err) 223 | } 224 | tmpFile.Close() 225 | 226 | tmpFile, err = os.Open(tmpName) 227 | if err != nil { 228 | FailWithError(t, "TestTLVListReadWrite", err) 229 | } 230 | 231 | rtlvl, err := Read(tmpFile) 232 | if err != nil { 233 | FailWithError(t, "TestTLVListReadWrite", err) 234 | } 235 | 236 | for _, testTLV := range tlvs { 237 | rTLV, err := rtlvl.Get(testTLV.Type()) 238 | if err != nil { 239 | FailWithError(t, "TestTLVListReadWrite", err) 240 | } else if !Equal(testTLV, rTLV) { 241 | FailWithError(t, "TestTLVListReadWrite", errNoMatch) 242 | } 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /comm/tlv.go: -------------------------------------------------------------------------------- 1 | package comm 2 | 3 | import ( 4 | "bytes" 5 | "container/list" 6 | "encoding/binary" 7 | "fmt" 8 | "io" 9 | "strings" 10 | ) 11 | 12 | // TLV represents a Type-Length-Value object. 13 | type TLV interface { 14 | Type() uint16 15 | Length() uint16 16 | Value() []byte 17 | } 18 | 19 | type object struct { 20 | typ uint16 21 | len uint16 22 | val []byte 23 | } 24 | 25 | // Type returns the object's type 26 | func (o *object) Type() uint16 { 27 | return o.typ 28 | } 29 | 30 | // Length returns the object's type 31 | func (o *object) Length() uint16 { 32 | return o.len 33 | } 34 | 35 | // Value returns the object's value 36 | func (o *object) Value() []byte { 37 | return o.val 38 | } 39 | 40 | func (o *object) String() string { 41 | return fmt.Sprintf("{tag: %x, len: %d, val: %#x}", o.typ, o.len, o.val) 42 | } 43 | 44 | // Equal returns true if a pair of TLV objects are the same. 45 | func Equal(tlv1, tlv2 TLV) bool { 46 | if tlv1 == nil { 47 | return tlv2 == nil 48 | } else if tlv2 == nil { 49 | return false 50 | } else if tlv1.Type() != tlv2.Type() { 51 | return false 52 | } else if tlv1.Length() != tlv2.Length() { 53 | return false 54 | } else if !bytes.Equal(tlv1.Value(), tlv2.Value()) { 55 | return false 56 | } 57 | return true 58 | } 59 | 60 | var ( 61 | // ErrTLVRead is returned when there is an error reading a TLV object. 62 | ErrTLVRead = fmt.Errorf("TLV %s", "read error") 63 | // ErrTLVWrite is returned when there is an error writing a TLV object. 64 | ErrTLVWrite = fmt.Errorf("TLV %s", "write error") 65 | // ErrTypeNotFound is returned when a request for a TLV type is made and none can be found. 66 | ErrTypeNotFound = fmt.Errorf("TLV %s", "type not found") 67 | ) 68 | 69 | // New returns a TLV object from the args 70 | func New(typ uint16, val []byte) TLV { 71 | tlv := new(object) 72 | tlv.typ = typ 73 | tlv.len = uint16(len(val)) 74 | tlv.val = make([]byte, tlv.Length()) 75 | copy(tlv.val, val) 76 | return tlv 77 | } 78 | 79 | // FromBytes returns a TLV object from bytes 80 | func FromBytes(data []byte) (TLV, error) { 81 | objBuf := bytes.NewBuffer(data) 82 | return ReadObject(objBuf) 83 | } 84 | 85 | // ToBytes returns bytes from a TLV object 86 | func ToBytes(tlv TLV) ([]byte, error) { 87 | data := make([]byte, 0) 88 | objBuf := bytes.NewBuffer(data) 89 | err := WriteObject(tlv, objBuf) 90 | return objBuf.Bytes(), err 91 | } 92 | 93 | // ReadObject returns a TLV object from io.Reader 94 | func ReadObject(r io.Reader) (TLV, error) { 95 | tlv := new(object) 96 | 97 | var typ uint16 98 | var err error 99 | err = binary.Read(r, binary.BigEndian, &typ) 100 | if err != nil { 101 | return nil, err 102 | } 103 | tlv.typ = typ 104 | 105 | var length uint16 106 | err = binary.Read(r, binary.BigEndian, &length) 107 | if err != nil { 108 | return nil, err 109 | } 110 | tlv.len = length 111 | 112 | tlv.val = make([]byte, tlv.Length()) 113 | l, err := r.Read(tlv.val) 114 | if err != nil { 115 | return nil, err 116 | } else if uint16(l) != tlv.Length() { 117 | return tlv, ErrTLVRead 118 | } 119 | 120 | return tlv, nil 121 | } 122 | 123 | // WriteObject writes a TLV object to io.Writer 124 | func WriteObject(tlv TLV, w io.Writer) error { 125 | var err error 126 | 127 | typ := tlv.Type() 128 | err = binary.Write(w, binary.BigEndian, typ) 129 | if err != nil { 130 | return err 131 | } 132 | 133 | length := tlv.Length() 134 | err = binary.Write(w, binary.BigEndian, length) 135 | if err != nil { 136 | return err 137 | } 138 | 139 | n, err := w.Write(tlv.Value()) 140 | if err != nil { 141 | return err 142 | } else if uint16(n) != tlv.Length() { 143 | return ErrTLVWrite 144 | } 145 | 146 | return nil 147 | } 148 | 149 | // TlvList is ad double-linked list containing TLV objects. 150 | type TlvList struct { 151 | objects *list.List 152 | } 153 | 154 | // NewTlvList returns a new, empty TLVList. 155 | func NewTlvList() *TlvList { 156 | tl := new(TlvList) 157 | tl.objects = list.New() 158 | return tl 159 | } 160 | 161 | // Length returns the number of objects int the TLVList. 162 | func (tl *TlvList) Length() int32 { 163 | return int32(tl.objects.Len()) 164 | } 165 | 166 | // Get checks the TLVList for any object matching the type, It returns the first one found. 167 | // If the type could not be found, Get returns ErrTypeNotFound. 168 | func (tl *TlvList) Get(typ uint16) (TLV, error) { 169 | for e := tl.objects.Front(); e != nil; e = e.Next() { 170 | if e.Value.(*object).Type() == typ { 171 | return e.Value.(*object), nil 172 | } 173 | } 174 | return nil, ErrTypeNotFound 175 | } 176 | 177 | // GetAll checks the TLVList for all objects matching the type, returning a slice containing all matching objects. 178 | // If no object has the requested type, an empty slice is returned. 179 | func (tl *TlvList) GetAll(typ uint16) []TLV { 180 | ts := make([]TLV, 0) 181 | for e := tl.objects.Front(); e != nil; e = e.Next() { 182 | if e.Value.(*object).Type() == typ { 183 | ts = append(ts, e.Value.(TLV)) 184 | } 185 | } 186 | return ts 187 | } 188 | 189 | // Remove removes all objects with the requested type. 190 | // It returns a count of the number of removed objects. 191 | func (tl *TlvList) Remove(typ uint16) int { 192 | var totalRemoved int 193 | for { 194 | var removed int 195 | for e := tl.objects.Front(); e != nil; e = e.Next() { 196 | if e.Value.(*object).Type() == typ { 197 | tl.objects.Remove(e) 198 | removed++ 199 | break 200 | } 201 | } 202 | if removed == 0 { 203 | break 204 | } 205 | totalRemoved += removed 206 | } 207 | return totalRemoved 208 | } 209 | 210 | // RemoveObject takes an TLV object as an argument, and removes all matching objects. 211 | // It matches on not just type, but also the value contained in the object. 212 | func (tl *TlvList) RemoveObject(obj TLV) int { 213 | var totalRemoved int 214 | for { 215 | var removed int 216 | for e := tl.objects.Front(); e != nil; e = e.Next() { 217 | if Equal(e.Value.(*object), obj) { 218 | tl.objects.Remove(e) 219 | removed++ 220 | break 221 | } 222 | } 223 | 224 | if removed == 0 { 225 | break 226 | } 227 | totalRemoved += removed 228 | } 229 | return totalRemoved 230 | } 231 | 232 | // Add pushes a new TLV object onto the TLVList. It builds the object from its args 233 | func (tl *TlvList) Add(typ uint16, value []byte) { 234 | obj := New(typ, value) 235 | tl.objects.PushBack(obj) 236 | } 237 | 238 | // AddObject adds a TLV object onto the TLVList 239 | func (tl *TlvList) AddObject(obj TLV) { 240 | tl.objects.PushBack(obj) 241 | } 242 | 243 | // Write writes out the TLVList to an io.Writer. 244 | func (tl *TlvList) Write(w io.Writer) error { 245 | for e := tl.objects.Front(); e != nil; e = e.Next() { 246 | err := WriteObject(e.Value.(TLV), w) 247 | if err != nil { 248 | return err 249 | } 250 | } 251 | return nil 252 | } 253 | 254 | func (tl *TlvList) String() string { 255 | var sb strings.Builder 256 | sb.Grow(int(8 * tl.Length())) 257 | sb.WriteString("[") 258 | for e := tl.objects.Front(); e != nil; e = e.Next() { 259 | o := e.Value.(*object) 260 | sb.WriteString(o.String()) 261 | sb.WriteString(",") 262 | } 263 | sb.WriteString("]") 264 | return sb.String() 265 | } 266 | 267 | // Read takes an io.Reader and builds a TLVList from that. 268 | func Read(r io.Reader) (*TlvList, error) { 269 | tl := NewTlvList() 270 | var err error 271 | for { 272 | var tlv TLV 273 | if tlv, err = ReadObject(r); err != nil { 274 | break 275 | } 276 | tl.objects.PushBack(tlv) 277 | } 278 | 279 | if err == io.EOF { 280 | err = nil 281 | } 282 | return tl, err 283 | } 284 | -------------------------------------------------------------------------------- /cmd/ismg/smgp/server_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "math/rand" 7 | "net" 8 | "os" 9 | "sync" 10 | "sync/atomic" 11 | "testing" 12 | "time" 13 | 14 | "github.com/panjf2000/gnet/v2/pkg/pool/goroutine" 15 | "github.com/stretchr/testify/assert" 16 | 17 | "github.com/aaronwong1989/gosms/codec/smgp" 18 | "github.com/aaronwong1989/gosms/comm" 19 | "github.com/aaronwong1989/gosms/comm/yml_config" 20 | ) 21 | 22 | func init() { 23 | rand.Seed(time.Now().Unix()) // 随机种子 24 | smgp.Conf = yml_config.CreateYamlFactory("smgp.yaml") 25 | dc := smgp.Conf.GetInt("data-center-id") 26 | wk := smgp.Conf.GetInt("worker-id") 27 | smgwId := smgp.Conf.GetString("smgw-id") 28 | smgp.Seq32 = comm.NewCycleSequence(int32(dc), int32(wk)) 29 | smgp.Seq80 = comm.NewBcdSequence(smgwId) 30 | } 31 | 32 | var ( 33 | pool = goroutine.Default() 34 | counterMt int64 35 | counterRt int64 36 | counterAt int64 37 | wg sync.WaitGroup 38 | mtChan = make(chan struct{}, 1) 39 | dlyChan = make(chan struct{}, 1) 40 | readChan = make(chan struct{}, 1) 41 | termChan = make(chan struct{}) 42 | 43 | clients = 1 44 | duration = time.Second * 10 45 | // addr = "10.211.55.13:9000" 46 | addr = ":9100" 47 | ) 48 | 49 | func TestClient(t *testing.T) { 50 | wg.Add(1) 51 | defer func() { 52 | pool.Release() 53 | logResult(t) 54 | }() 55 | 56 | for i := 0; i < clients; i++ { 57 | runClient(t) 58 | } 59 | time.Sleep(duration) 60 | 61 | // 停掉发送 62 | dlyChan <- struct{}{} 63 | mtChan <- struct{}{} 64 | // 停掉接收 65 | time.Sleep(100 * time.Millisecond) 66 | readChan <- struct{}{} 67 | time.Sleep(100 * time.Millisecond) 68 | // 发送断开连接报文 69 | wg.Done() 70 | <-termChan 71 | } 72 | 73 | func TestLogin(t *testing.T) { 74 | c, err := net.Dial("tcp", addr) 75 | if err != nil { 76 | t.Errorf("%v", err) 77 | return 78 | } 79 | defer func(c net.Conn) { 80 | err := c.Close() 81 | if err != nil { 82 | t.Errorf("%v", err) 83 | } 84 | }(c) 85 | 86 | login(t, c) 87 | } 88 | 89 | func TestTerminate(t *testing.T) { 90 | c, err := net.Dial("tcp", addr) 91 | if err != nil { 92 | t.Errorf("%v", err) 93 | return 94 | } 95 | defer func(c net.Conn) { 96 | err := c.Close() 97 | if err != nil { 98 | t.Errorf("%v", err) 99 | } 100 | }(c) 101 | 102 | if !login(t, c) { 103 | return 104 | } 105 | 106 | terminate(t, c) 107 | } 108 | 109 | func runClient(t *testing.T) { 110 | go func(t *testing.T) { 111 | c, err := net.Dial("tcp", addr) 112 | if err != nil { 113 | t.Errorf("%v", err) 114 | return 115 | } 116 | defer func(c net.Conn) { 117 | err := c.Close() 118 | if err != nil { 119 | t.Errorf("%v", err) 120 | } 121 | }(c) 122 | 123 | if !login(t, c) { 124 | panic("登录失败,程序退出!") 125 | } 126 | 127 | _ = pool.Submit(func() { 128 | for s := true; s; { 129 | select { 130 | case <-mtChan: 131 | s = false 132 | t.Logf("接收到 mtChan 的停止信号") 133 | default: 134 | s = sendMt(t, c) 135 | } 136 | } 137 | }) 138 | 139 | _ = pool.Submit(func() { 140 | for s := true; s; { 141 | select { 142 | case <-dlyChan: 143 | s = false 144 | t.Logf("接收到 dlyChan 的停止信号") 145 | default: 146 | s = sendDelivery(t, c) 147 | time.Sleep(time.Millisecond * 50) 148 | } 149 | } 150 | }) 151 | 152 | _ = pool.Submit(func() { 153 | for s := true; s; { 154 | select { 155 | case <-readChan: 156 | s = false 157 | t.Logf("接收到 readChan 的停止信号") 158 | default: 159 | s = readResp(t, c) 160 | } 161 | } 162 | }) 163 | 164 | wg.Wait() 165 | terminate(t, c) 166 | termChan <- struct{}{} 167 | }(t) 168 | } 169 | 170 | func login(t *testing.T, c net.Conn) bool { 171 | con := smgp.NewLogin() 172 | t.Logf(">>>: %s", con) 173 | i, _ := c.Write(con.Encode()) 174 | assert.True(t, uint32(i) == con.PacketLength) 175 | 176 | pl := smgp.LoginRespLen 177 | resp := make([]byte, pl) 178 | i, _ = c.Read(resp) 179 | assert.True(t, i == pl) 180 | 181 | header := &smgp.MessageHeader{} 182 | err := header.Decode(resp) 183 | if err != nil { 184 | return false 185 | } 186 | rep := &smgp.LoginResp{} 187 | err = rep.Decode(header, resp[smgp.HeadLength:]) 188 | if err != nil { 189 | return false 190 | } 191 | t.Logf("<<<: %s", rep) 192 | return 0 == rep.Status() 193 | } 194 | 195 | func sendMt(t *testing.T, c net.Conn) bool { 196 | mts := smgp.NewSubmit([]string{"13100001111"}, fmt.Sprintf("hello world! %d", rand.Uint64()), smgp.MtOptions{}) 197 | mt := mts[0] 198 | _, err := c.Write(mt.Encode()) 199 | if err != nil { 200 | t.Errorf("%v", err) 201 | return false 202 | } 203 | t.Logf(">>> %s", mt) 204 | return true 205 | } 206 | 207 | func readResp(t *testing.T, c net.Conn) bool { 208 | bytes := make([]byte, 12) 209 | _, err := c.Read(bytes) 210 | if err != nil { 211 | t.Errorf("%v", err) 212 | return false 213 | } 214 | header := &smgp.MessageHeader{} 215 | _ = header.Decode(bytes) 216 | l := int(header.PacketLength - 12) 217 | bytes = make([]byte, l) 218 | l, err = c.Read(bytes) 219 | if err != nil { 220 | t.Errorf("%v", err) 221 | return false 222 | } 223 | if header.RequestId == smgp.CmdSubmitResp { 224 | csr := &smgp.SubmitResp{} 225 | err := csr.Decode(header, bytes) 226 | if err != nil { 227 | t.Errorf("%v", err) 228 | return false 229 | } else { 230 | atomic.AddInt64(&counterMt, 1) 231 | t.Logf("<<< %s", csr) 232 | } 233 | } else if header.RequestId == smgp.CmdDeliver { 234 | dly := &smgp.Deliver{} 235 | err := dly.Decode(header, bytes) 236 | if err != nil { 237 | t.Errorf("%v", err) 238 | return false 239 | } else { 240 | // 状态报告计数 241 | if dly.IsReport() { 242 | atomic.AddInt64(&counterRt, 1) 243 | } 244 | t.Logf("<<< %s", dly) 245 | } 246 | } else if header.RequestId == smgp.CmdActiveTestResp { 247 | at := (*smgp.ActiveTest)(header) 248 | t.Logf("<<< %s", header) 249 | ats := at.ToResponse(0).(*smgp.ActiveTestResp) 250 | _, err = c.Write(ats.Encode()) 251 | if err != nil { 252 | t.Errorf("%v", err) 253 | return false 254 | } else { 255 | atomic.AddInt64(&counterAt, 1) 256 | t.Logf(">>> %s", ats) 257 | } 258 | } else { 259 | t.Logf("<<< %s:%x", header, bytes) 260 | } 261 | return true 262 | } 263 | 264 | func sendDelivery(t *testing.T, c net.Conn) bool { 265 | dly := smgp.NewDeliver("13700001111", "123", "hello word 中国") 266 | _, err := c.Write(dly.Encode()) 267 | if err != nil { 268 | t.Errorf("%v", err) 269 | return false 270 | } 271 | t.Logf(">>> %s", dly) 272 | return true 273 | } 274 | 275 | func logResult(t *testing.T) { 276 | result := fmt.Sprintf("%s CounterMt=%d, CounterDl=%d, CounterAt=%d\n", time.Now().Format("2006-01-02T15:04:05.000"), counterMt, counterRt, counterAt) 277 | t.Logf(result) 278 | file, err := os.OpenFile("./test.result.txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) 279 | if err != nil { 280 | t.Errorf("%v", err) 281 | } 282 | writer := bufio.NewWriter(file) 283 | _, _ = writer.WriteString(result) 284 | defer func(file *os.File, writer *bufio.Writer) { 285 | _ = writer.Flush() 286 | _ = file.Close() 287 | }(file, writer) 288 | } 289 | 290 | func terminate(t *testing.T, c net.Conn) { 291 | term := smgp.NewExit() 292 | _, err := c.Write(term.Encode()) 293 | if err != nil { 294 | t.Errorf("%v", err) 295 | return 296 | } 297 | t.Logf(">>> %s", term) 298 | 299 | bytes := make([]byte, 12) 300 | l, err := c.Read(bytes) 301 | if err != nil || l != 12 { 302 | t.Errorf("%v", err) 303 | } 304 | h := &smgp.MessageHeader{} 305 | err = h.Decode(bytes) 306 | if err != nil { 307 | t.Errorf("%v", err) 308 | } 309 | t.Logf("<<< %s", term) 310 | } 311 | -------------------------------------------------------------------------------- /cmd/ismg/cmpp/server_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "math/rand" 7 | "net" 8 | "os" 9 | "sync" 10 | "sync/atomic" 11 | "testing" 12 | "time" 13 | 14 | "github.com/panjf2000/gnet/v2/pkg/pool/goroutine" 15 | "github.com/stretchr/testify/assert" 16 | 17 | "github.com/aaronwong1989/gosms/codec/cmpp" 18 | "github.com/aaronwong1989/gosms/comm" 19 | "github.com/aaronwong1989/gosms/comm/snowflake" 20 | "github.com/aaronwong1989/gosms/comm/yml_config" 21 | ) 22 | 23 | func init() { 24 | rand.Seed(time.Now().Unix()) // 随机种子 25 | cmpp.Conf = yml_config.CreateYamlFactory("cmpp.yaml") 26 | dc := cmpp.Conf.GetInt("data-center-id") 27 | wk := cmpp.Conf.GetInt("worker-id") 28 | cmpp.Seq32 = comm.NewCycleSequence(int32(dc), int32(wk)) 29 | cmpp.Seq64 = snowflake.NewSnowflake(int64(dc), int64(wk)) 30 | cmpp.ReportSeq = comm.NewCycleSequence(int32(dc), int32(wk)) 31 | } 32 | 33 | var ( 34 | pool = goroutine.Default() 35 | counterMt int64 36 | counterRt int64 37 | counterAt int64 38 | wg sync.WaitGroup 39 | mtChan = make(chan struct{}, 1) 40 | dlyChan = make(chan struct{}, 1) 41 | readChan = make(chan struct{}, 1) 42 | termChan = make(chan struct{}) 43 | 44 | clients = 1 45 | duration = time.Second * 30 46 | // addr = "10.211.55.13:9000" 47 | addr = ":9000" 48 | ) 49 | 50 | func TestClient(t *testing.T) { 51 | wg.Add(1) 52 | defer func() { 53 | pool.Release() 54 | logResult(t) 55 | }() 56 | 57 | for i := 0; i < clients; i++ { 58 | runClient(t) 59 | } 60 | time.Sleep(duration) 61 | 62 | // 停掉发送 63 | dlyChan <- struct{}{} 64 | mtChan <- struct{}{} 65 | // 停掉接收 66 | time.Sleep(100 * time.Millisecond) 67 | readChan <- struct{}{} 68 | time.Sleep(100 * time.Millisecond) 69 | // 发送断开连接报文 70 | wg.Done() 71 | <-termChan 72 | } 73 | 74 | func TestLogin(t *testing.T) { 75 | c, err := net.Dial("tcp", addr) 76 | if err != nil { 77 | t.Errorf("%v", err) 78 | return 79 | } 80 | defer func(c net.Conn) { 81 | err := c.Close() 82 | if err != nil { 83 | t.Errorf("%v", err) 84 | } 85 | }(c) 86 | 87 | login(t, c) 88 | } 89 | 90 | func TestTerminate(t *testing.T) { 91 | c, err := net.Dial("tcp", addr) 92 | if err != nil { 93 | t.Errorf("%v", err) 94 | return 95 | } 96 | defer func(c net.Conn) { 97 | err := c.Close() 98 | if err != nil { 99 | t.Errorf("%v", err) 100 | } 101 | }(c) 102 | 103 | if !login(t, c) { 104 | return 105 | } 106 | 107 | terminate(t, c) 108 | } 109 | 110 | func runClient(t *testing.T) { 111 | go func(t *testing.T) { 112 | c, err := net.Dial("tcp", addr) 113 | if err != nil { 114 | t.Errorf("%v", err) 115 | return 116 | } 117 | defer func(c net.Conn) { 118 | err := c.Close() 119 | if err != nil { 120 | t.Errorf("%v", err) 121 | } 122 | }(c) 123 | 124 | if !login(t, c) { 125 | panic("登录失败,程序退出!") 126 | } 127 | 128 | _ = pool.Submit(func() { 129 | for s := true; s; { 130 | select { 131 | case <-mtChan: 132 | s = false 133 | t.Logf("接收到 mtChan 的停止信号") 134 | default: 135 | s = sendMt(t, c) 136 | } 137 | } 138 | }) 139 | 140 | _ = pool.Submit(func() { 141 | for s := true; s; { 142 | select { 143 | case <-dlyChan: 144 | s = false 145 | t.Logf("接收到 dlyChan 的停止信号") 146 | default: 147 | s = sendDelivery(t, c) 148 | time.Sleep(time.Millisecond * 50) 149 | } 150 | } 151 | }) 152 | 153 | _ = pool.Submit(func() { 154 | for s := true; s; { 155 | select { 156 | case <-readChan: 157 | s = false 158 | t.Logf("接收到 readChan 的停止信号") 159 | default: 160 | s = readResp(t, c) 161 | } 162 | } 163 | }) 164 | 165 | wg.Wait() 166 | terminate(t, c) 167 | termChan <- struct{}{} 168 | }(t) 169 | } 170 | 171 | func login(t *testing.T, c net.Conn) bool { 172 | con := cmpp.NewConnect() 173 | t.Logf(">>>: %s", con) 174 | i, _ := c.Write(con.Encode()) 175 | assert.True(t, uint32(i) == con.TotalLength) 176 | 177 | pl := 30 178 | if cmpp.V3() { 179 | pl = 33 180 | } 181 | resp := make([]byte, pl) 182 | i, _ = c.Read(resp) 183 | assert.True(t, i == pl) 184 | 185 | header := &cmpp.MessageHeader{} 186 | err := header.Decode(resp) 187 | if err != nil { 188 | return false 189 | } 190 | rep := &cmpp.ConnectResp{} 191 | err = rep.Decode(header, resp[cmpp.HeadLength:]) 192 | if err != nil { 193 | return false 194 | } 195 | t.Logf("<<<: %s", rep) 196 | return 0 == rep.Status() 197 | } 198 | 199 | func sendMt(t *testing.T, c net.Conn) bool { 200 | mts := cmpp.NewSubmit([]string{"13100001111"}, fmt.Sprintf("hello world! %d", rand.Uint64())) 201 | mt := mts[0] 202 | _, err := c.Write(mt.Encode()) 203 | if err != nil { 204 | t.Errorf("%v", err) 205 | return false 206 | } 207 | t.Logf(">>> %s", mt) 208 | return true 209 | } 210 | 211 | func readResp(t *testing.T, c net.Conn) bool { 212 | bytes := make([]byte, 12) 213 | _, err := c.Read(bytes) 214 | if err != nil { 215 | t.Errorf("%v", err) 216 | return false 217 | } 218 | header := &cmpp.MessageHeader{} 219 | _ = header.Decode(bytes) 220 | l := int(header.TotalLength - 12) 221 | bytes = make([]byte, l) 222 | l, err = c.Read(bytes) 223 | if err != nil { 224 | t.Errorf("%v", err) 225 | return false 226 | } 227 | if header.CommandId == cmpp.CMPP_SUBMIT_RESP { 228 | csr := &cmpp.SubmitResp{} 229 | err := csr.Decode(header, bytes) 230 | if err != nil { 231 | t.Errorf("%v", err) 232 | return false 233 | } else { 234 | atomic.AddInt64(&counterMt, 1) 235 | t.Logf("<<< %s", csr) 236 | } 237 | } else if header.CommandId == cmpp.CMPP_DELIVER { 238 | dly := &cmpp.Delivery{} 239 | err := dly.Decode(header, bytes) 240 | if err != nil { 241 | t.Errorf("%v", err) 242 | return false 243 | } else { 244 | // 状态报告计数 245 | if dly.RegisteredDelivery() == 1 { 246 | atomic.AddInt64(&counterRt, 1) 247 | } 248 | t.Logf("<<< %s", dly) 249 | } 250 | } else if header.CommandId == cmpp.CMPP_ACTIVE_TEST { 251 | at := cmpp.ActiveTest{MessageHeader: header} 252 | t.Logf("<<< %s", at) 253 | ats := at.ToResponse(0).(*cmpp.ActiveTestResp) 254 | _, err = c.Write(ats.Encode()) 255 | if err != nil { 256 | t.Errorf("%v", err) 257 | return false 258 | } else { 259 | atomic.AddInt64(&counterAt, 1) 260 | t.Logf(">>> %s", ats) 261 | } 262 | } else { 263 | t.Logf("<<< %s:%x", header, bytes) 264 | } 265 | return true 266 | } 267 | 268 | func sendDelivery(t *testing.T, c net.Conn) bool { 269 | dly := cmpp.NewDelivery("13700001111", "hello word 中国", "", "") 270 | _, err := c.Write(dly.Encode()) 271 | if err != nil { 272 | t.Errorf("%v", err) 273 | return false 274 | } 275 | t.Logf(">>> %s", dly) 276 | return true 277 | } 278 | 279 | func logResult(t *testing.T) { 280 | result := fmt.Sprintf("%s CounterMt=%d, CounterDl=%d, CounterAt=%d\n", time.Now().Format("2006-01-02T15:04:05.000"), counterMt, counterRt, counterAt) 281 | t.Logf(result) 282 | file, err := os.OpenFile("./test.result.txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) 283 | if err != nil { 284 | t.Errorf("%v", err) 285 | } 286 | writer := bufio.NewWriter(file) 287 | _, _ = writer.WriteString(result) 288 | defer func(file *os.File, writer *bufio.Writer) { 289 | _ = writer.Flush() 290 | _ = file.Close() 291 | }(file, writer) 292 | } 293 | 294 | func terminate(t *testing.T, c net.Conn) { 295 | term := cmpp.NewTerminate() 296 | _, err := c.Write(term.Encode()) 297 | if err != nil { 298 | t.Errorf("%v", err) 299 | return 300 | } 301 | t.Logf(">>> %s", term) 302 | 303 | bytes := make([]byte, 12) 304 | l, err := c.Read(bytes) 305 | if err != nil || l != 12 { 306 | t.Errorf("%v", err) 307 | } 308 | err = term.Decode(bytes) 309 | if err != nil { 310 | t.Errorf("%v", err) 311 | } 312 | t.Logf("<<< %s", term) 313 | } 314 | -------------------------------------------------------------------------------- /codec/smgp/submit.go: -------------------------------------------------------------------------------- 1 | package smgp 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | 8 | "github.com/aaronwong1989/gosms/comm" 9 | ) 10 | 11 | type Submit struct { 12 | *MessageHeader 13 | msgType byte // 【1字节】短消息类型 14 | needReport byte // 【1字节】SP是否要求返回状态报告 15 | priority byte // 【1字节】短消息发送优先级 16 | serviceID string // 【10字节】业务代码 17 | feeType string // 【2字节】收费类型 18 | feeCode string // 【6字节】资费代码 19 | fixedFee string // 【6字节】包月费/封顶费 20 | msgFormat byte // 【1字节】短消息格式 21 | validTime string // 【17字节】短消息有效时间 22 | atTime string // 【17字节】短消息定时发送时间 23 | srcTermID string // 【21字节】短信息发送方号码 24 | chargeTermID string // 【21字节】计费用户号码 25 | destTermIDCount byte // 【1字节】短消息接收号码总数 26 | destTermID []string // 【21*DestTermCount字节】短消息接收号码 27 | msgLength byte // 【1字节】短消息长度 28 | msgContent string // 【MsgLength字节】短消息内容 29 | msgBytes []byte // 消息内容按照Msg_Fmt编码后的数据 30 | reserve string // 【8字节】保留 31 | tlvList *comm.TlvList // 【TLV】可选项参数 32 | } 33 | 34 | type SubmitResp struct { 35 | *MessageHeader 36 | msgId []byte // 【10字节】短消息流水号 37 | status uint32 38 | } 39 | 40 | const MtBaseLen = 126 41 | 42 | func NewSubmit(phones []string, content string, options MtOptions) (messages []*Submit) { 43 | 44 | head := &MessageHeader{PacketLength: MtBaseLen, RequestId: CmdSubmit, SequenceId: uint32(Seq32.NextVal())} 45 | mt := &Submit{} 46 | mt.MessageHeader = head 47 | mt.SetOptions(options) 48 | mt.msgType = 6 49 | // 从配置文件设置属性 50 | mt.feeType = Conf.GetString("fee-type") 51 | mt.feeCode = Conf.GetString("fee-code") 52 | mt.chargeTermID = Conf.GetString("charge-term-id") 53 | mt.fixedFee = Conf.GetString("fixed-fee") 54 | // 初步设置入参 55 | mt.destTermID = phones 56 | mt.destTermIDCount = byte(len(phones)) 57 | 58 | mt.msgFormat = 15 59 | data, err := GbEncoder.Bytes([]byte(content)) 60 | if err != nil { 61 | return nil 62 | } 63 | slices := comm.ToTPUDHISlices(data, 140) 64 | if len(slices) == 1 { 65 | mt.msgBytes = slices[0] 66 | mt.msgLength = byte(len(mt.msgBytes)) 67 | mt.PacketLength = uint32(MtBaseLen + len(mt.destTermID)*21 + int(mt.msgLength)) 68 | return []*Submit{mt} 69 | } else { 70 | for i, dt := range slices { 71 | // 拷贝 mt 72 | tmp := *mt 73 | tmpHead := *tmp.MessageHeader 74 | sub := &tmp 75 | sub.MessageHeader = &tmpHead 76 | if i != 0 { 77 | sub.SequenceId = uint32(Seq32.NextVal()) 78 | } 79 | sub.msgLength = byte(len(dt)) 80 | sub.msgBytes = dt 81 | l := 0 82 | sub.tlvList = comm.NewTlvList() 83 | sub.tlvList.Add(TP_pid, []byte{0x01}) 84 | l += 5 85 | sub.tlvList.Add(TP_udhi, []byte{0x01}) 86 | l += 5 87 | sub.tlvList.Add(PkTotal, []byte{byte(len(slices))}) 88 | l += 5 89 | sub.tlvList.Add(PkNumber, []byte{byte(i)}) 90 | l += 5 91 | sub.PacketLength = uint32(MtBaseLen + len(sub.destTermID)*21 + int(sub.msgLength) + l) 92 | messages = append(messages, sub) 93 | } 94 | return messages 95 | } 96 | } 97 | 98 | func (s *Submit) Encode() []byte { 99 | if len(s.destTermID) != int(s.destTermIDCount) { 100 | return nil 101 | } 102 | frame := s.MessageHeader.Encode() 103 | index := 12 104 | index = comm.CopyByte(frame, s.msgType, index) 105 | index = comm.CopyByte(frame, s.needReport, index) 106 | index = comm.CopyByte(frame, s.priority, index) 107 | index = comm.CopyStr(frame, s.serviceID, index, 10) 108 | index = comm.CopyStr(frame, s.feeType, index, 2) 109 | index = comm.CopyStr(frame, s.feeCode, index, 6) 110 | index = comm.CopyStr(frame, s.fixedFee, index, 6) 111 | index = comm.CopyByte(frame, s.msgFormat, index) 112 | index = comm.CopyStr(frame, s.validTime, index, 17) 113 | index = comm.CopyStr(frame, s.atTime, index, 17) 114 | index = comm.CopyStr(frame, s.srcTermID, index, 21) 115 | index = comm.CopyStr(frame, s.chargeTermID, index, 21) 116 | index = comm.CopyByte(frame, s.destTermIDCount, index) 117 | for _, tid := range s.destTermID { 118 | index = comm.CopyStr(frame, tid, index, 21) 119 | } 120 | 121 | index = comm.CopyByte(frame, s.msgLength, index) 122 | copy(frame[index:index+int(s.msgLength)], s.msgBytes) 123 | index += +int(s.msgLength) 124 | index = comm.CopyStr(frame, s.reserve, index, 8) 125 | if s.tlvList != nil { 126 | buff := new(bytes.Buffer) 127 | err := s.tlvList.Write(buff) 128 | if err != nil { 129 | log.Errorf("%v", err) 130 | return nil 131 | } 132 | copy(frame[index:], buff.Bytes()) 133 | } 134 | return frame 135 | } 136 | 137 | func (s *Submit) Decode(header *MessageHeader, frame []byte) error { 138 | // check 139 | if header == nil || header.RequestId != CmdSubmit || uint32(len(frame)) < (header.PacketLength-HeadLength) { 140 | return ErrorPacket 141 | } 142 | s.MessageHeader = header 143 | 144 | var index int 145 | s.msgType = frame[index] 146 | index++ 147 | s.needReport = frame[index] 148 | index++ 149 | s.priority = frame[index] 150 | index++ 151 | s.serviceID = comm.TrimStr(frame[index : index+10]) 152 | index += 10 153 | s.feeType = comm.TrimStr(frame[index : index+2]) 154 | index += 2 155 | s.feeCode = comm.TrimStr(frame[index : index+6]) 156 | index += 6 157 | s.fixedFee = comm.TrimStr(frame[index : index+6]) 158 | index += 6 159 | s.msgFormat = frame[index] 160 | index++ 161 | s.validTime = comm.TrimStr(frame[index : index+17]) 162 | index += 17 163 | s.atTime = comm.TrimStr(frame[index : index+17]) 164 | index += 17 165 | s.srcTermID = comm.TrimStr(frame[index : index+21]) 166 | index += 21 167 | s.chargeTermID = comm.TrimStr(frame[index : index+21]) 168 | index += 21 169 | s.destTermIDCount = frame[index] 170 | index++ 171 | for i := byte(0); i < s.destTermIDCount; i++ { 172 | s.destTermID = append(s.destTermID, comm.TrimStr(frame[index:index+21])) 173 | index += 21 174 | } 175 | s.msgLength = frame[index] 176 | index++ 177 | content := frame[index : index+int(s.msgLength)] 178 | s.msgBytes = content 179 | if content[0] == 0x05 && content[1] == 0x00 && content[2] == 0x03 { 180 | content = content[6:] 181 | } 182 | index += int(s.msgLength) 183 | tmp, _ := GbDecoder.Bytes(content) 184 | s.msgContent = string(tmp) 185 | s.reserve = comm.TrimStr(frame[index : index+8]) 186 | index += 8 187 | // 一个tlv至少5字节 188 | if uint32(index+5) < s.PacketLength { 189 | buf := bytes.NewBuffer(frame[index:]) 190 | s.tlvList, _ = comm.Read(buf) 191 | } 192 | return nil 193 | } 194 | 195 | func (s *Submit) ToResponse(code uint32) interface{} { 196 | header := *s.MessageHeader 197 | header.RequestId = CmdSubmitResp 198 | header.PacketLength = 26 199 | resp := &SubmitResp{MessageHeader: &header} 200 | resp.status = code 201 | resp.msgId = Seq80.NextVal() 202 | return resp 203 | } 204 | 205 | func (s *Submit) String() string { 206 | bts := s.msgBytes 207 | if s.msgLength > 6 { 208 | bts = s.msgBytes[:6] 209 | } 210 | return fmt.Sprintf("{ header: %v, msgType: %v, NeedReport: %v, Priority: %v, ServiceID: %v, "+ 211 | "feeType: %v, feeCode: %v, fixedFee: %v, msgFormat: %v, validTime: %v, AtTime: %v, SrcTermID: %v, "+ 212 | "chargeTermID: %v, destTermIDCount: %v, destTermID: %v, msgLength: %v, msgContent: %#x..., "+ 213 | "reserve: %v, tlvList: %s }", 214 | s.MessageHeader, s.msgType, s.needReport, s.priority, s.serviceID, 215 | s.feeType, s.feeCode, s.fixedFee, s.msgFormat, s.validTime, s.atTime, s.srcTermID, 216 | s.chargeTermID, s.destTermIDCount, s.destTermID, s.msgLength, bts, 217 | s.reserve, s.tlvList) 218 | } 219 | 220 | func (r *SubmitResp) Encode() []byte { 221 | frame := r.MessageHeader.Encode() 222 | index := 12 223 | copy(frame[index:index+10], r.msgId) 224 | index += 10 225 | binary.BigEndian.PutUint32(frame[index:index+4], r.status) 226 | return frame 227 | } 228 | 229 | func (r *SubmitResp) Decode(header *MessageHeader, frame []byte) error { 230 | // check 231 | if header == nil || header.RequestId != CmdSubmitResp || uint32(len(frame)) < (header.PacketLength-HeadLength) { 232 | return ErrorPacket 233 | } 234 | r.MessageHeader = header 235 | r.msgId = make([]byte, 10) 236 | copy(r.msgId, frame[0:10]) 237 | r.status = binary.BigEndian.Uint32(frame[10:14]) 238 | return nil 239 | } 240 | 241 | func (r *SubmitResp) String() string { 242 | return fmt.Sprintf("{ header: %s, msgId: %x, status: {%d:%s} }", r.MessageHeader, r.msgId, r.status, StatMap[r.status]) 243 | } 244 | 245 | func (r *SubmitResp) MsgId() []byte { 246 | return r.msgId 247 | } 248 | 249 | func (r *SubmitResp) Status() uint32 { 250 | return r.status 251 | } 252 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /codec/cmpp/submit.go: -------------------------------------------------------------------------------- 1 | package cmpp 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "strings" 7 | "time" 8 | 9 | "github.com/aaronwong1989/gosms/comm" 10 | ) 11 | 12 | // Submit 13 | // 3.0版 feeTerminalId、destTerminalId 均为32字节,无Reserve字段,有LinkId字段 14 | // 2.0版 feeTerminalId、destTerminalId 均为21字节,无LinkId字段,有Reserve字段 15 | 16 | type Submit struct { 17 | *MessageHeader // 消息头,【12字节】 18 | msgId uint64 // 信息标识,由 SP 接入的短信网关本身产 生,本处填空(0)。【8字节】 19 | pkTotal uint8 // 相同Msg_Id的信息总条数 【1字节】 20 | pkNumber uint8 // 相同Msg_Id的信息序号,从1开始 【1字节】 21 | registeredDel uint8 // 是否要求返回状态确认报告: 0:不需要,1:需要。【1字节】 22 | msgLevel uint8 // 信息级别,1-9 【1字节】 23 | serviceId string // 业务标识,是数字、字母和符号的组合。【10字节】 24 | feeUsertype uint8 // 计费用户类型字段 【1字节】 25 | feeTerminalId string // 被计费用户的号码(如本字节填空,则表示本字段无效,对谁计费参见Fee_UserType字段,本字段与Fee_UserType字段互斥)【32字节】 26 | feeTerminalType uint8 // 被计费用户的号码类型,0:真实号码;1:伪码 【1字节】 27 | tpPid uint8 // GSM协议类型。详细是解释请参考GSM03.40中的9.2.3.9 【1字节】 28 | tpUdhi uint8 // GSM协议类型。详细是解释请参考GSM03.40中的9.2.3.9 【1字节】 29 | msgFmt uint8 // 信息格式 【1字节】 30 | msgSrc string // 信息内容来源(SP_Id) 【6字节】 31 | feeType string // 资费类别【2字节】 32 | feeCode string // 资费代码(以分为单位) 【6字节】 33 | validTime string // 存活有效期,格式遵循SMPP3.3协议 【17字节】 34 | atTime string // 定时发送时间,格式遵循SMPP3.3协议 【17字节】 35 | srcId string // 源号码 SP的服务代码或前缀为服务代码的长号码, 网关将该号码完整的填到SMPP协议Submit_SM消息相应的source_addr字段,该号码最终在用户手机上显示为短消息的主叫号码【21字节】 36 | destUsrTl uint8 // 接收信息的用户数量(小于100个用户) 【1字节】 37 | destTerminalId string // 接收短信的MSISDN号码【32*DestUsrTl字节】 38 | termIds []byte // DestTerminalId编码后的格式 39 | destTerminalType uint8 // 接收短信的用户的号码类型,0:真实号码;1:伪码【1字节】 40 | msgLength uint8 // 信息长度(Msg_Fmt值为0时:<160个字节;其它<=140个字节) 【1字节】 41 | msgContent string // 信息内容 【MsgLength字节】 42 | msgBytes []byte // 消息内容按照Msg_Fmt编码后的数据 43 | linkID string // 点播业务使用的LinkID,非点播类业务的MT流程不使用该字段 【20字节】 44 | } 45 | 46 | func NewSubmit(phones []string, content string, opts ...Option) (messages []*Submit) { 47 | options := loadOptions(opts...) 48 | baseLen := 138 49 | if V3() { 50 | baseLen = 163 51 | } 52 | header := &MessageHeader{TotalLength: uint32(baseLen), CommandId: CMPP_SUBMIT, SequenceId: uint32(Seq32.NextVal())} 53 | mt := &Submit{MessageHeader: header} 54 | 55 | setOptions(mt, options) 56 | mt.msgFmt = MsgFmt(content) 57 | 58 | mt.destUsrTl = uint8(len(phones)) 59 | mt.destTerminalId = strings.Join(phones, ",") 60 | idLen := 21 61 | if V3() { 62 | idLen = 32 63 | } 64 | termIds := make([]byte, idLen*int(mt.destUsrTl)) 65 | for i, p := range phones { 66 | copy(termIds[i*idLen:(i+1)*idLen], p) 67 | } 68 | mt.termIds = termIds 69 | 70 | mt.msgSrc = Conf.GetString("source-addr") 71 | 72 | mt.msgContent = content 73 | slices := MsgSlices(mt.msgFmt, content) 74 | 75 | if len(slices) == 1 { 76 | mt.pkTotal = 1 77 | mt.pkNumber = 1 78 | mt.msgLength = uint8(len(slices[0])) 79 | mt.msgBytes = slices[0] 80 | mt.TotalLength = uint32(baseLen + len(termIds) + len(slices[0])) 81 | return []*Submit{mt} 82 | } else { 83 | mt.tpUdhi = 1 84 | mt.pkTotal = uint8(len(slices)) 85 | 86 | for i, msgBytes := range slices { 87 | // 拷贝 mt 88 | tmp := *mt 89 | tmpHead := *tmp.MessageHeader 90 | sub := &tmp 91 | sub.MessageHeader = &tmpHead 92 | if i != 0 { 93 | sub.SequenceId = uint32(Seq32.NextVal()) 94 | } 95 | sub.pkNumber = uint8(i + 1) 96 | sub.msgLength = uint8(len(msgBytes)) 97 | sub.msgBytes = msgBytes 98 | sub.TotalLength = uint32(baseLen + len(termIds) + len(msgBytes)) 99 | messages = append(messages, sub) 100 | } 101 | 102 | return messages 103 | } 104 | } 105 | 106 | func (sub *Submit) Encode() []byte { 107 | frame := sub.MessageHeader.Encode() 108 | frame[20] = sub.pkTotal 109 | frame[21] = sub.pkNumber 110 | frame[22] = sub.registeredDel 111 | frame[23] = sub.msgLevel 112 | copy(frame[24:34], sub.serviceId) 113 | frame[34] = sub.feeUsertype 114 | index := 35 115 | if V3() { 116 | copy(frame[index:index+32], sub.feeTerminalId) 117 | index += 32 118 | frame[index] = sub.feeTerminalType 119 | index++ 120 | } else { 121 | copy(frame[index:index+21], sub.feeTerminalId) 122 | index += 21 123 | } 124 | frame[index] = sub.tpPid 125 | index++ 126 | frame[index] = sub.tpUdhi 127 | index++ 128 | frame[index] = sub.msgFmt 129 | index++ 130 | copy(frame[index:index+6], sub.msgSrc) 131 | index += 6 132 | copy(frame[index:index+2], sub.feeType) 133 | index += 2 134 | copy(frame[index:index+6], sub.feeCode) 135 | index += 6 136 | copy(frame[index:index+17], sub.validTime) 137 | index += 17 138 | copy(frame[index:index+17], sub.atTime) 139 | index += 17 140 | copy(frame[index:index+21], sub.srcId) 141 | index += 21 142 | frame[index] = sub.destUsrTl 143 | index++ 144 | copy(frame[index:index+len(sub.termIds)], sub.termIds) 145 | index += len(sub.termIds) 146 | if V3() { 147 | frame[index] = sub.destTerminalType 148 | index++ 149 | } 150 | frame[index] = sub.msgLength 151 | index++ 152 | copy(frame[index:index+len(sub.msgBytes)], sub.msgBytes) 153 | index += len(sub.msgBytes) 154 | if V3() { 155 | copy(frame[index:index+20], sub.linkID) 156 | } 157 | return frame 158 | } 159 | 160 | func (sub *Submit) Decode(header *MessageHeader, frame []byte) error { 161 | // check 162 | if header == nil || header.CommandId != CMPP_SUBMIT || uint32(len(frame)) < (header.TotalLength-HeadLength) { 163 | return ErrorPacket 164 | } 165 | sub.MessageHeader = header 166 | // msgId uint64 167 | index := 8 168 | sub.pkTotal = frame[index] 169 | index++ 170 | sub.pkNumber = frame[index] 171 | index++ 172 | sub.registeredDel = frame[index] 173 | index++ 174 | sub.msgLevel = frame[index] 175 | index++ 176 | sub.serviceId = TrimStr(frame[index : index+10]) 177 | index += 10 178 | sub.feeUsertype = frame[index] 179 | index++ 180 | if V3() { 181 | sub.feeTerminalId = TrimStr(frame[index : index+32]) 182 | index += 32 183 | sub.feeTerminalType = frame[index] 184 | index++ 185 | } else { 186 | sub.feeTerminalId = TrimStr(frame[index : index+21]) 187 | index += 21 188 | } 189 | sub.tpPid = frame[index] 190 | index++ 191 | sub.tpUdhi = frame[index] 192 | index++ 193 | sub.msgFmt = frame[index] 194 | index++ 195 | sub.msgSrc = TrimStr(frame[index : index+6]) 196 | index += 6 197 | sub.feeType = TrimStr(frame[index : index+2]) 198 | index += 2 199 | sub.feeCode = TrimStr(frame[index : index+6]) 200 | index += 6 201 | sub.validTime = TrimStr(frame[index : index+17]) 202 | index += 17 203 | sub.atTime = TrimStr(frame[index : index+17]) 204 | index += 17 205 | sub.srcId = TrimStr(frame[index : index+21]) 206 | index += 21 207 | sub.destUsrTl = frame[index] 208 | index++ 209 | l := int(sub.destUsrTl * 21) 210 | if V3() { 211 | l = int(sub.destUsrTl) << 5 212 | } 213 | sub.destTerminalId = TrimStr(frame[index : index+l]) 214 | index += l 215 | if V3() { 216 | sub.destTerminalType = frame[index] 217 | index++ 218 | } 219 | sub.msgLength = frame[index] 220 | index++ 221 | content := frame[index : index+int(sub.msgLength)] 222 | sub.msgBytes = content 223 | if content[0] == 0x05 && content[1] == 0x00 && content[2] == 0x03 { 224 | content = content[6:] 225 | } 226 | if sub.msgFmt == 8 { 227 | sub.msgContent = comm.Ucs2Decode(content) 228 | } else { 229 | sub.msgContent = TrimStr(content) 230 | } 231 | index += int(sub.msgLength) 232 | if V3() { 233 | sub.linkID = TrimStr(frame[index : index+20]) 234 | } 235 | return nil 236 | } 237 | 238 | type SubmitResp struct { 239 | *MessageHeader 240 | msgId uint64 241 | result uint32 242 | } 243 | 244 | func (sub *Submit) ToResponse(result uint32) interface{} { 245 | resp := &SubmitResp{} 246 | header := *sub.MessageHeader 247 | resp.MessageHeader = &header 248 | resp.CommandId = CMPP_SUBMIT_RESP 249 | resp.TotalLength = HeadLength + 9 250 | if V3() { 251 | resp.TotalLength = HeadLength + 12 252 | } 253 | if result == 0 { 254 | resp.msgId = uint64(Seq64.NextVal()) 255 | } 256 | resp.result = result 257 | return resp 258 | } 259 | 260 | func (sub *Submit) ToDeliveryReport(msgId uint64) *Delivery { 261 | d := Delivery{} 262 | 263 | head := *sub.MessageHeader 264 | d.MessageHeader = &head 265 | d.TotalLength = 145 266 | if V3() { 267 | d.TotalLength = 169 268 | } 269 | d.CommandId = CMPP_DELIVER 270 | d.SequenceId = uint32(Seq32.NextVal()) 271 | 272 | d.registeredDelivery = 1 273 | d.msgLength = 60 274 | d.destId = sub.srcId 275 | d.serviceId = sub.serviceId 276 | d.srcTerminalId = sub.destTerminalId 277 | d.srcTerminalType = sub.destTerminalType 278 | 279 | subTime := time.Now().Format("0601021504") 280 | doneTime := time.Now().Add(10 * time.Second).Format("0601021504") 281 | report := NewReport(msgId, sub.destTerminalId, subTime, doneTime) 282 | d.report = report 283 | 284 | return &d 285 | } 286 | 287 | func (resp *SubmitResp) Encode() []byte { 288 | frame := resp.MessageHeader.Encode() 289 | binary.BigEndian.PutUint64(frame[12:20], resp.msgId) 290 | if V3() { 291 | binary.BigEndian.PutUint32(frame[20:24], resp.result) 292 | } else { 293 | frame[20] = byte(resp.result) 294 | } 295 | return frame 296 | } 297 | func (resp *SubmitResp) Decode(header *MessageHeader, frame []byte) error { 298 | // check 299 | if header == nil || header.CommandId != CMPP_SUBMIT_RESP || uint32(len(frame)) < (header.TotalLength-HeadLength) { 300 | return ErrorPacket 301 | } 302 | resp.MessageHeader = header 303 | resp.msgId = binary.BigEndian.Uint64(frame[0:8]) 304 | if V3() { 305 | resp.result = binary.BigEndian.Uint32(frame[8:12]) 306 | } else { 307 | resp.result = uint32(frame[8]) 308 | } 309 | return nil 310 | } 311 | 312 | func (resp *SubmitResp) MsgId() uint64 { 313 | return resp.msgId 314 | } 315 | 316 | func (resp *SubmitResp) Result() uint32 { 317 | return resp.result 318 | } 319 | 320 | func MsgSlices(fmt uint8, content string) (slices [][]byte) { 321 | var msgBytes []byte 322 | // 含中文 323 | if fmt == 8 { 324 | msgBytes = comm.Ucs2Encode(content) 325 | slices = comm.ToTPUDHISlices(msgBytes, 140) 326 | } else { 327 | // 纯英文 328 | msgBytes = []byte(content) 329 | slices = comm.ToTPUDHISlices(msgBytes, 160) 330 | } 331 | return 332 | } 333 | 334 | // MsgFmt 通过消息内容判断,设置编码格式。 335 | // 如果是纯拉丁字符采用0:ASCII串 336 | // 如果含多字节字符,这采用8:UCS-2编码 337 | func MsgFmt(content string) uint8 { 338 | if len(content) < 2 { 339 | return 0 340 | } 341 | all7bits := len(content) == len([]rune(content)) 342 | if all7bits { 343 | return 0 344 | } else { 345 | return 8 346 | } 347 | } 348 | 349 | // 设置可选项 350 | func setOptions(sub *Submit, opts *MtOptions) { 351 | if opts.FeeUsertype != uint8(0xf) { 352 | sub.feeUsertype = opts.FeeUsertype 353 | } else { 354 | sub.feeUsertype = byte(Conf.GetInt("fee-user-type")) 355 | } 356 | 357 | if opts.MsgLevel != uint8(0xf) { 358 | sub.msgLevel = opts.MsgLevel 359 | } else { 360 | sub.msgLevel = byte(Conf.GetInt("default-msg-level")) 361 | } 362 | 363 | if opts.RegisteredDel != uint8(0xf) { 364 | sub.registeredDel = opts.RegisteredDel 365 | } else { 366 | sub.registeredDel = byte(Conf.GetInt("need-report")) 367 | } 368 | 369 | if opts.FeeTerminalType != uint8(0xf) { 370 | sub.feeTerminalType = opts.FeeTerminalType 371 | } else { 372 | sub.feeTerminalType = byte(Conf.GetInt("fee-terminal-type")) 373 | } 374 | 375 | if opts.FeeType != "" { 376 | sub.feeType = opts.FeeType 377 | } else { 378 | sub.feeType = Conf.GetString("fee-type") 379 | } 380 | 381 | if opts.AtTime != "" { 382 | sub.atTime = opts.AtTime 383 | } 384 | 385 | if opts.ValidTime != "" { 386 | sub.validTime = opts.ValidTime 387 | } else { 388 | t := time.Now().Add(Conf.GetDuration("default-valid-duration")) 389 | s := t.Format("060102150405") 390 | sub.validTime = s + "032+" 391 | } 392 | 393 | if opts.FeeCode != "" { 394 | sub.feeCode = opts.FeeCode 395 | } else { 396 | sub.feeCode = Conf.GetString("fee-code") 397 | } 398 | 399 | if opts.FeeTerminalId != "" { 400 | sub.feeTerminalId = opts.FeeTerminalId 401 | } else { 402 | sub.feeTerminalId = Conf.GetString("fee-terminal-id") 403 | } 404 | 405 | if opts.SrcId != "" { 406 | sub.srcId = opts.SrcId 407 | } else { 408 | sub.srcId = Conf.GetString("sms-display-no") 409 | } 410 | 411 | if opts.ServiceId != "" { 412 | sub.serviceId = opts.ServiceId 413 | } else { 414 | sub.serviceId = Conf.GetString("service-id") 415 | } 416 | 417 | if opts.LinkID != "" { 418 | sub.linkID = opts.LinkID 419 | } else { 420 | sub.linkID = Conf.GetString("link-id") 421 | } 422 | } 423 | 424 | func (sub *Submit) String() string { 425 | l := len(sub.msgBytes) 426 | if l > 6 { 427 | l = 6 428 | } 429 | return fmt.Sprintf("{ header: %s, msgId: %v, pkTotal: %v, pkNumber: %v, registeredDel: %v, "+ 430 | "msgLevel: %v, serviceId: %v, feeUsertype: %v, feeTerminalId: %v, feeTerminalType: %v, tpPid: %v, "+ 431 | "tpUdhi: %v, msgFmt: %v, msgSrc: %v, feeType: %v, feeCode: %v, validTime: %v, atTime: %v, srcId: %v, "+ 432 | "destUsrTl: %v, destTerminalId: [%v], destTerminalType: %v, msgLength: %v, msgBytes: %0x..., linkID: %v }", 433 | sub.MessageHeader, sub.msgId, sub.pkTotal, sub.pkNumber, sub.registeredDel, 434 | sub.msgLevel, sub.serviceId, sub.feeUsertype, sub.feeTerminalId, sub.feeTerminalType, sub.tpPid, 435 | sub.tpUdhi, sub.msgFmt, sub.msgSrc, sub.feeType, sub.feeCode, sub.validTime, sub.atTime, sub.srcId, 436 | sub.destUsrTl, sub.destTerminalId, sub.destTerminalType, sub.msgLength, sub.msgBytes[0:l], sub.linkID, 437 | ) 438 | } 439 | 440 | func (resp *SubmitResp) String() string { 441 | return fmt.Sprintf("{ header: %s, msgId: %v, result: {%d: %s} }", resp.MessageHeader, resp.msgId, resp.result, SubmitResultMap[resp.result]) 442 | } 443 | 444 | var SubmitResultMap = map[uint32]string{ 445 | 0: "正确", 446 | 1: "消息结构错", 447 | 2: "命令字错", 448 | 3: "消息序号重复", 449 | 4: "消息长度错", 450 | 5: "资费代码错", 451 | 6: "超过最大信息长", 452 | 7: "业务代码错", 453 | 8: "流量控制错", 454 | 9: "本网关不负责服务此计费号码", 455 | 10: "Src_Id 错误", 456 | 11: "Msg_src 错误", 457 | 12: "Fee_terminal_Id 错误", 458 | 13: "Dest_terminal_Id 错误", 459 | } 460 | -------------------------------------------------------------------------------- /cmd/ismg/smgp/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | _ "net/http/pprof" 7 | "sync" 8 | "time" 9 | 10 | "github.com/panjf2000/ants/v2" 11 | "github.com/panjf2000/gnet/v2" 12 | "github.com/panjf2000/gnet/v2/pkg/pool/goroutine" 13 | 14 | "github.com/aaronwong1989/gosms/codec/smgp" 15 | "github.com/aaronwong1989/gosms/comm" 16 | "github.com/aaronwong1989/gosms/comm/logging" 17 | ) 18 | 19 | type Server struct { 20 | gnet.BuiltinEventEngine 21 | engine gnet.Engine 22 | protocol string 23 | address string 24 | multicore bool 25 | pool *goroutine.Pool 26 | conMap sync.Map 27 | window chan struct{} 28 | } 29 | 30 | var ( 31 | poolSize int 32 | windowSize int 33 | ) 34 | 35 | func StartServer() { 36 | var port int 37 | var multicore bool 38 | flag.IntVar(&port, "port", 9100, "--port 9100") 39 | flag.BoolVar(&multicore, "multicore", true, "--multicore=true") 40 | flag.Parse() 41 | 42 | poolSize = smgp.Conf.GetInt("max-pool-size") 43 | windowSize = smgp.Conf.GetInt("receive-window-size") 44 | 45 | // 定义异步工作Go程池 46 | options := ants.Options{ 47 | ExpiryDuration: time.Minute, // 1 分钟内不被使用的worker会被清除 48 | Nonblocking: false, // 如果为true,worker池满了后提交任务会直接返回nil 49 | MaxBlockingTasks: poolSize, // blocking模式有效,否则worker池满了后提交任务会直接返回nil 50 | PreAlloc: false, 51 | PanicHandler: func(e interface{}) { 52 | log.Errorf("%v", e) 53 | }, 54 | } 55 | pool, _ := ants.NewPool(poolSize, ants.WithOptions(options)) 56 | defer pool.Release() 57 | 58 | ss := &Server{ 59 | protocol: "tcp", 60 | address: fmt.Sprintf(":%d", port), 61 | multicore: multicore, 62 | pool: pool, 63 | window: make(chan struct{}, windowSize), // 用通道控制消息接收窗口 64 | } 65 | 66 | comm.StartMonitor(port) 67 | log.Infof("current pid is %s.", comm.SavePid("smgp.pid")) 68 | 69 | err := gnet.Run(ss, ss.protocol+"://"+ss.address, gnet.WithMulticore(multicore), gnet.WithTicker(true)) 70 | log.Errorf("server(%s://%s) exits with error: %v", ss.protocol, ss.address, err) 71 | } 72 | 73 | func (s *Server) OnBoot(eng gnet.Engine) (action gnet.Action) { 74 | log.Infof("[%-9s] running server on %s with multi-core=%t", "OnBoot", fmt.Sprintf("%s://%s", s.protocol, s.address), s.multicore) 75 | s.engine = eng 76 | return 77 | } 78 | 79 | func (s *Server) OnShutdown(eng gnet.Engine) { 80 | log.Warnf("[%-9s] shutdown server %s ...", "OnShutdown", fmt.Sprintf("%s://%s", s.protocol, s.address)) 81 | for eng.CountConnections() > 0 { 82 | log.Warnf("[%-9s] active connections is %d, waiting...", eng.CountConnections()) 83 | time.Sleep(10 * time.Millisecond) 84 | } 85 | log.Warnf("[%-9s] shutdown server %s completed!", "OnShutdown", fmt.Sprintf("%s://%s", s.protocol, s.address)) 86 | } 87 | 88 | func (s *Server) OnOpen(c gnet.Conn) (out []byte, action gnet.Action) { 89 | if s.countConn() >= smgp.Conf.GetInt("max-cons") { 90 | log.Warnf("[%-9s] [%v<->%v] FLOW CONTROL:connections threshold reached, closing new connection...", "OnOpen", c.RemoteAddr(), c.LocalAddr()) 91 | return nil, gnet.Close 92 | } else if len(s.window) == windowSize { 93 | log.Warnf("[%-9s] [%v<->%v] FLOW CONTROL:receive window threshold reached, closing new connection...", "OnOpen", c.RemoteAddr(), c.LocalAddr()) 94 | // 已达到窗口时,拒绝新的连接 95 | return nil, gnet.Close 96 | } else { 97 | log.Infof("[%-9s] [%v<->%v] activeCons=%d.", "OnOpen", c.RemoteAddr(), c.LocalAddr(), s.activeCons()) 98 | return 99 | } 100 | } 101 | 102 | func (s *Server) OnClose(c gnet.Conn, e error) (action gnet.Action) { 103 | log.Warnf("[%-9s] [%v<->%v] activeCons=%d, reason=%v.", "OnClose", c.RemoteAddr(), c.LocalAddr(), s.activeCons(), e) 104 | s.conMap.Delete(c.RemoteAddr().String()) 105 | return 106 | } 107 | 108 | func (s *Server) OnTraffic(c gnet.Conn) (action gnet.Action) { 109 | header := getHeader(c) 110 | if header == nil || header.PacketLength < 12 || header.PacketLength > 10240 { 111 | log.Warnf("[%-9s] [%v<->%v] decode error, header: %s, close session...", "OnTraffic", c.RemoteAddr(), c.LocalAddr(), header) 112 | return gnet.Close 113 | } 114 | action = checkReceiveWindow(s, c, header) 115 | if action == gnet.Close { 116 | return action 117 | } 118 | 119 | switch header.RequestId { 120 | case 0: // 触发限速 121 | return gnet.None 122 | case smgp.CmdLogin: 123 | return handleLogin(s, c, header) 124 | case smgp.CmdLoginResp: 125 | return gnet.None 126 | case smgp.CmdSubmit: 127 | return handleSubmit(s, c, header) 128 | case smgp.CmdSubmitResp: 129 | return gnet.None 130 | case smgp.CmdDeliver: 131 | return handleDelivery(s, c, header) 132 | case smgp.CmdDeliverResp: 133 | return handleDeliveryResp(s, c, header) 134 | case smgp.CmdActiveTest: 135 | return handActive(s, c, header) 136 | case smgp.CmdActiveTestResp: 137 | return handActiveResp(c, header) 138 | case smgp.CmdExit: 139 | return handleExit(s, c, header) 140 | case smgp.CmdExitResp: 141 | return handleExitResp(s, c, header) 142 | default: 143 | // 不合法包,关闭连接 144 | return gnet.Close 145 | } 146 | } 147 | 148 | func (s *Server) OnTick() (delay time.Duration, action gnet.Action) { 149 | log.Infof("[%-9s] %d active connections.", "OnTick", s.activeCons()) 150 | s.conMap.Range(func(key, value interface{}) bool { 151 | addr := key.(string) 152 | con, ok := value.(gnet.Conn) 153 | if ok { 154 | _ = s.pool.Submit(func() { 155 | at := smgp.NewActiveTest() 156 | err := con.AsyncWrite(at.Encode(), nil) 157 | if err == nil { 158 | log.Infof("[%-9s] >>> %s to %s", "OnTick", at, addr) 159 | } else { 160 | log.Errorf("[%-9s] >>> ACTIVE_TEST to %s, error: %v", "OnTick", addr, err) 161 | } 162 | }) 163 | } 164 | return true 165 | }) 166 | return smgp.Conf.GetDuration("active-test-duration"), gnet.None 167 | } 168 | 169 | func (s *Server) countConn() int { 170 | counter := 0 171 | s.conMap.Range(func(key, value interface{}) bool { 172 | counter++ 173 | return true 174 | }) 175 | return counter 176 | } 177 | 178 | func (s *Server) activeCons() int { 179 | return s.engine.CountConnections() 180 | } 181 | 182 | func handleLogin(s *Server, c gnet.Conn, header *smgp.MessageHeader) gnet.Action { 183 | frame := comm.TakeBytes(c, smgp.LoginLen-smgp.HeadLength) 184 | comm.LogHex(logging.DebugLevel, "Login", frame) 185 | 186 | connect := &smgp.Login{} 187 | err := connect.Decode(header, frame) 188 | if err != nil { 189 | log.Errorf("[%-9s] LOGIN ERROR: %v", "OnTraffic", err) 190 | return gnet.Close 191 | } 192 | 193 | log.Infof("[%-9s] <<< %s", "OnTraffic", connect) 194 | resp := connect.ToResponse(0).(*smgp.LoginResp) 195 | if resp.Status() != 0 { 196 | log.Errorf("[%-9s] LOGIN ERROR: Auth Error, status=(%d,%s)", "OnTraffic", resp.Status(), smgp.ConnectStatusMap[resp.Status()]) 197 | } 198 | 199 | // send smgp_connect_resp async 200 | _ = s.pool.Submit(func() { 201 | err = c.AsyncWrite(resp.Encode(), func(c gnet.Conn) error { 202 | log.Infof("[%-9s] >>> %s", "OnTraffic", resp) 203 | if resp.Status() == 0 { 204 | s.conMap.Store(c.RemoteAddr().String(), c) 205 | } else { 206 | // 客户端登录失败,关闭连接 207 | _ = c.Close() 208 | } 209 | return nil 210 | }) 211 | if err != nil { 212 | log.Errorf("[%-9s] LOGIN_RESP ERROR: %v", "OnTraffic", err) 213 | } 214 | }) 215 | return gnet.None 216 | } 217 | 218 | func handleExit(s *Server, c gnet.Conn, header *smgp.MessageHeader) gnet.Action { 219 | log.Infof("[%-9s] <<< %s", "OnTraffic", header) 220 | resp := smgp.NewExitResp(header.SequenceId) 221 | // send smgp_connect_resp async 222 | _ = s.pool.Submit(func() { 223 | err := c.AsyncWrite(resp.Encode(), func(c gnet.Conn) error { 224 | log.Infof("[%-9s] >>> %s", "OnTraffic", resp) 225 | s.conMap.Delete(c.RemoteAddr().String()) 226 | _ = c.Close() 227 | return nil 228 | }) 229 | if err != nil { 230 | log.Errorf("[%-9s] EXIT_RESP ERROR: %v", "OnTraffic", err) 231 | } 232 | }) 233 | return gnet.None 234 | } 235 | 236 | func handleExitResp(s *Server, c gnet.Conn, header *smgp.MessageHeader) gnet.Action { 237 | log.Infof("[%-9s] <<< %s", "OnTraffic", header) 238 | log.Infof("[%-9s] closing connection [%v<-->%v]", "OnTraffic", c.RemoteAddr(), c.LocalAddr()) 239 | s.conMap.Delete(c.RemoteAddr().String()) 240 | _ = c.Flush() 241 | _ = c.Close() 242 | return gnet.Close 243 | } 244 | 245 | // 处理上行消息 246 | func handleDelivery(s *Server, c gnet.Conn, header *smgp.MessageHeader) gnet.Action { 247 | // check connect 248 | _, ok := s.conMap.Load(c.RemoteAddr().String()) 249 | if !ok { 250 | log.Warnf("[%-9s] unLogin connection: %s, closing...", "OnTraffic", c.RemoteAddr()) 251 | return gnet.Close 252 | } 253 | 254 | frame := comm.TakeBytes(c, int(header.PacketLength-smgp.HeadLength)) 255 | comm.LogHex(logging.DebugLevel, "Deliver", frame) 256 | dly := &smgp.Deliver{} 257 | err := dly.Decode(header, frame) 258 | if err != nil { 259 | log.Errorf("[%-9s] DELIVER ERROR: %v", "OnTraffic", err) 260 | return gnet.Close 261 | } 262 | log.Debugf("[%-9s] <<< %s", "OnTraffic", dly) 263 | // handle message async 264 | _ = s.pool.Submit(func() { 265 | // 模拟消息处理耗时 266 | _ = processTime() 267 | 268 | rtCode := uint32(0) 269 | if comm.DiceCheck(smgp.Conf.GetFloat64("success-rate")) { 270 | // 失败消息的返回码 271 | rtCode = 39 272 | } 273 | resp := dly.ToResponse(rtCode).(*smgp.DeliverResp) 274 | // 发送响应 275 | err := c.AsyncWrite(resp.Encode(), func(c gnet.Conn) error { 276 | log.Debugf("[%-9s] >>> %s", "OnTraffic", resp) 277 | return nil 278 | }) 279 | if err != nil { 280 | log.Errorf("[%-9s] DELIVERY_RESP ERROR: %v", "OnTraffic", err) 281 | } 282 | }) 283 | return gnet.None 284 | } 285 | 286 | // 处理上行消息Resp 287 | func handleDeliveryResp(s *Server, c gnet.Conn, header *smgp.MessageHeader) gnet.Action { 288 | // check connect 289 | _, ok := s.conMap.Load(c.RemoteAddr().String()) 290 | if !ok { 291 | log.Warnf("[%-9s] unLogin connection: %s, closing...", "OnTraffic", c.RemoteAddr()) 292 | return gnet.Close 293 | } 294 | frame := comm.TakeBytes(c, int(header.PacketLength-smgp.HeadLength)) 295 | comm.LogHex(logging.DebugLevel, "Deliver", frame) 296 | 297 | resp := &smgp.DeliverResp{} 298 | err := resp.Decode(header, frame) 299 | if err != nil { 300 | log.Errorf("[%-9s] DELIVER_RESP ERROR: %v", "OnTraffic", err) 301 | return gnet.Close 302 | } 303 | log.Debugf("[%-9s] <<< %s", "OnTraffic", resp) 304 | 305 | return gnet.None 306 | } 307 | 308 | func handleSubmit(s *Server, c gnet.Conn, header *smgp.MessageHeader) gnet.Action { 309 | // check connect 310 | _, ok := s.conMap.Load(c.RemoteAddr().String()) 311 | if !ok { 312 | log.Warnf("[%-9s] unLogin connection: %s, closing...", "OnTraffic", c.RemoteAddr()) 313 | return gnet.Close 314 | } 315 | 316 | frame := comm.TakeBytes(c, int(header.PacketLength-smgp.HeadLength)) 317 | comm.LogHex(logging.DebugLevel, "Submit", frame) 318 | sub := &smgp.Submit{} 319 | err := sub.Decode(header, frame) 320 | if err != nil { 321 | log.Errorf("[%-9s] SUBMIT ERROR: %v", "OnTraffic", err) 322 | return gnet.Close 323 | } 324 | log.Debugf("[%-9s] <<< %s", "OnTraffic", sub) 325 | // handle message async 326 | _ = s.pool.Submit(mtAsyncHandler(s, c, sub)) 327 | return gnet.None 328 | } 329 | 330 | func mtAsyncHandler(s *Server, c gnet.Conn, sub *smgp.Submit) func() { 331 | return func() { 332 | // 采用通道控制消息收发速度,向通道发送信号 333 | s.window <- struct{}{} 334 | defer func() { 335 | // defer函数消费信号,确保每个消息的信号最终都会被消费 336 | <-s.window 337 | }() 338 | 339 | // 模拟消息处理耗时,可配置 340 | processTime := processTime() 341 | 342 | rtCode := uint32(0) 343 | if comm.DiceCheck(smgp.Conf.GetFloat64("success-rate")) { 344 | // 失败消息的返回码 345 | rtCode = 39 346 | } 347 | resp := sub.ToResponse(rtCode).(*smgp.SubmitResp) 348 | // 发送响应 349 | err := c.AsyncWrite(resp.Encode(), func(c gnet.Conn) error { 350 | log.Debugf("[%-9s] >>> %s", "OnTraffic", resp) 351 | return nil 352 | }) 353 | if err != nil { 354 | log.Errorf("[%-9s] SUBMIT_RESP ERROR: %v", "OnTraffic", err) 355 | } 356 | 357 | // 发送状态报告 358 | if resp.Status() == 0 { 359 | _ = s.pool.Submit(reportAsyncSender(c, sub, resp.MsgId(), processTime)) 360 | } 361 | } 362 | } 363 | 364 | func reportAsyncSender(c gnet.Conn, sub *smgp.Submit, msgId []byte, wait time.Duration) func() { 365 | return func() { 366 | if comm.DiceCheck(smgp.Conf.GetFloat64("success-rate")) { 367 | return 368 | } 369 | dly := smgp.NewDeliveryReport(sub, msgId) 370 | // 模拟状态报告发送前的耗时 371 | ms := smgp.Conf.GetInt("fix-report-resp-ms") 372 | if ms > 0 { 373 | processTime := wait + time.Duration(ms) 374 | time.Sleep(processTime * time.Millisecond) 375 | } 376 | // 发送状态报告 377 | err := c.AsyncWrite(dly.Encode(), func(c gnet.Conn) error { 378 | log.Debugf("[%-9s] >>> %s", "OnTraffic", dly) 379 | return nil 380 | }) 381 | if err != nil { 382 | log.Errorf("[%-9s] DELIVERY_REPORT ERROR: %v", "OnTraffic", err) 383 | } 384 | } 385 | } 386 | 387 | func handActive(s *Server, c gnet.Conn, header *smgp.MessageHeader) (action gnet.Action) { 388 | resp := smgp.NewActiveTestResp(header.SequenceId) 389 | // send active_resp async 390 | _ = s.pool.Submit(func() { 391 | err := c.AsyncWrite(resp.Encode(), func(c gnet.Conn) error { 392 | log.Infof("[%-9s] >>> %s", "OnTraffic", resp) 393 | return nil 394 | }) 395 | if err != nil { 396 | log.Errorf("[%-9s] ACTIVE_TEST_RESP ERROR: %v", "OnTraffic", err) 397 | } 398 | }) 399 | return gnet.None 400 | } 401 | 402 | func handActiveResp(c gnet.Conn, header *smgp.MessageHeader) (action gnet.Action) { 403 | log.Infof("[%-9s] <<< %s from %s", "OnTraffic", header, c.RemoteAddr()) 404 | return gnet.None 405 | } 406 | 407 | func getHeader(c gnet.Conn) *smgp.MessageHeader { 408 | frame := comm.TakeBytes(c, smgp.HeadLength) 409 | if frame == nil { 410 | return nil 411 | } 412 | comm.LogHex(logging.DebugLevel, "Header", frame) 413 | 414 | header := smgp.MessageHeader{} 415 | err := header.Decode(frame) 416 | if err != nil { 417 | log.Errorf("[%-9s] decode error: %v", "OnTraffic", err) 418 | return nil 419 | } 420 | return &header 421 | } 422 | 423 | func checkReceiveWindow(s *Server, c gnet.Conn, header *smgp.MessageHeader) gnet.Action { 424 | if len(s.window) == windowSize && header.RequestId == smgp.CmdSubmit { 425 | log.Warnf("[%-9s] FLOW CONTROL:receive window threshold reached.", "OnTraffic") 426 | l := int(header.PacketLength - smgp.HeadLength) 427 | discard, err := c.Discard(l) 428 | if err != nil || discard != l { 429 | return gnet.Close 430 | } 431 | sub := &smgp.Submit{} 432 | sub.MessageHeader = header 433 | resp := sub.ToResponse(1).(*smgp.SubmitResp) 434 | // 发送响应 435 | err = c.AsyncWrite(resp.Encode(), func(c gnet.Conn) error { 436 | log.Debugf("[%-9s] >>> %s", "OnTraffic", resp) 437 | return nil 438 | }) 439 | if err != nil { 440 | log.Errorf("[%-9s] SUBMIT_RESP ERROR: %v", "OnTraffic", err) 441 | return gnet.Close 442 | } 443 | header.RequestId = 0 444 | } 445 | return gnet.None 446 | } 447 | 448 | func processTime() time.Duration { 449 | // 模拟消息处理耗时,可配置 450 | processTime := time.Duration(0) 451 | if smgp.Conf.GetInt("min-submit-resp-ms") > 0 && smgp.Conf.GetInt("max-submit-resp-ms") > smgp.Conf.GetInt("min-submit-resp-ms") { 452 | processTime := time.Duration(comm.RandNum( 453 | int32(smgp.Conf.GetInt("min-submit-resp-ms")), 454 | int32(smgp.Conf.GetInt("max-submit-resp-ms")), 455 | )) 456 | time.Sleep(processTime * time.Millisecond) 457 | } 458 | return processTime 459 | } 460 | -------------------------------------------------------------------------------- /cmd/ismg/cmpp/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net/http" 7 | _ "net/http/pprof" 8 | "strconv" 9 | "sync" 10 | "time" 11 | 12 | "github.com/panjf2000/ants/v2" 13 | "github.com/panjf2000/gnet/v2" 14 | "github.com/panjf2000/gnet/v2/pkg/pool/goroutine" 15 | 16 | "github.com/aaronwong1989/gosms/codec/cmpp" 17 | "github.com/aaronwong1989/gosms/comm" 18 | "github.com/aaronwong1989/gosms/comm/logging" 19 | ) 20 | 21 | type Server struct { 22 | gnet.BuiltinEventEngine 23 | engine gnet.Engine 24 | protocol string 25 | address string 26 | multicore bool 27 | pool *goroutine.Pool 28 | conMap sync.Map 29 | window chan struct{} 30 | } 31 | 32 | var ( 33 | poolSize int 34 | windowSize int 35 | ) 36 | 37 | func StartServer() { 38 | var port int 39 | var multicore bool 40 | flag.IntVar(&port, "port", 9000, "--port 9000") 41 | flag.BoolVar(&multicore, "multicore", true, "--multicore=true") 42 | flag.Parse() 43 | 44 | // 获取配置信息 45 | poolSize = cmpp.Conf.GetInt("max-pool-size") 46 | windowSize = cmpp.Conf.GetInt("receive-window-size") 47 | 48 | // 定义异步工作Go程池 49 | options := ants.Options{ 50 | ExpiryDuration: time.Minute, // 1 分钟内不被使用的worker会被清除 51 | Nonblocking: false, // 如果为true,worker池满了后提交任务会直接返回nil 52 | MaxBlockingTasks: poolSize, // blocking模式有效,否则worker池满了后提交任务会直接返回nil 53 | PreAlloc: false, 54 | PanicHandler: func(e interface{}) { 55 | log.Errorf("%v", e) 56 | }, 57 | } 58 | pool, _ := ants.NewPool(poolSize, ants.WithOptions(options)) 59 | defer pool.Release() 60 | 61 | ss := &Server{ 62 | protocol: "tcp", 63 | address: fmt.Sprintf(":%d", port), 64 | multicore: multicore, 65 | pool: pool, 66 | window: make(chan struct{}, windowSize), // 用通道控制消息接收窗口 67 | } 68 | 69 | startMonitor(port) 70 | log.Infof("current pid is %s.", comm.SavePid("cmpp.pid")) 71 | 72 | err := gnet.Run(ss, ss.protocol+"://"+ss.address, gnet.WithMulticore(multicore), gnet.WithTicker(true)) 73 | log.Errorf("server(%s://%s) exits with error: %v", ss.protocol, ss.address, err) 74 | } 75 | 76 | // 开启pprof,监听请求 77 | func startMonitor(port int) { 78 | go func() { 79 | addr := strconv.Itoa(port + 1) 80 | log.Infof("[Pprof ] http://localhost:%s/debug/pprof/", addr) 81 | if err := http.ListenAndServe(":"+addr, nil); err != nil { 82 | log.Infof("start pprof failed on %s", addr) 83 | } 84 | }() 85 | } 86 | 87 | func (s *Server) OnBoot(eng gnet.Engine) (action gnet.Action) { 88 | log.Infof("[%-9s] running server on %s with multi-core=%t", "OnBoot", fmt.Sprintf("%s://%s", s.protocol, s.address), s.multicore) 89 | s.engine = eng 90 | return 91 | } 92 | 93 | func (s *Server) OnShutdown(eng gnet.Engine) { 94 | log.Warnf("[%-9s] shutdown server %s ...", "OnShutdown", fmt.Sprintf("%s://%s", s.protocol, s.address)) 95 | for eng.CountConnections() > 0 { 96 | log.Warnf("[%-9s] active connections is %d, waiting...", eng.CountConnections()) 97 | time.Sleep(10 * time.Millisecond) 98 | } 99 | log.Warnf("[%-9s] shutdown server %s completed!", "OnShutdown", fmt.Sprintf("%s://%s", s.protocol, s.address)) 100 | } 101 | 102 | func (s *Server) OnOpen(c gnet.Conn) (out []byte, action gnet.Action) { 103 | if s.countConn() >= cmpp.Conf.GetInt("max-cons") { 104 | log.Warnf("[%-9s] [%v<->%v] FLOW CONTROL:connections threshold reached, closing new connection...", "OnOpen", c.RemoteAddr(), c.LocalAddr()) 105 | return nil, gnet.Close 106 | } else if len(s.window) == windowSize { 107 | log.Warnf("[%-9s] [%v<->%v] FLOW CONTROL:receive window threshold reached, closing new connection...", "OnOpen", c.RemoteAddr(), c.LocalAddr()) 108 | // 已达到窗口时,拒绝新的连接 109 | return nil, gnet.Close 110 | } else { 111 | log.Infof("[%-9s] [%v<->%v] activeCons=%d.", "OnOpen", c.RemoteAddr(), c.LocalAddr(), s.activeCons()) 112 | return 113 | } 114 | } 115 | 116 | func (s *Server) OnClose(c gnet.Conn, e error) (action gnet.Action) { 117 | log.Warnf("[%-9s] [%v<->%v] activeCons=%d, reason=%v.", "OnClose", c.RemoteAddr(), c.LocalAddr(), s.activeCons(), e) 118 | s.conMap.Delete(c.RemoteAddr().String()) 119 | return 120 | } 121 | 122 | func (s *Server) OnTraffic(c gnet.Conn) (action gnet.Action) { 123 | header := getHeader(c) 124 | // 防止粘包检测,不合法包,关闭连接 125 | if header == nil || header.TotalLength < 12 || header.TotalLength > 10240 { 126 | log.Warnf("[%-9s] [%v<->%v] decode error, header: %s, close session...", "OnTraffic", c.RemoteAddr(), c.LocalAddr(), header) 127 | return gnet.Close 128 | } 129 | action = checkReceiveWindow(s, c, header) 130 | if action == gnet.Close { 131 | return action 132 | } 133 | 134 | switch header.CommandId { 135 | case 0: // 触发限速 136 | return gnet.None 137 | case cmpp.CMPP_CONNECT: 138 | return handleConnect(s, c, header) 139 | case cmpp.CMPP_CONNECT_RESP: 140 | return gnet.None 141 | case cmpp.CMPP_SUBMIT: 142 | return handleSubmit(s, c, header) 143 | case cmpp.CMPP_SUBMIT_RESP: 144 | return gnet.None 145 | case cmpp.CMPP_DELIVER: 146 | return handleDelivery(s, c, header) 147 | case cmpp.CMPP_DELIVER_RESP: 148 | return handleDeliveryResp(s, c, header) 149 | case cmpp.CMPP_ACTIVE_TEST: 150 | return handActive(s, c, header) 151 | case cmpp.CMPP_ACTIVE_TEST_RESP: 152 | return handActiveResp(c, header) 153 | case cmpp.CMPP_TERMINATE: 154 | return handleTerminate(s, c, header) 155 | case cmpp.CMPP_TERMINATE_RESP: 156 | return handleTerminateResp(s, c, header) 157 | default: 158 | // 不合法包,关闭连接 159 | return gnet.Close 160 | } 161 | } 162 | 163 | func (s *Server) OnTick() (delay time.Duration, action gnet.Action) { 164 | log.Infof("[%-9s] %d active connections.", "OnTick", s.activeCons()) 165 | s.conMap.Range(func(key, value interface{}) bool { 166 | addr := key.(string) 167 | con, ok := value.(gnet.Conn) 168 | if ok { 169 | _ = s.pool.Submit(func() { 170 | at := cmpp.NewActiveTest() 171 | err := con.AsyncWrite(at.Encode(), nil) 172 | if err == nil { 173 | log.Infof("[%-9s] >>> %s to %s", "OnTick", at, addr) 174 | } else { 175 | log.Errorf("[%-9s] >>> CMPP_ACTIVE_TEST to %s, error: %v", "OnTick", addr, err) 176 | } 177 | }) 178 | } 179 | return true 180 | }) 181 | return cmpp.Conf.GetDuration("active-test-duration"), gnet.None 182 | } 183 | 184 | func (s *Server) countConn() int { 185 | counter := 0 186 | s.conMap.Range(func(key, value interface{}) bool { 187 | counter++ 188 | return true 189 | }) 190 | return counter 191 | } 192 | 193 | func (s *Server) activeCons() int { 194 | return s.engine.CountConnections() 195 | } 196 | 197 | func handleConnect(s *Server, c gnet.Conn, header *cmpp.MessageHeader) gnet.Action { 198 | frame := comm.TakeBytes(c, 39-cmpp.HeadLength) 199 | comm.LogHex(logging.DebugLevel, "Connect", frame) 200 | 201 | connect := &cmpp.Connect{} 202 | err := connect.Decode(header, frame) 203 | if err != nil { 204 | log.Errorf("[%-9s] CMPP_CONNECT ERROR: %v", "OnTraffic", err) 205 | return gnet.Close 206 | } 207 | 208 | log.Infof("[%-9s] <<< %s", "OnTraffic", connect) 209 | resp := connect.ToResponse(0).(*cmpp.ConnectResp) 210 | if resp.Status() != 0 { 211 | log.Errorf("[%-9s] CMPP_CONNECT ERROR: Auth Error, status=(%d,%s)", "OnTraffic", resp.Status(), cmpp.ConnectStatusMap[resp.Status()]) 212 | } 213 | 214 | // send cmpp_connect_resp async 215 | _ = s.pool.Submit(func() { 216 | err = c.AsyncWrite(resp.Encode(), func(c gnet.Conn) error { 217 | log.Infof("[%-9s] >>> %s", "OnTraffic", resp) 218 | if resp.Status() == 0 { 219 | s.conMap.Store(c.RemoteAddr().String(), c) 220 | } else { 221 | // 客户端登录失败,关闭连接 222 | _ = c.Close() 223 | } 224 | return nil 225 | }) 226 | if err != nil { 227 | log.Errorf("[%-9s] CMPP_CONNECT_RESP ERROR: %v", "OnTraffic", err) 228 | } 229 | }) 230 | return gnet.None 231 | } 232 | 233 | func handleTerminate(s *Server, c gnet.Conn, header *cmpp.MessageHeader) gnet.Action { 234 | log.Infof("[%-9s] <<< %s", "OnTraffic", header) 235 | resp := cmpp.NewTerminateResp(header.SequenceId) 236 | // send cmpp_connect_resp async 237 | _ = s.pool.Submit(func() { 238 | err := c.AsyncWrite(resp.Encode(), func(c gnet.Conn) error { 239 | log.Infof("[%-9s] >>> %s", "OnTraffic", resp) 240 | s.conMap.Delete(c.RemoteAddr().String()) 241 | _ = c.Close() 242 | return nil 243 | }) 244 | if err != nil { 245 | log.Errorf("[%-9s] CMPP_TERMINATE_RESP ERROR: %v", "OnTraffic", err) 246 | } 247 | }) 248 | return gnet.None 249 | } 250 | 251 | func handleTerminateResp(s *Server, c gnet.Conn, header *cmpp.MessageHeader) gnet.Action { 252 | log.Infof("[%-9s] <<< %s", "OnTraffic", header) 253 | log.Infof("[%-9s] closing connection [%v<-->%v]", "OnTraffic", c.RemoteAddr(), c.LocalAddr()) 254 | s.conMap.Delete(c.RemoteAddr().String()) 255 | _ = c.Flush() 256 | _ = c.Close() 257 | return gnet.Close 258 | } 259 | 260 | // 处理上行消息 261 | func handleDelivery(s *Server, c gnet.Conn, header *cmpp.MessageHeader) gnet.Action { 262 | // check connect 263 | _, ok := s.conMap.Load(c.RemoteAddr().String()) 264 | if !ok { 265 | log.Warnf("[%-9s] unLogin connection: %s, closing...", "OnTraffic", c.RemoteAddr()) 266 | return gnet.Close 267 | } 268 | 269 | frame := comm.TakeBytes(c, int(header.TotalLength-cmpp.HeadLength)) 270 | comm.LogHex(logging.DebugLevel, "Delivery", frame) 271 | dly := &cmpp.Delivery{} 272 | err := dly.Decode(header, frame) 273 | if err != nil { 274 | log.Errorf("[%-9s] CMPP_DELIVERY ERROR: %v", "OnTraffic", err) 275 | return gnet.Close 276 | } 277 | log.Debugf("[%-9s] <<< %s", "OnTraffic", dly) 278 | // handle message async 279 | _ = s.pool.Submit(func() { 280 | // 模拟消息处理耗时 281 | _ = processTime() 282 | 283 | rtCode := uint32(0) 284 | if comm.DiceCheck(cmpp.Conf.GetFloat64("success-rate")) { 285 | // 失败消息的返回码 286 | rtCode = 9 287 | } 288 | resp := dly.ToResponse(rtCode).(*cmpp.DeliveryResp) 289 | // 发送响应 290 | err := c.AsyncWrite(resp.Encode(), func(c gnet.Conn) error { 291 | log.Debugf("[%-9s] >>> %s", "OnTraffic", resp) 292 | return nil 293 | }) 294 | if err != nil { 295 | log.Errorf("[%-9s] CMPP_DELIVERY_RESP ERROR: %v", "OnTraffic", err) 296 | } 297 | }) 298 | return gnet.None 299 | } 300 | 301 | // 处理上行消息Resp 302 | func handleDeliveryResp(s *Server, c gnet.Conn, header *cmpp.MessageHeader) gnet.Action { 303 | // check connect 304 | _, ok := s.conMap.Load(c.RemoteAddr().String()) 305 | if !ok { 306 | log.Warnf("[%-9s] unLogin connection: %s, closing...", "OnTraffic", c.RemoteAddr()) 307 | return gnet.Close 308 | } 309 | frame := comm.TakeBytes(c, int(header.TotalLength-cmpp.HeadLength)) 310 | comm.LogHex(logging.DebugLevel, "Deliver", frame) 311 | 312 | resp := &cmpp.DeliveryResp{} 313 | err := resp.Decode(header, frame) 314 | if err != nil { 315 | log.Errorf("[%-9s] DELIVER_RESP ERROR: %v", "OnTraffic", err) 316 | return gnet.Close 317 | } 318 | log.Debugf("[%-9s] <<< %s", "OnTraffic", resp) 319 | 320 | return gnet.None 321 | } 322 | 323 | func handleSubmit(s *Server, c gnet.Conn, header *cmpp.MessageHeader) gnet.Action { 324 | // check connect 325 | _, ok := s.conMap.Load(c.RemoteAddr().String()) 326 | if !ok { 327 | log.Warnf("[%-9s] unLogin connection: %s, closing...", "OnTraffic", c.RemoteAddr()) 328 | return gnet.Close 329 | } 330 | 331 | frame := comm.TakeBytes(c, int(header.TotalLength-cmpp.HeadLength)) 332 | comm.LogHex(logging.DebugLevel, "Submit", frame) 333 | sub := &cmpp.Submit{} 334 | err := sub.Decode(header, frame) 335 | if err != nil { 336 | log.Errorf("[%-9s] CMPP_SUBMIT ERROR: %v", "OnTraffic", err) 337 | return gnet.Close 338 | } 339 | log.Debugf("[%-9s] <<< %s", "OnTraffic", sub) 340 | // handle message async 341 | _ = s.pool.Submit(mtAsyncHandler(s, c, sub)) 342 | return gnet.None 343 | } 344 | 345 | func mtAsyncHandler(s *Server, c gnet.Conn, sub *cmpp.Submit) func() { 346 | return func() { 347 | // 采用通道控制消息收发速度,向通道发送信号 348 | s.window <- struct{}{} 349 | defer func() { 350 | // defer函数消费信号,确保每个消息的信号最终都会被消费 351 | <-s.window 352 | }() 353 | 354 | // 模拟消息处理耗时 355 | processTime := processTime() 356 | 357 | rtCode := uint32(0) 358 | if comm.DiceCheck(cmpp.Conf.GetFloat64("success-rate")) { 359 | // 失败消息的返回码 360 | rtCode = 13 361 | } 362 | resp := sub.ToResponse(rtCode).(*cmpp.SubmitResp) 363 | // 发送响应 364 | err := c.AsyncWrite(resp.Encode(), func(c gnet.Conn) error { 365 | log.Debugf("[%-9s] >>> %s", "OnTraffic", resp) 366 | return nil 367 | }) 368 | if err != nil { 369 | log.Errorf("[%-9s] CMPP_SUBMIT_RESP ERROR: %v", "OnTraffic", err) 370 | } 371 | 372 | // 发送状态报告 373 | if resp.Result() == 0 { 374 | _ = s.pool.Submit(reportAsyncSender(c, sub, resp.MsgId(), processTime)) 375 | } 376 | } 377 | } 378 | 379 | func processTime() time.Duration { 380 | // 模拟消息处理耗时,可配置 381 | processTime := time.Duration(0) 382 | if cmpp.Conf.GetInt("min-submit-resp-ms") > 0 && cmpp.Conf.GetInt("max-submit-resp-ms") > cmpp.Conf.GetInt("min-submit-resp-ms") { 383 | processTime := time.Duration(comm.RandNum( 384 | int32(cmpp.Conf.GetInt("min-submit-resp-ms")), 385 | int32(cmpp.Conf.GetInt("max-submit-resp-ms")), 386 | )) 387 | time.Sleep(processTime * time.Millisecond) 388 | } 389 | return processTime 390 | } 391 | 392 | func reportAsyncSender(c gnet.Conn, sub *cmpp.Submit, msgId uint64, wait time.Duration) func() { 393 | return func() { 394 | if comm.DiceCheck(cmpp.Conf.GetFloat64("success-rate")) { 395 | return 396 | } 397 | dly := sub.ToDeliveryReport(msgId) 398 | // 模拟状态报告发送前的耗时 399 | ms := cmpp.Conf.GetInt("fix-report-resp-ms") 400 | if ms > 0 { 401 | processTime := wait + time.Duration(ms) 402 | time.Sleep(processTime * time.Millisecond) 403 | } 404 | // 发送状态报告 405 | err := c.AsyncWrite(dly.Encode(), func(c gnet.Conn) error { 406 | log.Debugf("[%-9s] >>> %s", "OnTraffic", dly) 407 | return nil 408 | }) 409 | if err != nil { 410 | log.Errorf("[%-9s] CMPP_DELIVERY_REPORT ERROR: %v", "OnTraffic", err) 411 | } 412 | } 413 | } 414 | 415 | func handActive(s *Server, c gnet.Conn, header *cmpp.MessageHeader) (action gnet.Action) { 416 | respHeader := &cmpp.MessageHeader{TotalLength: 13, CommandId: cmpp.CMPP_ACTIVE_TEST_RESP, SequenceId: header.SequenceId} 417 | resp := &cmpp.ActiveTestResp{MessageHeader: respHeader} 418 | // send cmpp_active_resp async 419 | _ = s.pool.Submit(func() { 420 | err := c.AsyncWrite(resp.Encode(), func(c gnet.Conn) error { 421 | log.Infof("[%-9s] >>> %s", "OnTraffic", resp) 422 | return nil 423 | }) 424 | if err != nil { 425 | log.Errorf("[%-9s] CMPP_ACTIVE_TEST_RESP ERROR: %v", "OnTraffic", err) 426 | } 427 | }) 428 | return gnet.None 429 | } 430 | 431 | func handActiveResp(c gnet.Conn, header *cmpp.MessageHeader) (action gnet.Action) { 432 | if c.InboundBuffered() >= 1 { 433 | _, _ = c.Discard(1) 434 | } 435 | log.Infof("[%-9s] <<< %s from %s", "OnTraffic", &cmpp.ActiveTestResp{MessageHeader: header}, c.RemoteAddr()) 436 | return gnet.None 437 | } 438 | 439 | func getHeader(c gnet.Conn) *cmpp.MessageHeader { 440 | frame := comm.TakeBytes(c, cmpp.HeadLength) 441 | if frame == nil { 442 | return nil 443 | } 444 | comm.LogHex(logging.DebugLevel, "Header", frame) 445 | 446 | header := cmpp.MessageHeader{} 447 | err := header.Decode(frame) 448 | if err != nil { 449 | log.Errorf("[%-9s] decode error: %v", "OnTraffic", err) 450 | return nil 451 | } 452 | return &header 453 | } 454 | 455 | func checkReceiveWindow(s *Server, c gnet.Conn, header *cmpp.MessageHeader) gnet.Action { 456 | if len(s.window) == windowSize && header.CommandId == cmpp.CMPP_SUBMIT { 457 | log.Warnf("[%-9s] FLOW CONTROL:receive window threshold reached.", "OnTraffic") 458 | l := int(header.TotalLength - cmpp.HeadLength) 459 | discard, err := c.Discard(l) 460 | if err != nil || discard != l { 461 | return gnet.Close 462 | } 463 | sub := &cmpp.Submit{} 464 | sub.MessageHeader = header 465 | resp := sub.ToResponse(8).(*cmpp.SubmitResp) 466 | // 发送响应 467 | err = c.AsyncWrite(resp.Encode(), func(c gnet.Conn) error { 468 | log.Debugf("[%-9s] >>> %s", "OnTraffic", resp) 469 | return nil 470 | }) 471 | if err != nil { 472 | log.Errorf("[%-9s] CMPP_SUBMIT_RESP ERROR: %v", "OnTraffic", err) 473 | return gnet.Close 474 | } 475 | header.CommandId = 0 476 | } 477 | return gnet.None 478 | } 479 | --------------------------------------------------------------------------------