feat: incr sync version.
This commit is contained in:
61
go/chao-sdk-core/internal/interaction/compressor.go
Normal file
61
go/chao-sdk-core/internal/interaction/compressor.go
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright © 2023 OpenIM SDK. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package interaction
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Compressor interface {
|
||||
Compress(rawData []byte) ([]byte, error)
|
||||
DeCompress(compressedData []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
type GzipCompressor struct {
|
||||
compressProtocol string
|
||||
}
|
||||
|
||||
func NewGzipCompressor() *GzipCompressor {
|
||||
return &GzipCompressor{compressProtocol: "gzip"}
|
||||
}
|
||||
|
||||
func (g *GzipCompressor) Compress(rawData []byte) ([]byte, error) {
|
||||
gzipBuffer := bytes.Buffer{}
|
||||
gz := gzip.NewWriter(&gzipBuffer)
|
||||
if _, err := gz.Write(rawData); err != nil {
|
||||
return nil, utils.Wrap(err, "")
|
||||
}
|
||||
if err := gz.Close(); err != nil {
|
||||
return nil, utils.Wrap(err, "")
|
||||
}
|
||||
return gzipBuffer.Bytes(), nil
|
||||
}
|
||||
|
||||
func (g *GzipCompressor) DeCompress(compressedData []byte) ([]byte, error) {
|
||||
buff := bytes.NewBuffer(compressedData)
|
||||
reader, err := gzip.NewReader(buff)
|
||||
if err != nil {
|
||||
return nil, utils.Wrap(err, "NewReader failed")
|
||||
}
|
||||
compressedData, err = io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, utils.Wrap(err, "ReadAll failed")
|
||||
}
|
||||
_ = reader.Close()
|
||||
return compressedData, nil
|
||||
}
|
||||
39
go/chao-sdk-core/internal/interaction/constant.go
Normal file
39
go/chao-sdk-core/internal/interaction/constant.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright © 2023 OpenIM SDK. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package interaction
|
||||
|
||||
const (
|
||||
WebSocket = iota
|
||||
Tcp
|
||||
)
|
||||
|
||||
const (
|
||||
// MessageText is for UTF-8 encoded text messages like JSON.
|
||||
MessageText = iota + 1
|
||||
// MessageBinary is for binary messages like protobufs.
|
||||
MessageBinary
|
||||
// CloseMessage denotes a close control message. The optional message
|
||||
// payload contains a numeric code and text. Use the FormatCloseMessage
|
||||
// function to format a close message payload.
|
||||
CloseMessage = 8
|
||||
|
||||
// PingMessage denotes a ping control message. The optional message payload
|
||||
// is UTF-8 encoded text.
|
||||
PingMessage = 9
|
||||
|
||||
// PongMessage denotes a pong control message. The optional message payload
|
||||
// is UTF-8 encoded text.
|
||||
PongMessage = 10
|
||||
)
|
||||
52
go/chao-sdk-core/internal/interaction/context.go
Normal file
52
go/chao-sdk-core/internal/interaction/context.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright © 2023 OpenIM SDK. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package interaction
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
)
|
||||
|
||||
type ConnContext struct {
|
||||
RemoteAddr string
|
||||
}
|
||||
|
||||
func (c *ConnContext) Deadline() (deadline time.Time, ok bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func (c *ConnContext) Done() <-chan struct{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ConnContext) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ConnContext) Value(key any) any {
|
||||
switch key {
|
||||
case constant.RemoteAddr:
|
||||
return c.RemoteAddr
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func newContext(remoteAddr string) *ConnContext {
|
||||
return &ConnContext{
|
||||
RemoteAddr: remoteAddr,
|
||||
}
|
||||
}
|
||||
51
go/chao-sdk-core/internal/interaction/encoder.go
Normal file
51
go/chao-sdk-core/internal/interaction/encoder.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright © 2023 OpenIM SDK. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package interaction
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils"
|
||||
)
|
||||
|
||||
type Encoder interface {
|
||||
Encode(data interface{}) ([]byte, error)
|
||||
Decode(encodeData []byte, decodeData interface{}) error
|
||||
}
|
||||
|
||||
type GobEncoder struct {
|
||||
}
|
||||
|
||||
func NewGobEncoder() *GobEncoder {
|
||||
return &GobEncoder{}
|
||||
}
|
||||
func (g *GobEncoder) Encode(data interface{}) ([]byte, error) {
|
||||
buff := bytes.Buffer{}
|
||||
enc := gob.NewEncoder(&buff)
|
||||
err := enc.Encode(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buff.Bytes(), nil
|
||||
}
|
||||
func (g *GobEncoder) Decode(encodeData []byte, decodeData interface{}) error {
|
||||
buff := bytes.NewBuffer(encodeData)
|
||||
dec := gob.NewDecoder(buff)
|
||||
err := dec.Decode(decodeData)
|
||||
if err != nil {
|
||||
return utils.Wrap(err, "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
579
go/chao-sdk-core/internal/interaction/long_conn_mgr.go
Normal file
579
go/chao-sdk-core/internal/interaction/long_conn_mgr.go
Normal file
@@ -0,0 +1,579 @@
|
||||
// Copyright © 2023 OpenIM SDK. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package interaction
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/open_im_sdk_callback"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/ccontext"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/common"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/sdkerrs"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/sdk_struct"
|
||||
"io"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/openimsdk/protocol/sdkws"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// Time allowed to write a message to the peer.
|
||||
writeWait = 10 * time.Second
|
||||
|
||||
// Time allowed to read the next pong message from the peer.
|
||||
pongWait = 30 * time.Second
|
||||
|
||||
// Send pings to peer with this period. Must be less than pongWait.
|
||||
pingPeriod = (pongWait * 9) / 10
|
||||
|
||||
// Maximum message size allowed from peer.
|
||||
maxMessageSize = 1024 * 1024
|
||||
|
||||
//Maximum number of reconnection attempts
|
||||
maxReconnectAttempts = 300
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultNotConnect = iota
|
||||
Closed = iota + 1
|
||||
Connecting
|
||||
Connected
|
||||
)
|
||||
|
||||
var (
|
||||
newline = []byte{'\n'}
|
||||
space = []byte{' '}
|
||||
)
|
||||
|
||||
var (
|
||||
ErrChanClosed = errors.New("send channel closed")
|
||||
ErrConnClosed = errors.New("conn has closed")
|
||||
ErrNotSupportMessageProtocol = errors.New("not support message protocol")
|
||||
ErrClientClosed = errors.New("client actively close the connection")
|
||||
ErrPanic = errors.New("panic error")
|
||||
)
|
||||
|
||||
type LongConnMgr struct {
|
||||
//conn status mutex
|
||||
w sync.Mutex
|
||||
connStatus int
|
||||
// The long connection,can be set tcp or websocket.
|
||||
conn LongConn
|
||||
listener open_im_sdk_callback.OnConnListener
|
||||
// Buffered channel of outbound messages.
|
||||
send chan Message
|
||||
pushMsgAndMaxSeqCh chan common.Cmd2Value
|
||||
conversationCh chan common.Cmd2Value
|
||||
loginMgrCh chan common.Cmd2Value
|
||||
heartbeatCh chan common.Cmd2Value
|
||||
closedErr error
|
||||
ctx context.Context
|
||||
IsCompression bool
|
||||
Syncer *WsRespAsyn
|
||||
encoder Encoder
|
||||
compressor Compressor
|
||||
reconnectStrategy ReconnectStrategy
|
||||
|
||||
mutex sync.Mutex
|
||||
IsBackground bool
|
||||
// write conn lock
|
||||
connWrite *sync.Mutex
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Message GeneralWsReq
|
||||
Resp chan *GeneralWsResp
|
||||
}
|
||||
|
||||
func NewLongConnMgr(ctx context.Context, listener open_im_sdk_callback.OnConnListener, heartbeatCmdCh, pushMsgAndMaxSeqCh, loginMgrCh chan common.Cmd2Value) *LongConnMgr {
|
||||
l := &LongConnMgr{listener: listener, pushMsgAndMaxSeqCh: pushMsgAndMaxSeqCh,
|
||||
loginMgrCh: loginMgrCh, IsCompression: true,
|
||||
Syncer: NewWsRespAsyn(), encoder: NewGobEncoder(), compressor: NewGzipCompressor(),
|
||||
reconnectStrategy: NewExponentialRetry()}
|
||||
l.send = make(chan Message, 10)
|
||||
l.conn = NewWebSocket(WebSocket)
|
||||
l.connWrite = new(sync.Mutex)
|
||||
l.ctx = ctx
|
||||
l.heartbeatCh = heartbeatCmdCh
|
||||
return l
|
||||
}
|
||||
func (c *LongConnMgr) Run(ctx context.Context) {
|
||||
//fmt.Println(mcontext.GetOperationID(ctx), "login run", string(debug.Stack()))
|
||||
go c.readPump(ctx)
|
||||
go c.writePump(ctx)
|
||||
go c.heartbeat(ctx)
|
||||
}
|
||||
|
||||
func (c *LongConnMgr) SendReqWaitResp(ctx context.Context, m proto.Message, reqIdentifier int, resp proto.Message) error {
|
||||
data, err := proto.Marshal(m)
|
||||
if err != nil {
|
||||
return sdkerrs.ErrArgs
|
||||
}
|
||||
msg := Message{
|
||||
Message: GeneralWsReq{
|
||||
ReqIdentifier: reqIdentifier,
|
||||
SendID: ccontext.Info(ctx).UserID(),
|
||||
OperationID: ccontext.Info(ctx).OperationID(),
|
||||
Data: data,
|
||||
},
|
||||
Resp: make(chan *GeneralWsResp, 1),
|
||||
}
|
||||
c.send <- msg
|
||||
log.ZDebug(ctx, "send message to send channel success", "msg", m, "reqIdentifier", reqIdentifier)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return sdkerrs.ErrCtxDeadline
|
||||
case v, ok := <-msg.Resp:
|
||||
if !ok {
|
||||
return errors.New("response channel closed")
|
||||
}
|
||||
if v.ErrCode != 0 {
|
||||
return errs.NewCodeError(v.ErrCode, v.ErrMsg)
|
||||
}
|
||||
if err := proto.Unmarshal(v.Data, resp); err != nil {
|
||||
return sdkerrs.ErrArgs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// readPump pumps messages from the websocket connection to the hub.
|
||||
//
|
||||
// The application runs readPump in a per-connection goroutine. The application
|
||||
// ensures that there is at most one reader on a connection by executing all
|
||||
// reads from this goroutine.
|
||||
|
||||
func (c *LongConnMgr) readPump(ctx context.Context) {
|
||||
log.ZDebug(ctx, "readPump start", "goroutine ID:", getGoroutineID())
|
||||
defer func() {
|
||||
_ = c.close()
|
||||
log.ZWarn(c.ctx, "readPump closed", c.closedErr)
|
||||
}()
|
||||
connNum := 0
|
||||
//c.conn.SetPongHandler(function(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
|
||||
for {
|
||||
ctx = ccontext.WithOperationID(ctx, utils.OperationIDGenerator())
|
||||
needRecon, err := c.reConn(ctx, &connNum)
|
||||
if !needRecon {
|
||||
c.closedErr = err
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
log.ZWarn(c.ctx, "reConn", err)
|
||||
time.Sleep(c.reconnectStrategy.GetSleepInterval())
|
||||
continue
|
||||
}
|
||||
c.conn.SetReadLimit(maxMessageSize)
|
||||
_ = c.conn.SetReadDeadline(pongWait)
|
||||
messageType, message, err := c.conn.ReadMessage()
|
||||
if err != nil {
|
||||
//if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
|
||||
// log.Printf("error: %v", err)
|
||||
//}
|
||||
//break
|
||||
//c.closedErr = err
|
||||
log.ZError(c.ctx, "readMessage err", err, "goroutine ID:", getGoroutineID())
|
||||
_ = c.close()
|
||||
continue
|
||||
}
|
||||
switch messageType {
|
||||
case MessageBinary:
|
||||
err := c.handleMessage(message)
|
||||
if err != nil {
|
||||
c.closedErr = err
|
||||
return
|
||||
}
|
||||
case MessageText:
|
||||
c.closedErr = ErrNotSupportMessageProtocol
|
||||
return
|
||||
//case PingMessage:
|
||||
// err := c.writePongMsg()
|
||||
// log.ZError(c.ctx, "writePongMsg", err)
|
||||
case CloseMessage:
|
||||
c.closedErr = ErrClientClosed
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// writePump pumps messages from the hub to the websocket connection.
|
||||
//
|
||||
// A goroutine running writePump is started for each connection. The
|
||||
// application ensures that there is at most one writer to a connection by
|
||||
// executing all writes from this goroutine.
|
||||
func (c *LongConnMgr) writePump(ctx context.Context) {
|
||||
log.ZDebug(ctx, "writePump start", "goroutine ID:", getGoroutineID())
|
||||
|
||||
defer func() {
|
||||
c.close()
|
||||
close(c.send)
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
c.closedErr = ctx.Err()
|
||||
return
|
||||
case message, ok := <-c.send:
|
||||
if !ok {
|
||||
// The hub closed the channel.
|
||||
_ = c.conn.SetWriteDeadline(writeWait)
|
||||
err := c.conn.WriteMessage(websocket.CloseMessage, []byte{})
|
||||
if err != nil {
|
||||
log.ZError(c.ctx, "send close message error", err)
|
||||
}
|
||||
c.closedErr = ErrChanClosed
|
||||
return
|
||||
}
|
||||
log.ZDebug(c.ctx, "writePump recv message", "reqIdentifier", message.Message.ReqIdentifier,
|
||||
"operationID", message.Message.OperationID, "sendID", message.Message.SendID)
|
||||
resp, err := c.sendAndWaitResp(&message.Message)
|
||||
if err != nil {
|
||||
resp = &GeneralWsResp{
|
||||
ReqIdentifier: message.Message.ReqIdentifier,
|
||||
OperationID: message.Message.OperationID,
|
||||
Data: nil,
|
||||
}
|
||||
if code, ok := errs.Unwrap(err).(errs.CodeError); ok {
|
||||
resp.ErrCode = code.Code()
|
||||
resp.ErrMsg = code.Msg()
|
||||
} else {
|
||||
log.ZError(c.ctx, "writeBinaryMsgAndRetry failed", err, "wsReq", message.Message)
|
||||
}
|
||||
|
||||
}
|
||||
nErr := c.Syncer.notifyCh(message.Resp, resp, 1)
|
||||
if nErr != nil {
|
||||
log.ZError(c.ctx, "TriggerCmdNewMsgCome failed", nErr, "wsResp", resp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *LongConnMgr) heartbeat(ctx context.Context) {
|
||||
log.ZDebug(ctx, "heartbeat start", "goroutine ID:", getGoroutineID())
|
||||
ticker := time.NewTicker(pingPeriod)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
log.ZWarn(c.ctx, "heartbeat closed", nil, "heartbeat", "heartbeat done sdk logout.....")
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.ZInfo(ctx, "heartbeat done sdk logout.....")
|
||||
return
|
||||
case <-c.heartbeatCh:
|
||||
c.sendPingToServer(ctx)
|
||||
case <-ticker.C:
|
||||
c.sendPingToServer(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
func getGoroutineID() int64 {
|
||||
buf := make([]byte, 64)
|
||||
buf = buf[:runtime.Stack(buf, false)]
|
||||
idField := strings.Fields(strings.TrimPrefix(string(buf), "goroutine "))[0]
|
||||
id, err := strconv.ParseInt(idField, 10, 64)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("cannot get goroutine id: %v", err))
|
||||
}
|
||||
return id
|
||||
}
|
||||
func (c *LongConnMgr) sendPingToServer(ctx context.Context) {
|
||||
if c.conn == nil {
|
||||
return
|
||||
}
|
||||
var m sdkws.GetMaxSeqReq
|
||||
m.UserID = ccontext.Info(ctx).UserID()
|
||||
opID := utils.OperationIDGenerator()
|
||||
sCtx := ccontext.WithOperationID(c.ctx, opID)
|
||||
log.ZInfo(sCtx, "ping and getMaxSeq start", "goroutine ID:", getGoroutineID())
|
||||
data, err := proto.Marshal(&m)
|
||||
if err != nil {
|
||||
log.ZError(sCtx, "proto.Marshal", err)
|
||||
return
|
||||
}
|
||||
req := &GeneralWsReq{
|
||||
ReqIdentifier: constant.GetNewestSeq,
|
||||
SendID: m.UserID,
|
||||
OperationID: opID,
|
||||
Data: data,
|
||||
}
|
||||
resp, err := c.sendAndWaitResp(req)
|
||||
if err != nil {
|
||||
log.ZError(sCtx, "sendAndWaitResp", err)
|
||||
_ = c.close()
|
||||
time.Sleep(time.Second * 1)
|
||||
return
|
||||
} else {
|
||||
if resp.ErrCode != 0 {
|
||||
log.ZError(sCtx, "getMaxSeq failed", nil, "errCode:", resp.ErrCode, "errMsg:", resp.ErrMsg)
|
||||
}
|
||||
var wsSeqResp sdkws.GetMaxSeqResp
|
||||
err = proto.Unmarshal(resp.Data, &wsSeqResp)
|
||||
if err != nil {
|
||||
log.ZError(sCtx, "proto.Unmarshal", err)
|
||||
}
|
||||
var cmd sdk_struct.CmdMaxSeqToMsgSync
|
||||
cmd.ConversationMaxSeqOnSvr = wsSeqResp.MaxSeqs
|
||||
|
||||
err := common.TriggerCmdMaxSeq(sCtx, &cmd, c.pushMsgAndMaxSeqCh)
|
||||
if err != nil {
|
||||
log.ZError(sCtx, "TriggerCmdMaxSeq failed", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
func (c *LongConnMgr) sendAndWaitResp(msg *GeneralWsReq) (*GeneralWsResp, error) {
|
||||
tempChan, err := c.writeBinaryMsgAndRetry(msg)
|
||||
defer c.Syncer.DelCh(msg.MsgIncr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
select {
|
||||
case resp := <-tempChan:
|
||||
return resp, nil
|
||||
case <-time.After(time.Second * 5):
|
||||
return nil, sdkerrs.ErrNetworkTimeOut
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (c *LongConnMgr) writeBinaryMsgAndRetry(msg *GeneralWsReq) (chan *GeneralWsResp, error) {
|
||||
msgIncr, tempChan := c.Syncer.AddCh(msg.SendID)
|
||||
msg.MsgIncr = msgIncr
|
||||
if c.GetConnectionStatus() != Connected && msg.ReqIdentifier == constant.GetNewestSeq {
|
||||
return tempChan, sdkerrs.ErrNetwork.WrapMsg("connection closed,conning...")
|
||||
}
|
||||
for i := 0; i < maxReconnectAttempts; i++ {
|
||||
err := c.writeBinaryMsg(*msg)
|
||||
if err != nil {
|
||||
log.ZError(c.ctx, "send binary message error", err, "message", msg)
|
||||
c.closedErr = err
|
||||
_ = c.close()
|
||||
time.Sleep(time.Second * 1)
|
||||
continue
|
||||
} else {
|
||||
return tempChan, nil
|
||||
}
|
||||
}
|
||||
return nil, sdkerrs.ErrNetwork.WrapMsg("send binary message error")
|
||||
}
|
||||
|
||||
func (c *LongConnMgr) writeBinaryMsg(req GeneralWsReq) error {
|
||||
c.connWrite.Lock()
|
||||
defer c.connWrite.Unlock()
|
||||
encodeBuf, err := c.encoder.Encode(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.GetConnectionStatus() != Connected {
|
||||
return sdkerrs.ErrNetwork.WrapMsg("connection closed,re conning...")
|
||||
}
|
||||
_ = c.conn.SetWriteDeadline(writeWait)
|
||||
if c.IsCompression {
|
||||
resultBuf, compressErr := c.compressor.Compress(encodeBuf)
|
||||
if compressErr != nil {
|
||||
return compressErr
|
||||
}
|
||||
return c.conn.WriteMessage(MessageBinary, resultBuf)
|
||||
} else {
|
||||
return c.conn.WriteMessage(MessageBinary, encodeBuf)
|
||||
}
|
||||
}
|
||||
func (c *LongConnMgr) close() error {
|
||||
c.w.Lock()
|
||||
defer c.w.Unlock()
|
||||
if c.connStatus == Closed || c.connStatus == Connecting || c.connStatus == DefaultNotConnect {
|
||||
return nil
|
||||
}
|
||||
c.connStatus = Closed
|
||||
log.ZWarn(c.ctx, "conn closed", c.closedErr)
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
func (c *LongConnMgr) handleMessage(message []byte) error {
|
||||
if c.IsCompression {
|
||||
var decompressErr error
|
||||
message, decompressErr = c.compressor.DeCompress(message)
|
||||
if decompressErr != nil {
|
||||
log.ZError(c.ctx, "DeCompress failed", decompressErr, message)
|
||||
return sdkerrs.ErrMsgDeCompression
|
||||
}
|
||||
}
|
||||
var wsResp GeneralWsResp
|
||||
err := c.encoder.Decode(message, &wsResp)
|
||||
if err != nil {
|
||||
log.ZError(c.ctx, "decodeBinaryWs err", err, "message", message)
|
||||
return sdkerrs.ErrMsgDecodeBinaryWs
|
||||
}
|
||||
ctx := context.WithValue(c.ctx, "operationID", wsResp.OperationID)
|
||||
log.ZInfo(ctx, "recv msg", "errCode", wsResp.ErrCode, "errMsg", wsResp.ErrMsg,
|
||||
"reqIdentifier", wsResp.ReqIdentifier)
|
||||
switch wsResp.ReqIdentifier {
|
||||
case constant.PushMsg:
|
||||
if err = c.doPushMsg(ctx, wsResp); err != nil {
|
||||
log.ZError(ctx, "doWSPushMsg failed", err, "wsResp", wsResp)
|
||||
}
|
||||
case constant.LogoutMsg:
|
||||
if err := c.Syncer.NotifyResp(ctx, wsResp); err != nil {
|
||||
log.ZError(ctx, "notifyResp failed", err, "wsResp", wsResp)
|
||||
}
|
||||
return sdkerrs.ErrLoginOut
|
||||
case constant.KickOnlineMsg:
|
||||
log.ZDebug(ctx, "client kicked offline")
|
||||
c.listener.OnKickedOffline()
|
||||
_ = common.TriggerCmdLogOut(ctx, c.loginMgrCh)
|
||||
return errors.New("client kicked offline")
|
||||
case constant.GetNewestSeq:
|
||||
fallthrough
|
||||
case constant.PullMsgBySeqList:
|
||||
fallthrough
|
||||
case constant.SendMsg:
|
||||
fallthrough
|
||||
case constant.SendSignalMsg:
|
||||
fallthrough
|
||||
case constant.SetBackgroundStatus:
|
||||
if err := c.Syncer.NotifyResp(ctx, wsResp); err != nil {
|
||||
log.ZError(ctx, "notifyResp failed", err, "reqIdentifier", wsResp.ReqIdentifier, "errCode",
|
||||
wsResp.ErrCode, "errMsg", wsResp.ErrMsg, "msgIncr", wsResp.MsgIncr, "operationID", wsResp.OperationID)
|
||||
}
|
||||
default:
|
||||
// log.Error(wsResp.OperationID, "type failed, ", wsResp.ReqIdentifier)
|
||||
return sdkerrs.ErrMsgBinaryTypeNotSupport
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (c *LongConnMgr) IsConnected() bool {
|
||||
c.w.Lock()
|
||||
defer c.w.Unlock()
|
||||
if c.connStatus == Connected {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
}
|
||||
func (c *LongConnMgr) GetConnectionStatus() int {
|
||||
c.w.Lock()
|
||||
defer c.w.Unlock()
|
||||
return c.connStatus
|
||||
}
|
||||
|
||||
func (c *LongConnMgr) SetConnectionStatus(status int) {
|
||||
c.w.Lock()
|
||||
defer c.w.Unlock()
|
||||
c.connStatus = status
|
||||
}
|
||||
func (c *LongConnMgr) reConn(ctx context.Context, num *int) (needRecon bool, err error) {
|
||||
if c.IsConnected() {
|
||||
return true, nil
|
||||
}
|
||||
c.connWrite.Lock()
|
||||
defer c.connWrite.Unlock()
|
||||
log.ZDebug(ctx, "conn start")
|
||||
c.listener.OnConnecting()
|
||||
c.SetConnectionStatus(Connecting)
|
||||
url := fmt.Sprintf("%s?sendID=%s&token=%s&platformID=%d&operationID=%s&isBackground=%t",
|
||||
ccontext.Info(ctx).WsAddr(), ccontext.Info(ctx).UserID(), ccontext.Info(ctx).Token(),
|
||||
ccontext.Info(ctx).PlatformID(), ccontext.Info(ctx).OperationID(), c.GetBackground())
|
||||
if c.IsCompression {
|
||||
url += fmt.Sprintf("&compression=%s", "gzip")
|
||||
}
|
||||
resp, err := c.conn.Dial(url, nil)
|
||||
if err != nil {
|
||||
c.SetConnectionStatus(Closed)
|
||||
if resp != nil {
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
log.ZInfo(ctx, "reConn resp", "body", string(body))
|
||||
var apiResp struct {
|
||||
ErrCode int `json:"errCode"`
|
||||
ErrMsg string `json:"errMsg"`
|
||||
ErrDlt string `json:"errDlt"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &apiResp); err != nil {
|
||||
return true, err
|
||||
}
|
||||
err = errs.NewCodeError(apiResp.ErrCode, apiResp.ErrMsg).WithDetail(apiResp.ErrDlt).Wrap()
|
||||
ccontext.GetApiErrCodeCallback(ctx).OnError(ctx, err)
|
||||
switch apiResp.ErrCode {
|
||||
case
|
||||
errs.TokenExpiredError,
|
||||
errs.TokenMalformedError,
|
||||
errs.TokenNotValidYetError,
|
||||
errs.TokenUnknownError:
|
||||
return false, err
|
||||
default:
|
||||
return true, err
|
||||
}
|
||||
}
|
||||
c.listener.OnConnectFailed(sdkerrs.NetworkError, err.Error())
|
||||
return true, err
|
||||
}
|
||||
c.listener.OnConnectSuccess()
|
||||
c.ctx = newContext(c.conn.LocalAddr())
|
||||
c.ctx = context.WithValue(ctx, "ConnContext", c.ctx)
|
||||
c.SetConnectionStatus(Connected)
|
||||
*num++
|
||||
log.ZInfo(c.ctx, "long conn establish success", "localAddr", c.conn.LocalAddr(), "connNum", *num)
|
||||
c.reconnectStrategy.Reset()
|
||||
_ = common.TriggerCmdConnected(ctx, c.pushMsgAndMaxSeqCh)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (c *LongConnMgr) doPushMsg(ctx context.Context, wsResp GeneralWsResp) error {
|
||||
var msg sdkws.PushMessages
|
||||
err := proto.Unmarshal(wsResp.Data, &msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return common.TriggerCmdPushMsg(ctx, &msg, c.pushMsgAndMaxSeqCh)
|
||||
}
|
||||
func (c *LongConnMgr) Close(ctx context.Context) {
|
||||
if c.GetConnectionStatus() == Connected {
|
||||
log.ZInfo(ctx, "network change conn close")
|
||||
c.closedErr = errors.New("closed by client network change")
|
||||
_ = c.close()
|
||||
} else {
|
||||
log.ZInfo(ctx, "conn already closed")
|
||||
}
|
||||
|
||||
}
|
||||
func (c *LongConnMgr) GetBackground() bool {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
return c.IsBackground
|
||||
}
|
||||
func (c *LongConnMgr) SetBackground(isBackground bool) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
c.IsBackground = isBackground
|
||||
}
|
||||
44
go/chao-sdk-core/internal/interaction/long_connection.go
Normal file
44
go/chao-sdk-core/internal/interaction/long_connection.go
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright © 2023 OpenIM SDK. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package interaction
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PongHandler func(string) error
|
||||
type LongConn interface {
|
||||
//Close this connection
|
||||
Close() error
|
||||
// WriteMessage Write message to connection,messageType means data type,can be set binary(2) and text(1).
|
||||
WriteMessage(messageType int, message []byte) error
|
||||
// ReadMessage Read message from connection.
|
||||
ReadMessage() (int, []byte, error)
|
||||
// SetReadDeadline sets the read deadline on the underlying network connection,
|
||||
//after a read has timed out, will return an error.
|
||||
SetReadDeadline(timeout time.Duration) error
|
||||
// SetWriteDeadline sets to write deadline when send message,when read has timed out,will return error.
|
||||
SetWriteDeadline(timeout time.Duration) error
|
||||
// Dial Try to dial a connection,url must set auth args,header can control compress data
|
||||
Dial(urlStr string, requestHeader http.Header) (*http.Response, error)
|
||||
// IsNil Whether the connection of the current long connection is nil
|
||||
IsNil() bool
|
||||
// SetReadLimit sets the maximum size for a message read from the peer.bytes
|
||||
SetReadLimit(limit int64)
|
||||
SetPongHandler(handler PongHandler)
|
||||
// LocalAddr returns the local network address.
|
||||
LocalAddr() string
|
||||
}
|
||||
387
go/chao-sdk-core/internal/interaction/msg_sync.go
Normal file
387
go/chao-sdk-core/internal/interaction/msg_sync.go
Normal file
@@ -0,0 +1,387 @@
|
||||
// Copyright © 2023 OpenIM SDK. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package interaction
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/common"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/db_interface"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/sdk_struct"
|
||||
|
||||
"github.com/openimsdk/protocol/sdkws"
|
||||
"github.com/openimsdk/tools/log"
|
||||
)
|
||||
|
||||
const (
|
||||
connectPullNums = 1
|
||||
defaultPullNums = 10
|
||||
SplitPullMsgNum = 100
|
||||
)
|
||||
|
||||
// The callback synchronization starts. The reconnection ends
|
||||
type MsgSyncer struct {
|
||||
loginUserID string // login user ID
|
||||
longConnMgr *LongConnMgr // long connection manager
|
||||
PushMsgAndMaxSeqCh chan common.Cmd2Value // channel for receiving push messages and the maximum SEQ number
|
||||
conversationCh chan common.Cmd2Value // storage and session triggering
|
||||
syncedMaxSeqs map[string]int64 // map of the maximum synced SEQ numbers for all group IDs
|
||||
db db_interface.DataBase // data store
|
||||
syncTimes int // times of sync
|
||||
ctx context.Context // context
|
||||
reinstalled bool //true if the app was uninstalled and reinstalled
|
||||
|
||||
}
|
||||
|
||||
// NewMsgSyncer creates a new instance of the message synchronizer.
|
||||
func NewMsgSyncer(ctx context.Context, conversationCh, PushMsgAndMaxSeqCh chan common.Cmd2Value,
|
||||
loginUserID string, longConnMgr *LongConnMgr, db db_interface.DataBase, syncTimes int) (*MsgSyncer, error) {
|
||||
m := &MsgSyncer{
|
||||
loginUserID: loginUserID,
|
||||
longConnMgr: longConnMgr,
|
||||
PushMsgAndMaxSeqCh: PushMsgAndMaxSeqCh,
|
||||
conversationCh: conversationCh,
|
||||
ctx: ctx,
|
||||
syncedMaxSeqs: make(map[string]int64),
|
||||
db: db,
|
||||
syncTimes: syncTimes,
|
||||
}
|
||||
if err := m.loadSeq(ctx); err != nil {
|
||||
log.ZError(ctx, "loadSeq err", err)
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// seq The db reads the data to the memory,set syncedMaxSeqs
|
||||
func (m *MsgSyncer) loadSeq(ctx context.Context) error {
|
||||
conversationIDList, err := m.db.GetAllConversationIDList(ctx)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "get conversation id list failed", err)
|
||||
return err
|
||||
}
|
||||
if len(conversationIDList) == 0 {
|
||||
m.reinstalled = true
|
||||
}
|
||||
//TODO With a large number of sessions, this could potentially cause blocking and needs optimization.
|
||||
for _, v := range conversationIDList {
|
||||
maxSyncedSeq, err := m.db.GetConversationNormalMsgSeq(ctx, v)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "get group normal seq failed", err, "conversationID", v)
|
||||
} else {
|
||||
m.syncedMaxSeqs[v] = maxSyncedSeq
|
||||
}
|
||||
}
|
||||
notificationSeqs, err := m.db.GetNotificationAllSeqs(ctx)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "get notification seq failed", err)
|
||||
return err
|
||||
}
|
||||
for _, notificationSeq := range notificationSeqs {
|
||||
m.syncedMaxSeqs[notificationSeq.ConversationID] = notificationSeq.Seq
|
||||
}
|
||||
log.ZDebug(ctx, "loadSeq", "syncedMaxSeqs", m.syncedMaxSeqs)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DoListener Listen to the message pipe of the message synchronizer
|
||||
// and process received and pushed messages
|
||||
func (m *MsgSyncer) DoListener(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
case cmd := <-m.PushMsgAndMaxSeqCh:
|
||||
m.handlePushMsgAndEvent(cmd)
|
||||
case <-ctx.Done():
|
||||
log.ZInfo(m.ctx, "msg syncer done, sdk logout.....")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get seqs need sync interval
|
||||
func (m *MsgSyncer) getSeqsNeedSync(syncedMaxSeq, maxSeq int64) []int64 {
|
||||
var seqs []int64
|
||||
for i := syncedMaxSeq + 1; i <= maxSeq; i++ {
|
||||
seqs = append(seqs, i)
|
||||
}
|
||||
return seqs
|
||||
}
|
||||
|
||||
// recv msg from
|
||||
func (m *MsgSyncer) handlePushMsgAndEvent(cmd common.Cmd2Value) {
|
||||
switch cmd.Cmd {
|
||||
case constant.CmdConnSuccesss:
|
||||
log.ZInfo(cmd.Ctx, "recv long conn mgr connected", "cmd", cmd.Cmd, "value", cmd.Value)
|
||||
m.doConnected(cmd.Ctx)
|
||||
case constant.CmdMaxSeq:
|
||||
log.ZInfo(cmd.Ctx, "recv max seqs from long conn mgr, start sync msgs", "cmd", cmd.Cmd, "value", cmd.Value)
|
||||
m.compareSeqsAndBatchSync(cmd.Ctx, cmd.Value.(*sdk_struct.CmdMaxSeqToMsgSync).ConversationMaxSeqOnSvr, defaultPullNums)
|
||||
case constant.CmdPushMsg:
|
||||
m.doPushMsg(cmd.Ctx, cmd.Value.(*sdkws.PushMessages))
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MsgSyncer) compareSeqsAndBatchSync(ctx context.Context, maxSeqToSync map[string]int64, pullNums int64) {
|
||||
needSyncSeqMap := make(map[string][2]int64)
|
||||
//when app reinstalled do not pull notifications messages.
|
||||
if m.reinstalled {
|
||||
notificationsSeqMap := make(map[string]int64)
|
||||
messagesSeqMap := make(map[string]int64)
|
||||
for conversationID, seq := range maxSeqToSync {
|
||||
if IsNotification(conversationID) {
|
||||
notificationsSeqMap[conversationID] = seq
|
||||
} else {
|
||||
messagesSeqMap[conversationID] = seq
|
||||
}
|
||||
}
|
||||
for conversationID, seq := range notificationsSeqMap {
|
||||
err := m.db.SetNotificationSeq(ctx, conversationID, seq)
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "SetNotificationSeq err", err, "conversationID", conversationID, "seq", seq)
|
||||
continue
|
||||
} else {
|
||||
m.syncedMaxSeqs[conversationID] = seq
|
||||
}
|
||||
}
|
||||
for conversationID, maxSeq := range messagesSeqMap {
|
||||
if syncedMaxSeq, ok := m.syncedMaxSeqs[conversationID]; ok {
|
||||
if maxSeq > syncedMaxSeq {
|
||||
needSyncSeqMap[conversationID] = [2]int64{syncedMaxSeq + 1, maxSeq}
|
||||
}
|
||||
} else {
|
||||
needSyncSeqMap[conversationID] = [2]int64{0, maxSeq}
|
||||
}
|
||||
}
|
||||
m.reinstalled = false
|
||||
} else {
|
||||
for conversationID, maxSeq := range maxSeqToSync {
|
||||
if syncedMaxSeq, ok := m.syncedMaxSeqs[conversationID]; ok {
|
||||
if maxSeq > syncedMaxSeq {
|
||||
needSyncSeqMap[conversationID] = [2]int64{syncedMaxSeq + 1, maxSeq}
|
||||
}
|
||||
} else {
|
||||
needSyncSeqMap[conversationID] = [2]int64{0, maxSeq}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = m.syncAndTriggerMsgs(m.ctx, needSyncSeqMap, pullNums)
|
||||
}
|
||||
|
||||
func (m *MsgSyncer) doPushMsg(ctx context.Context, push *sdkws.PushMessages) {
|
||||
log.ZDebug(ctx, "push msgs", "push", push, "syncedMaxSeqs", m.syncedMaxSeqs)
|
||||
m.pushTriggerAndSync(ctx, push.Msgs, m.triggerConversation)
|
||||
m.pushTriggerAndSync(ctx, push.NotificationMsgs, m.triggerNotification)
|
||||
}
|
||||
|
||||
func (m *MsgSyncer) pushTriggerAndSync(ctx context.Context, pullMsgs map[string]*sdkws.PullMsgs, triggerFunc func(ctx context.Context, msgs map[string]*sdkws.PullMsgs) error) {
|
||||
if len(pullMsgs) == 0 {
|
||||
return
|
||||
}
|
||||
needSyncSeqMap := make(map[string][2]int64)
|
||||
var lastSeq int64
|
||||
var storageMsgs []*sdkws.MsgData
|
||||
for conversationID, msgs := range pullMsgs {
|
||||
for _, msg := range msgs.Msgs {
|
||||
if msg.Seq == 0 {
|
||||
_ = triggerFunc(ctx, map[string]*sdkws.PullMsgs{conversationID: {Msgs: []*sdkws.MsgData{msg}}})
|
||||
continue
|
||||
}
|
||||
lastSeq = msg.Seq
|
||||
storageMsgs = append(storageMsgs, msg)
|
||||
}
|
||||
if lastSeq == m.syncedMaxSeqs[conversationID]+int64(len(storageMsgs)) && lastSeq != 0 {
|
||||
log.ZDebug(ctx, "trigger msgs", "msgs", storageMsgs)
|
||||
_ = triggerFunc(ctx, map[string]*sdkws.PullMsgs{conversationID: {Msgs: storageMsgs}})
|
||||
m.syncedMaxSeqs[conversationID] = lastSeq
|
||||
} else if lastSeq != 0 && lastSeq > m.syncedMaxSeqs[conversationID] {
|
||||
//must pull message when message type is notification
|
||||
needSyncSeqMap[conversationID] = [2]int64{m.syncedMaxSeqs[conversationID] + 1, lastSeq}
|
||||
}
|
||||
}
|
||||
m.syncAndTriggerMsgs(ctx, needSyncSeqMap, defaultPullNums)
|
||||
}
|
||||
|
||||
// Called after successful reconnection to synchronize the latest message
|
||||
func (m *MsgSyncer) doConnected(ctx context.Context) {
|
||||
common.TriggerCmdNotification(m.ctx, sdk_struct.CmdNewMsgComeToConversation{SyncFlag: constant.MsgSyncBegin}, m.conversationCh)
|
||||
var resp sdkws.GetMaxSeqResp
|
||||
if err := m.longConnMgr.SendReqWaitResp(m.ctx, &sdkws.GetMaxSeqReq{UserID: m.loginUserID}, constant.GetNewestSeq, &resp); err != nil {
|
||||
log.ZError(m.ctx, "get max seq error", err)
|
||||
common.TriggerCmdNotification(m.ctx, sdk_struct.CmdNewMsgComeToConversation{SyncFlag: constant.MsgSyncFailed}, m.conversationCh)
|
||||
return
|
||||
} else {
|
||||
log.ZDebug(m.ctx, "get max seq success", "resp", resp)
|
||||
}
|
||||
m.compareSeqsAndBatchSync(ctx, resp.MaxSeqs, connectPullNums)
|
||||
common.TriggerCmdNotification(m.ctx, sdk_struct.CmdNewMsgComeToConversation{SyncFlag: constant.MsgSyncEnd}, m.conversationCh)
|
||||
}
|
||||
|
||||
func IsNotification(conversationID string) bool {
|
||||
return strings.HasPrefix(conversationID, "n_")
|
||||
}
|
||||
|
||||
// Fragment synchronization message, seq refresh after successful trigger
|
||||
func (m *MsgSyncer) syncAndTriggerMsgs(ctx context.Context, seqMap map[string][2]int64, syncMsgNum int64) error {
|
||||
if len(seqMap) > 0 {
|
||||
log.ZDebug(ctx, "current sync seqMap", "seqMap", seqMap)
|
||||
tempSeqMap := make(map[string][2]int64, 50)
|
||||
msgNum := 0
|
||||
for k, v := range seqMap {
|
||||
oneConversationSyncNum := v[1] - v[0] + 1
|
||||
if (oneConversationSyncNum/SplitPullMsgNum) > 1 && IsNotification(k) {
|
||||
nSeqMap := make(map[string][2]int64, 1)
|
||||
count := int(oneConversationSyncNum / SplitPullMsgNum)
|
||||
startSeq := v[0]
|
||||
var end int64
|
||||
for i := 0; i <= count; i++ {
|
||||
if i == count {
|
||||
nSeqMap[k] = [2]int64{startSeq, v[1]}
|
||||
} else {
|
||||
end = startSeq + int64(SplitPullMsgNum)
|
||||
if end > v[1] {
|
||||
end = v[1]
|
||||
i = count
|
||||
}
|
||||
nSeqMap[k] = [2]int64{startSeq, end}
|
||||
}
|
||||
resp, err := m.pullMsgBySeqRange(ctx, nSeqMap, syncMsgNum)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "syncMsgFromSvr err", err, "nSeqMap", nSeqMap)
|
||||
return err
|
||||
}
|
||||
_ = m.triggerConversation(ctx, resp.Msgs)
|
||||
_ = m.triggerNotification(ctx, resp.NotificationMsgs)
|
||||
for conversationID, seqs := range nSeqMap {
|
||||
m.syncedMaxSeqs[conversationID] = seqs[1]
|
||||
}
|
||||
startSeq = end + 1
|
||||
}
|
||||
continue
|
||||
}
|
||||
tempSeqMap[k] = v
|
||||
if oneConversationSyncNum > 0 {
|
||||
msgNum += int(oneConversationSyncNum)
|
||||
}
|
||||
if msgNum >= SplitPullMsgNum {
|
||||
resp, err := m.pullMsgBySeqRange(ctx, tempSeqMap, syncMsgNum)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "syncMsgFromSvr err", err, "tempSeqMap", tempSeqMap)
|
||||
return err
|
||||
}
|
||||
_ = m.triggerConversation(ctx, resp.Msgs)
|
||||
_ = m.triggerNotification(ctx, resp.NotificationMsgs)
|
||||
for conversationID, seqs := range tempSeqMap {
|
||||
m.syncedMaxSeqs[conversationID] = seqs[1]
|
||||
}
|
||||
tempSeqMap = make(map[string][2]int64, 50)
|
||||
msgNum = 0
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := m.pullMsgBySeqRange(ctx, tempSeqMap, syncMsgNum)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "syncMsgFromSvr err", err, "seqMap", seqMap)
|
||||
return err
|
||||
}
|
||||
_ = m.triggerConversation(ctx, resp.Msgs)
|
||||
_ = m.triggerNotification(ctx, resp.NotificationMsgs)
|
||||
for conversationID, seqs := range seqMap {
|
||||
m.syncedMaxSeqs[conversationID] = seqs[1]
|
||||
}
|
||||
} else {
|
||||
log.ZDebug(ctx, "noting conversation to sync", "syncMsgNum", syncMsgNum)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MsgSyncer) splitSeqs(split int, seqsNeedSync []int64) (splitSeqs [][]int64) {
|
||||
if len(seqsNeedSync) <= split {
|
||||
splitSeqs = append(splitSeqs, seqsNeedSync)
|
||||
return
|
||||
}
|
||||
for i := 0; i < len(seqsNeedSync); i += split {
|
||||
end := i + split
|
||||
if end > len(seqsNeedSync) {
|
||||
end = len(seqsNeedSync)
|
||||
}
|
||||
splitSeqs = append(splitSeqs, seqsNeedSync[i:end])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *MsgSyncer) pullMsgBySeqRange(ctx context.Context, seqMap map[string][2]int64, syncMsgNum int64) (resp *sdkws.PullMessageBySeqsResp, err error) {
|
||||
log.ZDebug(ctx, "pullMsgBySeqRange", "seqMap", seqMap, "syncMsgNum", syncMsgNum)
|
||||
|
||||
req := sdkws.PullMessageBySeqsReq{UserID: m.loginUserID}
|
||||
for conversationID, seqs := range seqMap {
|
||||
req.SeqRanges = append(req.SeqRanges, &sdkws.SeqRange{
|
||||
ConversationID: conversationID,
|
||||
Begin: seqs[0],
|
||||
End: seqs[1],
|
||||
Num: syncMsgNum,
|
||||
})
|
||||
}
|
||||
resp = &sdkws.PullMessageBySeqsResp{}
|
||||
if err := m.longConnMgr.SendReqWaitResp(ctx, &req, constant.PullMsgBySeqList, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// synchronizes messages by SEQs.
|
||||
func (m *MsgSyncer) syncMsgBySeqs(ctx context.Context, conversationID string, seqsNeedSync []int64) (allMsgs []*sdkws.MsgData, err error) {
|
||||
pullMsgReq := sdkws.PullMessageBySeqsReq{}
|
||||
pullMsgReq.UserID = m.loginUserID
|
||||
split := constant.SplitPullMsgNum
|
||||
seqsList := m.splitSeqs(split, seqsNeedSync)
|
||||
for i := 0; i < len(seqsList); {
|
||||
var pullMsgResp sdkws.PullMessageBySeqsResp
|
||||
err := m.longConnMgr.SendReqWaitResp(ctx, &pullMsgReq, constant.PullMsgBySeqList, &pullMsgResp)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "syncMsgFromSvrSplit err", err, "pullMsgReq", pullMsgReq)
|
||||
continue
|
||||
}
|
||||
i++
|
||||
allMsgs = append(allMsgs, pullMsgResp.Msgs[conversationID].Msgs...)
|
||||
}
|
||||
return allMsgs, nil
|
||||
}
|
||||
|
||||
// triggers a conversation with a new message.
|
||||
func (m *MsgSyncer) triggerConversation(ctx context.Context, msgs map[string]*sdkws.PullMsgs) error {
|
||||
if len(msgs) >= 0 {
|
||||
err := common.TriggerCmdNewMsgCome(ctx, sdk_struct.CmdNewMsgComeToConversation{Msgs: msgs}, m.conversationCh)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "triggerCmdNewMsgCome err", err, "msgs", msgs)
|
||||
}
|
||||
log.ZDebug(ctx, "triggerConversation", "msgs", msgs)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MsgSyncer) triggerNotification(ctx context.Context, msgs map[string]*sdkws.PullMsgs) error {
|
||||
if len(msgs) >= 0 {
|
||||
err := common.TriggerCmdNotification(ctx, sdk_struct.CmdNewMsgComeToConversation{Msgs: msgs}, m.conversationCh)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "triggerCmdNewMsgCome err", err, "msgs", msgs)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
32
go/chao-sdk-core/internal/interaction/reconnect.go
Normal file
32
go/chao-sdk-core/internal/interaction/reconnect.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package interaction
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type ReconnectStrategy interface {
|
||||
GetSleepInterval() time.Duration
|
||||
Reset()
|
||||
}
|
||||
|
||||
type ExponentialRetry struct {
|
||||
attempts []int
|
||||
index int
|
||||
}
|
||||
|
||||
func NewExponentialRetry() *ExponentialRetry {
|
||||
return &ExponentialRetry{
|
||||
attempts: []int{1, 2, 4, 8, 16},
|
||||
index: -1,
|
||||
}
|
||||
}
|
||||
|
||||
func (rs *ExponentialRetry) GetSleepInterval() time.Duration {
|
||||
rs.index++
|
||||
interval := rs.index % len(rs.attempts)
|
||||
return time.Second * time.Duration(rs.attempts[interval])
|
||||
}
|
||||
|
||||
func (rs *ExponentialRetry) Reset() {
|
||||
rs.index = -1
|
||||
}
|
||||
87
go/chao-sdk-core/internal/interaction/ws_default.go
Normal file
87
go/chao-sdk-core/internal/interaction/ws_default.go
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright © 2023 OpenIM SDK. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !js
|
||||
|
||||
package interaction
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type Default struct {
|
||||
ConnType int
|
||||
conn *websocket.Conn
|
||||
isSetConf bool
|
||||
}
|
||||
|
||||
func (d *Default) SetReadDeadline(timeout time.Duration) error {
|
||||
return d.conn.SetReadDeadline(time.Now().Add(timeout))
|
||||
}
|
||||
|
||||
func (d *Default) SetWriteDeadline(timeout time.Duration) error {
|
||||
return d.conn.SetWriteDeadline(time.Now().Add(timeout))
|
||||
}
|
||||
|
||||
func (d *Default) SetReadLimit(limit int64) {
|
||||
if !d.isSetConf {
|
||||
d.conn.SetReadLimit(limit)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (d *Default) SetPongHandler(handler PongHandler) {
|
||||
if !d.isSetConf {
|
||||
d.conn.SetPongHandler(handler)
|
||||
d.isSetConf = true
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Default) LocalAddr() string {
|
||||
return d.conn.LocalAddr().String()
|
||||
}
|
||||
|
||||
func NewWebSocket(connType int) *Default {
|
||||
return &Default{ConnType: connType}
|
||||
}
|
||||
func (d *Default) Close() error {
|
||||
return d.conn.Close()
|
||||
}
|
||||
|
||||
func (d *Default) WriteMessage(messageType int, message []byte) error {
|
||||
return d.conn.WriteMessage(messageType, message)
|
||||
}
|
||||
|
||||
func (d *Default) ReadMessage() (int, []byte, error) {
|
||||
return d.conn.ReadMessage()
|
||||
}
|
||||
|
||||
func (d *Default) Dial(urlStr string, requestHeader http.Header) (*http.Response, error) {
|
||||
conn, httpResp, err := websocket.DefaultDialer.Dial(urlStr, requestHeader)
|
||||
if err == nil {
|
||||
d.conn = conn
|
||||
}
|
||||
return httpResp, err
|
||||
|
||||
}
|
||||
|
||||
func (d *Default) IsNil() bool {
|
||||
if d.conn != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
140
go/chao-sdk-core/internal/interaction/ws_js.go
Normal file
140
go/chao-sdk-core/internal/interaction/ws_js.go
Normal file
@@ -0,0 +1,140 @@
|
||||
// Copyright © 2023 OpenIM SDK. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package interaction
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"nhooyr.io/websocket"
|
||||
"time"
|
||||
)
|
||||
|
||||
type JSWebSocket struct {
|
||||
ConnType int
|
||||
conn *websocket.Conn
|
||||
sendConn *websocket.Conn
|
||||
}
|
||||
|
||||
func (w *JSWebSocket) SetReadDeadline(timeout time.Duration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *JSWebSocket) SetWriteDeadline(timeout time.Duration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *JSWebSocket) SetReadLimit(limit int64) {
|
||||
w.conn.SetReadLimit(limit)
|
||||
}
|
||||
|
||||
func (w *JSWebSocket) SetPongHandler(handler PongHandler) {
|
||||
|
||||
}
|
||||
|
||||
func (w *JSWebSocket) LocalAddr() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func NewWebSocket(connType int) *JSWebSocket {
|
||||
return &JSWebSocket{ConnType: connType}
|
||||
}
|
||||
|
||||
func (w *JSWebSocket) Close() error {
|
||||
return w.conn.Close(websocket.StatusGoingAway, "Actively close the conn have old conn")
|
||||
}
|
||||
|
||||
func (w *JSWebSocket) WriteMessage(messageType int, message []byte) error {
|
||||
return w.conn.Write(context.Background(), websocket.MessageType(messageType), message)
|
||||
}
|
||||
|
||||
func (w *JSWebSocket) ReadMessage() (int, []byte, error) {
|
||||
messageType, b, err := w.conn.Read(context.Background())
|
||||
return int(messageType), b, err
|
||||
}
|
||||
|
||||
func (w *JSWebSocket) dial(ctx context.Context, urlStr string) (*websocket.Conn, *http.Response, error) {
|
||||
u, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
query := u.Query()
|
||||
query.Set("isMsgResp", "true")
|
||||
u.RawQuery = query.Encode()
|
||||
conn, httpResp, err := websocket.Dial(ctx, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if httpResp == nil {
|
||||
httpResp = &http.Response{
|
||||
StatusCode: http.StatusSwitchingProtocols,
|
||||
}
|
||||
}
|
||||
_, data, err := conn.Read(ctx)
|
||||
if err != nil {
|
||||
_ = conn.CloseNow()
|
||||
return nil, nil, fmt.Errorf("read response error %w", err)
|
||||
}
|
||||
var apiResp struct {
|
||||
ErrCode int `json:"errCode"`
|
||||
ErrMsg string `json:"errMsg"`
|
||||
ErrDlt string `json:"errDlt"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &apiResp); err != nil {
|
||||
return nil, nil, fmt.Errorf("unmarshal response error %w", err)
|
||||
}
|
||||
if apiResp.ErrCode == 0 {
|
||||
return conn, httpResp, nil
|
||||
}
|
||||
log.ZDebug(ctx, "ws msg read resp", "data", string(data))
|
||||
httpResp.Body = io.NopCloser(bytes.NewReader(data))
|
||||
return conn, httpResp, fmt.Errorf("read response error %d %s %s",
|
||||
apiResp.ErrCode, apiResp.ErrMsg, apiResp.ErrDlt)
|
||||
}
|
||||
|
||||
func (w *JSWebSocket) Dial(urlStr string, _ http.Header) (*http.Response, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
conn, httpResp, err := w.dial(ctx, urlStr)
|
||||
if err == nil {
|
||||
w.conn = conn
|
||||
}
|
||||
return httpResp, err
|
||||
}
|
||||
|
||||
//func (w *JSWebSocket) Dial(urlStr string, _ http.Header) (*http.Response, error) {
|
||||
// ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
// defer cancel()
|
||||
// conn, httpResp, err := websocket.Dial(ctx, urlStr, nil)
|
||||
// if err == nil {
|
||||
// w.conn = conn
|
||||
// }
|
||||
// return httpResp, err
|
||||
//}
|
||||
|
||||
func (w *JSWebSocket) IsNil() bool {
|
||||
if w.conn != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
168
go/chao-sdk-core/internal/interaction/ws_resp_asyn.go
Normal file
168
go/chao-sdk-core/internal/interaction/ws_resp_asyn.go
Normal file
@@ -0,0 +1,168 @@
|
||||
// Copyright © 2023 OpenIM SDK. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package interaction
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/tools/log"
|
||||
)
|
||||
|
||||
type GeneralWsResp struct {
|
||||
ReqIdentifier int `json:"reqIdentifier"`
|
||||
ErrCode int `json:"errCode"`
|
||||
ErrMsg string `json:"errMsg"`
|
||||
MsgIncr string `json:"msgIncr"`
|
||||
OperationID string `json:"operationID"`
|
||||
Data []byte `json:"data"`
|
||||
}
|
||||
|
||||
type GeneralWsReq struct {
|
||||
ReqIdentifier int `json:"reqIdentifier"`
|
||||
Token string `json:"token"`
|
||||
SendID string `json:"sendID"`
|
||||
OperationID string `json:"operationID"`
|
||||
MsgIncr string `json:"msgIncr"`
|
||||
Data []byte `json:"data"`
|
||||
}
|
||||
|
||||
type WsRespAsyn struct {
|
||||
wsNotification map[string]chan *GeneralWsResp
|
||||
wsMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func NewWsRespAsyn() *WsRespAsyn {
|
||||
return &WsRespAsyn{wsNotification: make(map[string]chan *GeneralWsResp, 10)}
|
||||
}
|
||||
|
||||
func GenMsgIncr(userID string) string {
|
||||
return userID + "_" + utils.OperationIDGenerator()
|
||||
}
|
||||
|
||||
func (u *WsRespAsyn) AddCh(userID string) (string, chan *GeneralWsResp) {
|
||||
u.wsMutex.Lock()
|
||||
defer u.wsMutex.Unlock()
|
||||
msgIncr := GenMsgIncr(userID)
|
||||
|
||||
ch := make(chan *GeneralWsResp, 1)
|
||||
_, ok := u.wsNotification[msgIncr]
|
||||
if ok {
|
||||
}
|
||||
u.wsNotification[msgIncr] = ch
|
||||
return msgIncr, ch
|
||||
}
|
||||
|
||||
func (u *WsRespAsyn) AddChByIncr(msgIncr string) chan *GeneralWsResp {
|
||||
u.wsMutex.Lock()
|
||||
defer u.wsMutex.Unlock()
|
||||
ch := make(chan *GeneralWsResp, 1)
|
||||
_, ok := u.wsNotification[msgIncr]
|
||||
if ok {
|
||||
log.ZError(context.Background(), "Repeat failed", nil, msgIncr)
|
||||
}
|
||||
u.wsNotification[msgIncr] = ch
|
||||
return ch
|
||||
}
|
||||
|
||||
func (u *WsRespAsyn) GetCh(msgIncr string) chan *GeneralWsResp {
|
||||
ch, ok := u.wsNotification[msgIncr]
|
||||
if ok {
|
||||
return ch
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *WsRespAsyn) DelCh(msgIncr string) {
|
||||
u.wsMutex.Lock()
|
||||
defer u.wsMutex.Unlock()
|
||||
ch, ok := u.wsNotification[msgIncr]
|
||||
if ok {
|
||||
close(ch)
|
||||
delete(u.wsNotification, msgIncr)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *WsRespAsyn) notifyCh(ch chan *GeneralWsResp, value *GeneralWsResp, timeout int64) error {
|
||||
var flag = 0
|
||||
select {
|
||||
case ch <- value:
|
||||
flag = 1
|
||||
case <-time.After(time.Second * time.Duration(timeout)):
|
||||
flag = 2
|
||||
}
|
||||
if flag == 1 {
|
||||
return nil
|
||||
} else {
|
||||
return errors.New("send cmd timeout")
|
||||
}
|
||||
}
|
||||
|
||||
// write a unit test for this function
|
||||
func (u *WsRespAsyn) NotifyResp(ctx context.Context, wsResp GeneralWsResp) error {
|
||||
u.wsMutex.Lock()
|
||||
defer u.wsMutex.Unlock()
|
||||
|
||||
ch := u.GetCh(wsResp.MsgIncr)
|
||||
if ch == nil {
|
||||
return utils.Wrap(errors.New("no ch"), "GetCh failed "+wsResp.MsgIncr)
|
||||
}
|
||||
for {
|
||||
err := u.notifyCh(ch, &wsResp, 1)
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "TriggerCmdNewMsgCome failed ", err, "ch", ch, "wsResp", wsResp)
|
||||
continue
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
func (u *WsRespAsyn) WaitResp(ctx context.Context, ch chan *GeneralWsResp, timeout int) (*GeneralWsResp, error) {
|
||||
select {
|
||||
case r, ok := <-ch:
|
||||
if !ok { //ch has been closed
|
||||
//log.Debug(operationID, "ws network has been changed ")
|
||||
return nil, nil
|
||||
}
|
||||
//log.Debug(operationID, "ws ch recvMsg success, code ", r.ErrCode)
|
||||
if r.ErrCode != 0 {
|
||||
//log.Error(operationID, "ws ch recvMsg failed, code, err msg: ", r.ErrCode, r.ErrMsg)
|
||||
//switch r.ErrCode {
|
||||
//case int(constant.ErrInBlackList.ErrCode):
|
||||
// return nil, &constant.ErrInBlackList
|
||||
//case int(constant.ErrNotFriend.ErrCode):
|
||||
// return nil, &constant.ErrNotFriend
|
||||
//}
|
||||
//return nil, errors.New(utils.IntToString(r.ErrCode) + ":" + r.ErrMsg)
|
||||
} else {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
case <-time.After(time.Second * time.Duration(timeout)):
|
||||
//log.Error(operationID, "ws ch recvMsg err, timeout")
|
||||
//if w.conn.IsNil() {
|
||||
// return nil, errors.New("ws ch recvMsg err, timeout,conn is nil")
|
||||
//}
|
||||
//if w.conn.CheckSendConnDiffNow() {
|
||||
// return nil, constant.WsRecvConnDiff
|
||||
//} else {
|
||||
// return nil, constant.WsRecvConnSame
|
||||
//}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
Reference in New Issue
Block a user