Hibok
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

450 lines
11 KiB

  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'dart:typed_data';
  4. import 'package:buffer/buffer.dart';
  5. import 'package:chat/data/UserData.dart';
  6. import 'package:chat/data/chat_data_mgr.dart';
  7. import 'package:chat/data/constants.dart';
  8. import 'package:chat/data/group_data_mgr.dart';
  9. import 'package:chat/generated/i18n.dart';
  10. import 'package:chat/proto/all.pbserver.dart';
  11. import 'package:chat/utils/LoadingDialog.dart';
  12. import 'package:chat/utils/net_state_util.dart';
  13. import 'package:chat/utils/screen.dart';
  14. import 'package:chat/utils/upload_util.dart';
  15. import 'package:oktoast/oktoast.dart';
  16. import 'package:protobuf/protobuf.dart';
  17. import 'package:web_socket_channel/io.dart';
  18. import 'HttpUtil.dart';
  19. import 'MessageMgr.dart';
  20. import 'TokenMgr.dart';
  21. import 'msgHandler.dart';
  22. const HeartInverval = 30000;
  23. const ReconnectCountLimit = 5; //重连次数限制
  24. const MinMsgmByteLen = 9;
  25. class NetWork {
  26. //私有构造函数
  27. NetWork._internal();
  28. //保存单例
  29. static NetWork _singleton = new NetWork._internal();
  30. //工厂构造函数
  31. factory NetWork() => _singleton;
  32. static IOWebSocketChannel channel;
  33. // enum ChatState {
  34. // connecting, //连接中
  35. // connected, //连接成功
  36. // connectFailed, //连接失败
  37. // logining, //登录中
  38. // logined, //登录成功
  39. // loginFailed //登录失败
  40. // }
  41. bool isLogin = false; //是否登录聊天服务器
  42. int heartOutCount = 0; //超时次数
  43. int reconnectCount = 0; //重连次数
  44. bool isConnecting = false; //避免重复连接
  45. bool isConnect = false;
  46. bool isNormalClose = false;
  47. NetStateBloc _stateBloc = NetStateBloc(); //监控网络状态
  48. Stream<int> get stream => _stateBloc.stream;
  49. Timer heartTimer;
  50. Timer sendTimer;
  51. Timer connectServerTimer; //尝试连接服务器
  52. Timer loginTimer; //登录
  53. // 计时器
  54. Timer iosBgTimer; //登录计时器
  55. bool isInit = false;
  56. String host;
  57. int port;
  58. static String serverIp;
  59. initNetWithServerInfo() {
  60. var data = {
  61. "userid": UserData().basicInfo.userId.toString(),
  62. };
  63. data['sign'] = TokenMgr().getSign(data);
  64. HttpUtil().post('/service/config', data: data).then((res) {
  65. print('获取聊天服务器地址成功');
  66. res = json.decode(res.toString());
  67. if (res['code'] == 0) {
  68. var config = res['data'][0];
  69. host = config['ip'];
  70. port = config['port'];
  71. print('host : $host port : $port');
  72. // host = '192.168.0.139';
  73. // port = 9091;
  74. serverIp = 'ws://' + host + ':' + port.toString();
  75. if (host == null || host.length == 0) {
  76. // showToast('server url error : $host : $port');
  77. } else {
  78. createWebSocket();
  79. _stateBloc.start();
  80. isInit = true;
  81. }
  82. }
  83. });
  84. }
  85. checkChatState() {
  86. print('检查聊天状态');
  87. if (!isConnect) {
  88. print('没有连接');
  89. _stateBloc.addState(ChatState.connectFailed);
  90. } else if (!isLogin) {
  91. print('没有登录');
  92. _stateBloc.addState(ChatState.loginFailed);
  93. }
  94. }
  95. onData(dynamic message) {
  96. //任何一条消息都作为心跳包处理
  97. receiveHeart();
  98. if (message is String) {
  99. print(message);
  100. } else if (message is Uint8List) {
  101. var buffReader = ByteDataReader(endian: Endian.big);
  102. buffReader.add(message);
  103. int comId = buffReader.readInt16();
  104. int msgId = buffReader.readInt16();
  105. buffReader.readInt8();
  106. int msgLen = buffReader.readUint32();
  107. if (message.length < msgLen) {
  108. print('数据没有接受完毕');
  109. return;
  110. }
  111. int pbLen = msgLen - MinMsgmByteLen;
  112. var content;
  113. if (pbLen > 0) {
  114. content = buffReader.read(pbLen);
  115. }
  116. if (comId == ComId.Heart) {
  117. if (msgId == 3) {
  118. onConnect();
  119. }
  120. } else if (comId == ComId.Login) {
  121. if (msgId == 2) {
  122. //登陆成功回应
  123. var msgContent = LoginAccountRes.fromBuffer(content);
  124. if (msgContent.errorCode == 0) {
  125. print('登录成功');
  126. isLogin = true;
  127. _stateBloc.addState(ChatState.logined);
  128. loginTimer?.cancel();
  129. MsgHandler.flushCacheMsg();
  130. UploadUtil().setUploadUrl(msgContent.httpAddr);
  131. GroupInfoMgr().checkGroupValid();
  132. } else {
  133. _stateBloc.addState(ChatState.loginFailed);
  134. print('登录失败${msgContent.errorCode}');
  135. showToast(I18n.of(LoadingManage.context).fail);
  136. }
  137. } else if (msgId == 3) {
  138. //用户登出,其他设备替换
  139. isLogin = false;
  140. var msgContent = PushUserOutLogin.fromBuffer(content);
  141. print('登出原因:${msgContent.errorCode}');
  142. MessageMgr().emit('Login Out');
  143. }
  144. } else {
  145. MsgHandler.handlerServerMsg(comId, msgId, content);
  146. }
  147. } else {
  148. print("Unknown datatype recieved : " + message.runtimeType.toString());
  149. }
  150. }
  151. onConnect() {
  152. print('连接服务器成功');
  153. connectServerTimer?.cancel();
  154. isNormalClose = false;
  155. isConnect = true;
  156. isConnecting = false;
  157. heartOutCount = 0;
  158. reconnectCount = 0;
  159. _stateBloc.addState(ChatState.connected);
  160. login();
  161. startHeartTimer();
  162. }
  163. reLogin() {
  164. if (isConnect) {
  165. login();
  166. } else {
  167. initNetWithServerInfo();
  168. }
  169. }
  170. void sendMsg(int comId, int msgId, [GeneratedMessage pb]) {
  171. //序列化pb对象
  172. Uint8List pbBody;
  173. int pbLen = 0;
  174. if (pb != null) {
  175. pbBody = pb.writeToBuffer();
  176. pbLen = pbBody.length;
  177. }
  178. //包头部分
  179. var msgLen = pbLen + MinMsgmByteLen; //整体长度
  180. var buffWriter = ByteDataWriter(bufferLength: msgLen, endian: Endian.big);
  181. buffWriter.writeInt16(comId);
  182. buffWriter.writeInt16(msgId);
  183. buffWriter.writeInt8(0x24);
  184. buffWriter.writeUint32(msgLen);
  185. if (pbLen > 0) {
  186. buffWriter.write(pbBody);
  187. }
  188. var finalMsg = buffWriter.toBytes();
  189. //给服务器发消息
  190. try {
  191. if (channel != null && channel.closeCode == null && isConnect) {
  192. // print("发送消息");
  193. channel.sink.add(finalMsg);
  194. } else {
  195. reconnect();
  196. print('channel 为空,无法发送消息');
  197. }
  198. } catch (e) {
  199. print("send捕获异常e=${e.toString()}");
  200. }
  201. }
  202. startHeartTimer() {
  203. heartOutCount = 0;
  204. heartTimer?.cancel();
  205. print('开始心跳计时器');
  206. heartTimer = Timer.periodic(Duration(milliseconds: HeartInverval), (t) {
  207. sendHeart();
  208. });
  209. }
  210. receiveHeart() {
  211. //print('收到心跳${DateTime.now()}');
  212. heartOutCount = 0;
  213. sendTimer?.cancel();
  214. }
  215. sendHeart() {
  216. if (heartOutCount < 3) {
  217. // print('发送心跳${DateTime.now()}');
  218. sendMsg(ComId.Heart, 1);
  219. sendTimer = Timer(Duration(milliseconds: HeartInverval), () {
  220. //超时了,次数加1
  221. heartOutCount += 1;
  222. print('心跳超时$heartOutCount次');
  223. });
  224. } else {
  225. print('心跳次数大于 $heartOutCount');
  226. close();
  227. // createWebSocket();
  228. }
  229. }
  230. close() {
  231. print('清除连接状态');
  232. if (isConnecting) {
  233. print('连接状态,返回');
  234. return;
  235. }
  236. isConnecting = false;
  237. isConnect = false;
  238. isLogin = false;
  239. heartOutCount = 0;
  240. sendTimer?.cancel();
  241. heartTimer?.cancel();
  242. loginTimer?.cancel();
  243. print('清除计时器');
  244. connectServerTimer?.cancel();
  245. if (channel != null) {
  246. channel.sink.close();
  247. channel = null;
  248. }
  249. }
  250. reallyClose() {
  251. if(iosBgTimer==null){
  252. debugPrint('#### ios bg 没有计时器--计时器你启动');
  253. iosBgTimer = Timer(Duration(seconds: 20), () async{
  254. debugPrint('#### ios bg ios后台关闭socket连接');
  255. isNormalClose=true;
  256. isConnecting = false;
  257. isConnect = false;
  258. isLogin = false;
  259. heartOutCount = 0;
  260. sendTimer?.cancel();
  261. heartTimer?.cancel();
  262. loginTimer?.cancel();
  263. connectServerTimer?.cancel();
  264. if (channel != null) {
  265. await channel.sink.close();
  266. channel = null;
  267. }
  268. });
  269. }else{
  270. debugPrint('#### ios bg 已有计时器');
  271. }
  272. }
  273. checkConnect() {
  274. debugPrint(' ios bg 取消计时器');
  275. iosBgTimer?.cancel();
  276. if (isInit && !isConnect && !isConnecting) {
  277. reconnect();
  278. } else {
  279. if (!isLogin) {
  280. login();
  281. }
  282. }
  283. }
  284. createWebSocket() {
  285. if (isConnecting) {
  286. return;
  287. }
  288. if (channel != null) {
  289. channel.sink.close();
  290. channel = null;
  291. }
  292. connectServerTimer?.cancel();
  293. print('尝试连接服务器');
  294. isConnecting = true;
  295. _stateBloc.addState(ChatState.connecting);
  296. reconnectCount += 1;
  297. if (reconnectCount > ReconnectCountLimit) {
  298. print('重连数次超出');
  299. _stateBloc.addState(ChatState.connectFailed);
  300. isConnecting = false;
  301. close();
  302. return;
  303. }
  304. try {
  305. channel = IOWebSocketChannel.connect(serverIp);
  306. channel.stream.listen(
  307. onData,
  308. onDone: onDone,
  309. onError: onError,
  310. );
  311. } catch (e) {
  312. // showToast('连接错误${e.toString()}');
  313. isConnecting = false;
  314. //_stateBloc.addState(ChatState.connectFailed);
  315. }
  316. }
  317. login() {
  318. var myId = UserData().basicInfo.userId;
  319. if (myId == null) {
  320. return;
  321. }
  322. var data = {
  323. "userid": UserData().basicInfo.userId,
  324. };
  325. LoginAccountReq req = LoginAccountReq.create();
  326. req.userId = data['userid'];
  327. req.sigin = TokenMgr().getSign(data);
  328. print('发送登陆消息');
  329. loginTimer = Timer(Duration(milliseconds: 3000), () {
  330. _stateBloc.addState(ChatState.loginFailed);
  331. });
  332. sendMsg(ComId.Login, 1, req);
  333. _stateBloc.addState(ChatState.logining);
  334. }
  335. singOut() {
  336. print('用户登出');
  337. //清缓存
  338. ChatDataMgr().logout();
  339. MsgHandler.clear();
  340. isNormalClose = true;
  341. close();
  342. }
  343. onError(error) {
  344. isConnecting = false;
  345. print('ws error $error');
  346. }
  347. reconnect() {
  348. close();
  349. reconnectCount = 0;
  350. if (host == null || host.length == 0) {
  351. initNetWithServerInfo();
  352. } else {
  353. createWebSocket();
  354. }
  355. }
  356. onDone() {
  357. print('ws channel closed${DateTime.now()}');
  358. close();
  359. if (!isNormalClose ) {
  360. print('无法连接聊天服务器,5秒后重新连接');
  361. connectServerTimer = Timer(Duration(seconds: 5), () {
  362. createWebSocket();
  363. });
  364. }
  365. }
  366. }