-
Notifications
You must be signed in to change notification settings - Fork 866
/
SocketTCP.lua
202 lines (174 loc) · 6.24 KB
/
SocketTCP.lua
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
--[[
For quick-cocos2d-x
SocketTCP lua
@author zrong (zengrong.net)
Creation: 2013-11-12
Last Modification: 2013-12-05
@see http://cn.quick-x.com/?topic=quickkydsocketfzl
]]
local SOCKET_TICK_TIME = 0.1 -- check socket data interval
local SOCKET_RECONNECT_TIME = 5 -- socket reconnect try interval
local SOCKET_CONNECT_FAIL_TIMEOUT = 3 -- socket failure timeout
local STATUS_CLOSED = "closed"
local STATUS_NOT_CONNECTED = "Socket is not connected"
local STATUS_ALREADY_CONNECTED = "already connected"
local STATUS_ALREADY_IN_PROGRESS = "Operation already in progress"
local STATUS_TIMEOUT = "timeout"
local scheduler = require("framework.scheduler")
local socket = require "socket"
local SocketTCP = class("SocketTCP")
SocketTCP.EVENT_DATA = "SOCKET_TCP_DATA"
SocketTCP.EVENT_CLOSE = "SOCKET_TCP_CLOSE"
SocketTCP.EVENT_CLOSED = "SOCKET_TCP_CLOSED"
SocketTCP.EVENT_CONNECTED = "SOCKET_TCP_CONNECTED"
SocketTCP.EVENT_CONNECT_FAILURE = "SOCKET_TCP_CONNECT_FAILURE"
SocketTCP._VERSION = socket._VERSION
SocketTCP._DEBUG = socket._DEBUG
function SocketTCP.getTime()
return socket.gettime()
end
function SocketTCP:ctor(__host, __port, __retryConnectWhenFailure)
cc(self):addComponent("components.behavior.EventProtocol"):exportMethods()
self.host = __host
self.port = __port
self.tickScheduler = nil -- timer for data
self.reconnectScheduler = nil -- timer for reconnect
self.connectTimeTickScheduler = nil -- timer for connect timeout
self.name = 'SocketTCP'
self.tcp = nil
self.isRetryConnect = __retryConnectWhenFailure
self.isConnected = false
end
function SocketTCP:setName( __name )
self.name = __name
return self
end
function SocketTCP:setTickTime(__time)
SOCKET_TICK_TIME = __time
return self
end
function SocketTCP:setReconnTime(__time)
SOCKET_RECONNECT_TIME = __time
return self
end
function SocketTCP:setConnFailTime(__time)
SOCKET_CONNECT_FAIL_TIMEOUT = __time
return self
end
function SocketTCP:connect(__host, __port, __retryConnectWhenFailure)
if __host then self.host = __host end
if __port then self.port = __port end
if __retryConnectWhenFailure ~= nil then self.isRetryConnect = __retryConnectWhenFailure end
assert(self.host or self.port, "Host and port are necessary!")
--printInfo("%s.connect(%s, %d)", self.name, self.host, self.port)
self.tcp = socket.tcp()
self.tcp:settimeout(0)
local function __checkConnect()
local __succ = self:_connect()
if __succ then
self:_onConnected()
end
return __succ
end
if not __checkConnect() then
-- check whether connection is success
-- the connection is failure if socket isn't connected after SOCKET_CONNECT_FAIL_TIMEOUT seconds
local __connectTimeTick = function ()
--printInfo("%s.connectTimeTick", self.name)
if self.isConnected then return end
self.waitConnect = self.waitConnect or 0
self.waitConnect = self.waitConnect + SOCKET_TICK_TIME
if self.waitConnect >= SOCKET_CONNECT_FAIL_TIMEOUT then
self.waitConnect = nil
self:close()
self:_connectFailure()
end
__checkConnect()
end
self.connectTimeTickScheduler = scheduler.scheduleGlobal(__connectTimeTick, SOCKET_TICK_TIME)
end
end
function SocketTCP:send(__data)
assert(self.isConnected, self.name .. " is not connected.")
self.tcp:send(__data)
end
function SocketTCP:close( ... )
--printInfo("%s.close", self.name)
self.tcp:close();
if self.connectTimeTickScheduler then scheduler.unscheduleGlobal(self.connectTimeTickScheduler) end
if self.tickScheduler then scheduler.unscheduleGlobal(self.tickScheduler) end
self:dispatchEvent({name=SocketTCP.EVENT_CLOSE})
end
-- disconnect on user's own initiative.
function SocketTCP:disconnect()
self:_disconnect()
self.isRetryConnect = false -- initiative to disconnect, no reconnect.
end
--------------------
-- private
--------------------
--- When connect a connected socket server, it will return "already connected"
-- @see: http://lua-users.org/lists/lua-l/2009-10/msg00584.html
function SocketTCP:_connect()
local __succ, __status = self.tcp:connect(self.host, self.port)
-- print("SocketTCP._connect:", __succ, __status)
return __succ == 1 or __status == STATUS_ALREADY_CONNECTED
end
function SocketTCP:_disconnect()
self.isConnected = false
self.tcp:shutdown()
self:dispatchEvent({name=SocketTCP.EVENT_CLOSED})
end
function SocketTCP:_onDisconnect()
--printInfo("%s._onDisConnect", self.name);
self.isConnected = false
self:dispatchEvent({name=SocketTCP.EVENT_CLOSED})
self:_reconnect();
end
-- connecte success, cancel the connection timerout timer
function SocketTCP:_onConnected()
--printInfo("%s._onConnectd", self.name)
self.isConnected = true
self:dispatchEvent({name=SocketTCP.EVENT_CONNECTED})
if self.connectTimeTickScheduler then scheduler.unscheduleGlobal(self.connectTimeTickScheduler) end
local __tick = function()
while true do
-- if use "*l" pattern, some buffer will be discarded, why?
local __body, __status, __partial = self.tcp:receive("*a") -- read the package body
--print("body:", __body, "__status:", __status, "__partial:", __partial)
if __status == STATUS_CLOSED or __status == STATUS_NOT_CONNECTED then
self:close()
if self.isConnected then
self:_onDisconnect()
else
self:_connectFailure()
end
return
end
if (__body and string.len(__body) == 0) or
(__partial and string.len(__partial) == 0)
then return end
if __body and __partial then __body = __body .. __partial end
self:dispatchEvent({name=SocketTCP.EVENT_DATA, data=(__partial or __body), partial=__partial, body=__body})
end
end
-- start to read TCP data
self.tickScheduler = scheduler.scheduleGlobal(__tick, SOCKET_TICK_TIME)
end
function SocketTCP:_connectFailure(status)
--printInfo("%s._connectFailure", self.name);
self:dispatchEvent({name=SocketTCP.EVENT_CONNECT_FAILURE})
self:_reconnect();
end
-- if connection is initiative, do not reconnect
function SocketTCP:_reconnect(__immediately)
if not self.isRetryConnect then return end
printInfo("%s._reconnect", self.name)
if __immediately then self:connect() return end
if self.reconnectScheduler then scheduler.unscheduleGlobal(self.reconnectScheduler) end
local __doReConnect = function ()
self:connect()
end
self.reconnectScheduler = scheduler.performWithDelayGlobal(__doReConnect, SOCKET_RECONNECT_TIME)
end
return SocketTCP