import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; import 'package:buffer/buffer.dart'; import 'package:chat/data/UserData.dart'; import 'package:chat/data/chat_data_mgr.dart'; import 'package:chat/data/constants.dart'; import 'package:chat/data/group_data_mgr.dart'; import 'package:chat/generated/i18n.dart'; import 'package:chat/proto/all.pbserver.dart'; import 'package:chat/utils/net_state_util.dart'; import 'package:chat/utils/screen.dart'; import 'package:chat/utils/upload_util.dart'; import 'package:oktoast/oktoast.dart'; import 'package:protobuf/protobuf.dart'; import 'package:web_socket_channel/io.dart'; import 'HttpUtil.dart'; import 'MessageMgr.dart'; import 'TokenMgr.dart'; import 'msgHandler.dart'; const HeartInverval = 30000; const ReconnectCountLimit = 5; //重连次数限制 const MinMsgmByteLen = 9; class NetWork { //私有构造函数 NetWork._internal(); //保存单例 static NetWork _singleton = new NetWork._internal(); //工厂构造函数 factory NetWork() => _singleton; static IOWebSocketChannel channel; // enum ChatState { // connecting, //连接中 // connected, //连接成功 // connectFailed, //连接失败 // logining, //登录中 // logined, //登录成功 // loginFailed //登录失败 // } bool isLogin = false; //是否登录聊天服务器 int heartOutCount = 0; //超时次数 int reconnectCount = 0; //重连次数 bool isConnecting = false; //避免重复连接 bool isConnect = false; bool isNormalClose = false; NetStateBloc _stateBloc = NetStateBloc(); //监控网络状态 Stream get stream => _stateBloc.stream; Timer heartTimer; Timer sendTimer; Timer connectServerTimer; //尝试连接服务器 Timer loginTimer; //登录 // 计时器 Timer iosBgTimer; //登录计时器 bool isInit = false; String host; int port; static String serverIp; initNetWithServerInfo() { var data = { "userid": UserData().basicInfo.userId.toString(), }; data['sign'] = TokenMgr().getSign(data); HttpUtil().post('/service/config', data: data).then((res) { print('获取聊天服务器地址成功'); res = json.decode(res.toString()); if (res['code'] == 0) { var config = res['data'][0]; host = config['ip']; port = config['port']; print('host : $host port : $port'); // host = '192.168.0.139'; // port = 9091; serverIp = 'ws://' + host + ':' + port.toString(); if (host == null || host.length == 0) { // showToast('server url error : $host : $port'); } else { createWebSocket(); _stateBloc.start(); isInit = true; } } }); } checkChatState() { print('检查聊天状态'); if (!isConnect) { print('没有连接'); _stateBloc.addState(ChatState.connectFailed); } else if (!isLogin) { print('没有登录'); _stateBloc.addState(ChatState.loginFailed); } } onData(dynamic message) { //任何一条消息都作为心跳包处理 receiveHeart(); if (message is String) { print(message); } else if (message is Uint8List) { var buffReader = ByteDataReader(endian: Endian.big); buffReader.add(message); int comId = buffReader.readInt16(); int msgId = buffReader.readInt16(); buffReader.readInt8(); int msgLen = buffReader.readUint32(); if (message.length < msgLen) { print('数据没有接受完毕'); return; } int pbLen = msgLen - MinMsgmByteLen; var content; if (pbLen > 0) { content = buffReader.read(pbLen); } if (comId == ComId.Heart) { if (msgId == 3) { onConnect(); } } else if (comId == ComId.Login) { if (msgId == 2) { //登陆成功回应 var msgContent = LoginAccountRes.fromBuffer(content); if (msgContent.errorCode == 0) { UserData().isTranslateUser =msgContent.isTransHKInter; //是否是翻译人员 MessageMgr().emit(MessageMgr.TRANSLATE_HK_IS_RECEIVED_ORDER,msgContent.isReceipt);//是否开启接单 print('登录成功'); isLogin = true; _stateBloc.addState(ChatState.logined); loginTimer?.cancel(); MsgHandler.flushCacheMsg(); UploadUtil().setUploadUrl(msgContent.httpAddr); GroupInfoMgr().checkGroupValid(); } else { _stateBloc.addState(ChatState.loginFailed); print('登录失败${msgContent.errorCode}'); showToast(I18n.of(Constants.getCurrentContext()).fail); } } else if (msgId == 3) { //用户登出,其他设备替换 isLogin = false; var msgContent = PushUserOutLogin.fromBuffer(content); print('登出原因:${msgContent.errorCode}'); MessageMgr().emit('Login Out'); } } else { MsgHandler.handlerServerMsg(comId, msgId, content); } } else { print("Unknown datatype recieved : " + message.runtimeType.toString()); } } onConnect() { print('连接服务器成功'); connectServerTimer?.cancel(); isNormalClose = false; isConnect = true; isConnecting = false; heartOutCount = 0; reconnectCount = 0; _stateBloc.addState(ChatState.connected); login(); startHeartTimer(); } reLogin() { if (isConnect) { login(); } else { initNetWithServerInfo(); } } void sendMsg(int comId, int msgId, [GeneratedMessage pb]) { //序列化pb对象 Uint8List pbBody; int pbLen = 0; if (pb != null) { pbBody = pb.writeToBuffer(); pbLen = pbBody.length; } //包头部分 var msgLen = pbLen + MinMsgmByteLen; //整体长度 var buffWriter = ByteDataWriter(bufferLength: msgLen, endian: Endian.big); buffWriter.writeInt16(comId); buffWriter.writeInt16(msgId); buffWriter.writeInt8(0x24); buffWriter.writeUint32(msgLen); if (pbLen > 0) { buffWriter.write(pbBody); } var finalMsg = buffWriter.toBytes(); //给服务器发消息 try { if (channel != null && channel.closeCode == null && isConnect) { // print("发送消息"); channel.sink.add(finalMsg); } else { reconnect(); print('channel 为空,无法发送消息'); } } catch (e) { print("send捕获异常e=${e.toString()}"); } } startHeartTimer() { heartOutCount = 0; heartTimer?.cancel(); print('开始心跳计时器'); heartTimer = Timer.periodic(Duration(milliseconds: HeartInverval), (t) { sendHeart(); }); } receiveHeart() { //print('收到心跳${DateTime.now()}'); heartOutCount = 0; sendTimer?.cancel(); } sendHeart() { if (heartOutCount < 3) { // print('发送心跳${DateTime.now()}'); sendMsg(ComId.Heart, 1); sendTimer = Timer(Duration(milliseconds: HeartInverval), () { //超时了,次数加1 heartOutCount += 1; print('心跳超时$heartOutCount次'); }); } else { print('心跳次数大于 $heartOutCount'); close(); // createWebSocket(); } } close() { print('清除连接状态'); if (isConnecting) { print('连接状态,返回'); return; } isConnecting = false; isConnect = false; isLogin = false; heartOutCount = 0; sendTimer?.cancel(); heartTimer?.cancel(); loginTimer?.cancel(); print('清除计时器'); connectServerTimer?.cancel(); if (channel != null) { channel.sink.close(); channel = null; } } reallyClose() { if(iosBgTimer==null){ debugPrint('#### ios bg 没有计时器--计时器你启动'); iosBgTimer = Timer(Duration(seconds: 20), () async{ debugPrint('#### ios bg ios后台关闭socket连接'); isNormalClose=true; isConnecting = false; isConnect = false; isLogin = false; heartOutCount = 0; sendTimer?.cancel(); heartTimer?.cancel(); loginTimer?.cancel(); connectServerTimer?.cancel(); if (channel != null) { await channel.sink.close(); channel = null; } }); }else{ debugPrint('#### ios bg 已有计时器'); } } checkConnect() { debugPrint(' ios bg 取消计时器'); iosBgTimer?.cancel(); iosBgTimer=null; if (isInit && !isConnect && !isConnecting) { reconnect(); } else { if (!isLogin) { login(); } } } createWebSocket() { if (isConnecting) { return; } if (channel != null) { channel.sink.close(); channel = null; } connectServerTimer?.cancel(); print('尝试连接服务器'); isConnecting = true; _stateBloc.addState(ChatState.connecting); reconnectCount += 1; if (reconnectCount > ReconnectCountLimit) { print('重连数次超出'); _stateBloc.addState(ChatState.connectFailed); isConnecting = false; close(); return; } try { channel = IOWebSocketChannel.connect(serverIp); channel.stream.listen( onData, onDone: onDone, onError: onError, ); } catch (e) { // showToast('连接错误${e.toString()}'); isConnecting = false; //_stateBloc.addState(ChatState.connectFailed); } } login() { var myId = UserData().basicInfo.userId; if (myId == null) { return; } var data = { "userid": UserData().basicInfo.userId, }; LoginAccountReq req = LoginAccountReq.create(); req.userId = data['userid']; req.sigin = TokenMgr().getSign(data); print('发送登陆消息'); loginTimer = Timer(Duration(milliseconds: 3000), () { _stateBloc.addState(ChatState.loginFailed); }); sendMsg(ComId.Login, 1, req); _stateBloc.addState(ChatState.logining); } singOut() { print('用户登出'); //清缓存 ChatDataMgr().logout(); MsgHandler.clear(); isNormalClose = true; close(); } onError(error) { isConnecting = false; print('ws error $error'); } reconnect() { close(); reconnectCount = 0; if (host == null || host.length == 0) { initNetWithServerInfo(); } else { createWebSocket(); } } onDone() { print('ws channel closed${DateTime.now()}'); close(); if (!isNormalClose ) { print('无法连接聊天服务器,5秒后重新连接'); connectServerTimer = Timer(Duration(seconds: 5), () { createWebSocket(); }); } } }