-
Notifications
You must be signed in to change notification settings - Fork 0
/
socket_client.go
216 lines (200 loc) · 5.34 KB
/
socket_client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
package gosocket
import (
"crypto/tls"
"encoding/json"
"errors"
"net"
"strconv"
"sync"
"time"
)
// IConnectProvider is used to provide connect info
type IConnectProvider interface {
// GetConnectInfo Implement this function to provide connect info string to the server
// 重载这个函数向服务器提供连接信息
GetConnectInfo() string
}
// Client is a class responsible for connecting to the server by socket
// Make sure the port and the isTls value are the same as the ones on server
type Client struct {
ip string
port int
isTls bool
logger ILogger
conn *SocketClientConn
provider IConnectProvider
pingTimer *Timer
}
// NewClient create a new client by providing the ip, port of the server and whether to use tls
// 创建一个新的客户端连接
func NewClient(ip string, port int, isTls bool, log ILogger, provider IConnectProvider) *Client {
c := &Client{
ip: ip,
port: port,
isTls: isTls,
logger: log,
provider: provider,
}
return c
}
// Connect start to connect the server
// 连接聊天服务器
func (client *Client) Connect() (err error) {
defer func() {
if recoverObj := recover(); recoverObj != nil {
client.logger.Error(recoverObj)
}
}()
if client.port <= 0 {
panic("port needs to be above 0")
}
//连接服务器
var connection net.Conn
addr := client.ip + ":" + strconv.Itoa(client.port)
if client.isTls {
config := &tls.Config{
InsecureSkipVerify: true,
}
connection, err = tls.Dial("tcp", addr, config)
} else {
connection, err = net.Dial("tcp", addr)
}
if err != nil {
return
}
client.conn = NewSocketClientConn(connection, client.logger)
connectInfo := "{}"
if client.provider != nil {
connectInfo = client.provider.GetConnectInfo()
}
err = client.conn.Connect(connectInfo)
//如果连接成功
if err == nil {
//每隔一段时间发送心跳包
client.startAutoPing()
}
return
}
// GetDataCallback is the callback used by GetData function
type GetDataCallback func(err error, data string)
// GetData Call apis of the server
// 调用服务器的接口
//
// payloadType should contain two parts, for example 'chat.AddMessage'
// The first part corresponds to controller name
// The second part corresponds to action name
// In this way, the request will be routed to the certain action under certain controller automatically
//
// Payload should be the main content that is sent to the server, which will be encoded into json
func (client *Client) GetData(payloadType string, payload interface{}, callback GetDataCallback, data []byte) {
payloadStr := ""
if payload != nil {
payloadStr = JSONEncode(payload)
}
if client.conn == nil {
if callback != nil {
callback(errors.New("connect required"), "")
}
return
}
//加锁,确保计时器结束和接口返回不会出现并发
timeOutLock := &sync.RWMutex{}
isCallback := false
//启动计时器,如果一段时间没有收到服务器响应,则返回超时错误
timer := NewTimer(time.Second*10, func() {
timeOutLock.Lock()
defer timeOutLock.Unlock()
//如果已经收到服务器响应了,直接返回
if isCallback {
return
} else {
isCallback = true
}
if callback != nil {
callback(errors.New("timeout"), "")
}
})
client.conn.SendRequest(payloadType, payloadStr, func(payloadBody string) {
timeOutLock.Lock()
defer timeOutLock.Unlock()
defer func() {
if r := recover(); r != nil {
client.logger.Error(r)
}
}()
//如果已超时,直接返回
if isCallback {
return
} else {
isCallback = true
//停止计时器
timer.Stop()
}
err, ret := client.DecodeResponse(payloadBody)
if callback != nil {
callback(err, ret)
}
}, data)
}
type ClientResponseBody struct {
Status Status `json:"status"`
Message string `json:"message,omitempty"`
Data *json.RawMessage `json:"data,omitempty"`
}
// DecodeResponse Override this function to decode server response
// 重载这个方法对服务器的返回进行解析
func (client *Client) DecodeResponse(payloadBody string) (error, string) {
result := ""
respBody := ClientResponseBody{}
JSONDecode(payloadBody, &respBody)
if respBody.Status == StatusSuccess {
if respBody.Data != nil {
dataBytes, err := respBody.Data.MarshalJSON()
if err == nil {
result = string(dataBytes)
} else {
return errors.New("response data error"), ""
}
}
} else {
return errors.New("response status error"), ""
}
return nil, result
}
// Disconnect from server
// 断开与服务器的连接
func (client *Client) Disconnect() {
//停止心跳包
client.stopAutoPing()
//断开连接
client.conn.Disconnect()
client.conn = nil
}
// Start ping pong
// 开始心跳
func (client *Client) startAutoPing() {
client.pingTimer = NewTimer(60*time.Second, func() {
if client.conn != nil {
client.conn.SendPing()
}
})
}
// Stop ping pong
// 结束心跳
func (client *Client) stopAutoPing() {
//防止重复调用
if client.pingTimer != nil {
client.pingTimer.Stop()
client.pingTimer = nil
}
}
// OnSendReqReceived Override this function to handle the push notification from server
// 收到服务器推送
// ClientConnInterface
func (client *Client) OnSendReqReceived(reqType string, reqBody string) {}
// OnDisconnect Handle issues after the connection is off
// 连接已断开
// ClientConnInterface
func (client *Client) OnDisconnect() {
client.stopAutoPing()
}