diff --git a/Link/LinkClient.cpp b/Link/LinkClient.cpp new file mode 100644 index 00000000..aa52a2f2 --- /dev/null +++ b/Link/LinkClient.cpp @@ -0,0 +1,1300 @@ +#include "Kernel\TTime.h" +#include "Kernel\WaitHandle.h" + +#include "Net\Socket.h" +#include "Net\NetworkInterface.h" + +#include "Message\Json.h" + +#include "LinkClient.h" + +#include "Security\RC4.h" + +LinkClient* LinkClient::Current = nullptr; + +//static void BroadcastHelloTask(void* param); + +LinkClient::LinkClient() + : Routes(String::Compare) +{ + Token = 0; + + Opened = false; + Status = 0; + + LoginTime = 0; + LastSend = 0; + LastActive = 0; + Delay = 0; + MaxNotActive = 0; + + Master = nullptr; + Cfg = nullptr; + + Received = nullptr; + Param = nullptr; + + NextReport = -1; + ReportLength = 0; + + _Expect = nullptr; + + assert(!Current, "只能有一个令牌客户端实例"); + Current = this; +} + +void LinkClient::Open() +{ + if (Opened) return; + + TS("LinkClient::Open"); + + // 令牌客户端定时任务 + _task = Sys.AddTask(&LinkClient::LoopTask, this, 0, 1000, "令牌客户"); + + Cookie = Cfg->Token(); + + // 启动时记为最后一次活跃接收 + LastActive = Sys.Ms(); + + Opened = true; +} + +void LinkClient::Close() +{ + if (!Opened) return; + + Sys.RemoveTask(_task); + //Sys.RemoveTask(_taskBroadcast); + + if (Master) + { + delete Master; + Master = nullptr; + } + + auto& cs = Controls; + for (int i = 0; i < cs.Count(); i++) + { + delete cs[i]; + } + cs.Clear(); + + Opened = false; +} + +static TokenController* AddMaster(LinkClient& client) +{ + auto uri = client.Cfg->Uri(); + // 创建连接服务器的Socket + auto socket = Socket::CreateRemote(uri); + if (!socket) return nullptr; + + // 创建连接服务器的控制器 + auto ctrl = new TokenController(); + ctrl->_Socket = socket; + + // 创建客户端 + client.Master = ctrl; + + return ctrl; +} + +static TokenController* AddControl(LinkClient& client, NetworkInterface* host, const NetUri& uri, ushort remotePort) +{ + // 创建连接服务器的Socket + auto socket = host ? host->CreateClient(uri) : Socket::CreateClient(uri); + if (!socket) return nullptr; + + // 创建连接服务器的控制器 + auto ctrl = new TokenController(); + ctrl->_Socket = socket; + + // 创建客户端 + socket->Remote.Address = IPAddress::Broadcast(); + socket->Remote.Port = remotePort; + ctrl->ShowRemote = true; + client.Controls.Add(ctrl); + + return ctrl; +} + +void LinkClient::CheckNet() +{ + auto mst = Master; + auto& cs = Controls; + //assert(cs.Count() > 0, "令牌客户端还没设置控制器呢"); + + // 现在是否已连接 + bool linked = true; + + // 检测主链接 + if (!mst) + { + linked = false; + auto ctrl = AddMaster(*this); + if (!ctrl) return; + + debug_printf("LinkClient::CheckNet %s 成功创建主连接\r\n", ctrl->_Socket->Host->Name); + } + // 检测主链接是否已经断开 + else if (!mst->_Socket->Host->Linked) + { + linked = false; + debug_printf("LinkClient::CheckNet %s断开,切换主连接\r\n", mst->_Socket->Host->Name); + + delete mst; + Master = nullptr; + + Status = 0; + Token = 0; + + // 未连接时,加快网络检查速度 + Sys.SetTaskPeriod(_task, 1000); + + auto ctrl = AddMaster(*this); + if (!ctrl) return; + } + + // 从未连接转为已连接 + mst = Master; + if (!linked && mst) + { + Delegate2 dlg(&LinkClient::OnReceive, this); + mst->Received = dlg; + mst->Open(); + + Cfg->ServerIP = mst->_Socket->Remote.Address.Value; + } + + // 为其它网卡创建本地会话 + // 启动本地控制器 + if (_LocalReceive) + { + auto& nis = NetworkInterface::All; + for (int k = 0; k < nis.Count(); k++) + { + if (!nis[k]->Active()) continue; + + // 检测该接口上是否创建了控制器 + bool flag = false; + for (int i = 0; i < cs.Count(); i++) + { + if (cs[i]->_Socket->Host == nis[k]) + { + flag = true; + break; + } + } + // 在该接口上创建控制器 + if (!flag) + { + debug_printf("LinkClient::CheckNet %s 创建本地监听 ", nis[k]->Name); + + NetUri uri(NetType::Udp, IPAddress::Broadcast(), Cfg->Port); + auto ctrl = AddControl(*this, nis[k], uri, Cfg->Port); + if (ctrl) + { + ctrl->Received = _LocalReceive; + ctrl->Open(); + + debug_printf("成功\r\n"); + } + else + { + debug_printf("失败\r\n"); + } + } + } + // 令牌广播使用素数,避免跟别的任务重叠 + //if (cs.Count() > 0 && _taskBroadcast == 0) _taskBroadcast = Sys.AddTask(BroadcastHelloTask, this, 7000, 37000, "令牌广播"); + } + else if (cs.Count() > 0) + { + debug_printf("设置了本地令牌控制器,但是没有启用本地功能,你可能需要client->UserLocal()\r\n"); + } +} + +// 启用内网功能。必须显式调用,否则内网功能不参与编译链接,以减少大小 +void LinkClient::UseLocal() +{ + _LocalReceive = Delegate2(&LinkClient::OnReceiveLocal, this); +} + +bool LinkClient::Send(TokenMessage& msg, TokenController* ctrl) +{ + // 未登录之前,只能 握手、登录、注册 + if (Token == 0) + { + if (msg.Code != 0x01 && msg.Code != 0x02 && msg.Code != 0x07) return false; + } + + if (!ctrl) ctrl = Master; + if (!ctrl) return false; + + assert(ctrl, "LinkClient::Send"); + + // 最后发送仅统计主控制器 + if (ctrl == Master) LastSend = Sys.Ms(); + + return ctrl->Send(msg); +} + +bool LinkClient::Reply(TokenMessage& msg, TokenController* ctrl) +{ + // 未登录之前,只能 握手、登录、注册 + if (ctrl->Token == 0) + { + if (msg.Code != 0x01 && msg.Code != 0x02 && msg.Code != 0x07) return false; + } + + if (!ctrl) ctrl = Master; + if (!ctrl) return false; + + assert(ctrl, "LinkClient::Reply"); + + if (ctrl == Master) LastSend = Sys.Ms(); + + return ctrl->Reply(msg); +} + +void LinkClient::OnReceive(TokenMessage& msg, TokenController& ctrl) +{ + TS("LinkClient::OnReceive"); + + LastActive = Sys.Ms(); + + switch (msg.Code) + { + case 0x01: OnHello(msg, &ctrl); break; + case 0x02: OnLogin(msg, &ctrl); break; + case 0x03: OnPing(msg, &ctrl); break; + case 0x07: OnRegister(msg, &ctrl); break; + case 0x08: OnInvoke(msg, &ctrl); break; + } + // 握手登录心跳消息不需要转发 + if (msg.Code <= 0x03) return; + + // 消息转发 + if (Received) + { + Received(&ctrl, msg, Param); + } + else + { + switch (msg.Code) + { + case 0x05: OnRead(msg, &ctrl); break; + case 0x06: OnWrite(msg, &ctrl); break; + default: + break; + } + } +} + +void LinkClient::OnReceiveLocal(TokenMessage& msg, TokenController& ctrl) +{ + TS("LinkClient::OnReceiveLocal"); + debug_printf("LocalRev "); + + // 找到会话,如果不存在则创建 + auto remote = (IPEndPoint*)msg.State; + if (!remote) + { + debug_printf("无法取得消息来源地址,设计错误!\r\n"); + return; + } + // remote->Show(false); + + // 根据远程地址,从会话列表中找到会话。如果会话不存在,则新建会话 + TokenSession* ss = nullptr; + + for (int i = 0; i < Sessions.Count(); i++) + { + ss = (TokenSession*)Sessions[i]; + if (ss && ss->Remote == *remote) + { + debug_printf("ss[%d] ", i); + break; + } + ss = nullptr; + } + if (!ss) + { + debug_printf(" new TokenSession\r\n"); + ss = new TokenSession(*this, ctrl); + ss->Remote = *remote; + } + + TokenSession::Show(Sessions); + + ss->OnReceive(msg); +} + +// 内网分发 +void LinkClient::LocalSend(int start, const Buffer& bs) +{ + auto& cs = Sessions; + if (cs.Count() == 0) return; + + debug_printf("LocalSend\r\n"); + + TokenDataMessage dm; + dm.Start = start; + dm.Data = bs; + + TokenMessage msg; + msg.Code = 0x06; + dm.WriteMessage(msg); + + // 不要再发给云端 + //Send(msg); + + // 主动上报发给服务器的同时,也发给内网已登录用户 + for (int i = 0; i < cs.Count(); i++) + { + auto ss = (TokenSession*)cs[i]; + + if (ss && ss->Status >= 2) + { + debug_printf("ss[%d] ", i); + ss->Send(msg); + } + } +} + +// 常用系统级消息 + +// 定时任务 +void LinkClient::LoopTask() +{ + TS("LinkClient::LoopTask"); + + // 检查超时会话。倒序,因为可能有删除 + auto& cs = Sessions; + // 列表保存32个本地会话 + for (int i = cs.Count() - 1; i >= 0 && cs.Count() > 32; i--) + { + auto ss = (TokenSession*)cs[i]; + // 5分钟不活跃 或 1分钟未登录 销毁 + if (ss && ss->CheckExpired()) delete ss; + } + + // 最大不活跃时间ms,超过该时间时重启系统 + // WiFi触摸开关建议5~10分钟,网关建议5分钟 + // MaxNotActive 为零便不考虑重启 + if (MaxNotActive != 0 && LastActive + MaxNotActive < Sys.Ms()) Sys.Reboot(); + + auto flag = Master; + //if(!Master) + { + CheckNet(); + if (!Master) return; + + if (!flag) Sys.SetTaskPeriod(_task, 5000); + } + + // 状态。0准备、1握手完成、2登录后 + switch (Status) + { + case 0: + SayHello(false); + break; + case 1: + { + if (!Cfg->User()) + Register(); + else + { + Login(); + + //Sys.Sleep(1000); + //// 登录成功以后做一次内网广播 + //if (Status >= 2) SayHello(true); + + // 登录成功后,心跳一次,把数据同步上去 + Sys.Sleep(1000); + if (Status >= 2) Ping(); + } + + break; + } + case 2: + Ping(); + break; + } + + CheckReport(); +} + +/*void BroadcastHelloTask(void* param) +{ + TS("LinkClient::BroadcastHello"); + + auto client = (LinkClient*)param; + client->SayHello(true); +}*/ + +// 发送发现消息,告诉大家我在这 +// 请求:2版本 + S类型 + S名称 + 8本地时间 + 6本地IP端口 + S支持加密算法列表 +// 响应:2版本 + S类型 + S名称 + 8本地时间 + 6对方IP端口 + 1加密算法 + N密钥 +// 错误:0xFE + 1协议 + S服务器 + 2端口 +void LinkClient::SayHello(bool broadcast) +{ + TS("LinkClient::SayHello"); + + TokenMessage msg(0x01); + + HelloMessage ext; + ext.Reply = false; + ext.LocalTime = DateTime::Now().TotalMs(); + + auto& cs = Controls; + // 取第二通道为本地通道 + if (cs.Count() > 0) + { + auto ctrl = cs[0]; + auto sock = ctrl->_Socket; + //ext.EndPoint.Address = sock->Local.Address; + ext.EndPoint = sock->Local; + ext.Protocol = sock->Protocol == NetType::Udp ? 17 : 6; + } + + ext.Cipher = "RC4"; + ext.Name = Cfg->User(); + // 未注册时采用系统名称 + if (!ext.Name) + { + ext.Name = Sys.Name; + ext.Key = Buffer(Sys.ID, 16); + } + + ext.WriteMessage(msg); + ext.Show(true); + + // 特殊处理广播,指定广播地址,避免因为内网发现改变了本地端口 + if (broadcast) + { + msg.OneWay = true; + + //msg.State = &ctrl->_Socket->Remote; + for (int i = 0; i < cs.Count(); i++) + { + auto ctrl = cs[i]; + msg.State = &ctrl->_Socket->Remote; + if (ctrl->Port != nullptr) + { + Send(msg, ctrl); + } + } + } + else + Send(msg); +} + +// 握手响应 +bool LinkClient::OnHello(TokenMessage& msg, TokenController* ctrl) +{ + if (!msg.Reply) return false; + + // 解析数据 + HelloMessage ext; + ext.Reply = msg.Reply > 0; + + ext.ReadMessage(msg); + ext.Show(true); + + // 如果收到响应,并且来自来源服务器 + if (msg.Error) + { + if (OnRedirect(ext)) return false; + + TS("LinkClient::OnHello_Error"); + + Status = 0; + Token = 0; + + // 未握手错误,马上重新握手 + if (ext.ErrCode == 0x7F) Sys.SetTask(_task, true, 0); + } + else + { + TS("LinkClient::OnHello_Reply"); + //如果已经登陆还接收到握手响应,这不属于正常的握手响应(IP冲突会导致) + if (Status == 2) return false; + // 通讯密码 + if (ext.Key.Length() > 0) + { + if (ctrl) ctrl->Key.Copy(0, ext.Key, 0, ext.Key.Length()); + + debug_printf("握手得到通信密码:"); + ext.Key.Show(true); + } + Status = 1; + + // 握手完成后马上注册或登录 + Sys.SetTask(_task, true, 0); + + // 同步本地时间 + if (ext.LocalTime > 0) ((TTime&)Time).SetTime(ext.LocalTime / 1000); + } + + return true; +} + +bool LinkClient::OnRedirect(HelloMessage& msg) +{ + TS("LinkClient::OnRedirect"); + + if (!(msg.ErrCode == 0xFE || msg.ErrCode == 0xFD)) return false; + + Cookie = msg.Cookie; + + // 0xFE永久改变厂商地址 + if (msg.ErrCode == 0xFE) + { + auto cfg = Cfg; + cfg->Show(); + + cfg->Protocol = msg.Uri.Type; + cfg->Server() = msg.Uri.Host; + cfg->ServerPort = msg.Uri.Port; + cfg->Token() = msg.Cookie; + + cfg->Save(); + cfg->Show(); + } + + ChangeIPEndPoint(msg.Uri); + + return true; +} + +bool LinkClient::ChangeIPEndPoint(const NetUri& uri) +{ + TS("LinkClient::ChangeIPEndPoint"); + + debug_printf("ChangeIPEndPoint "); + + uri.ToString().Show(true); + + auto ctrl = Master; + auto socket = ctrl->_Socket; + if (socket == nullptr) return false; + + // 为了能够处理Tcp/Udp切换,重新建立连接 + + ctrl->Close(); + delete socket; + + socket = Socket::CreateRemote(uri); + ctrl->_Socket = socket; + ctrl->Open(); + + /*ctrl->Port->Close(); + socket->Remote.Port = uri.Port; + // 让驱动随机拿到 Port + socket->Local.Port = 0; + socket->Server = uri.Host;*/ + + //Cfg->ServerIP = socket->Remote.Address.Value; + // 马上开始重新握手 + Status = 0; + Sys.SetTask(_task, true, 0); + + return true; +} + +// 注册 +void LinkClient::Register() +{ + TS("LinkClient::Register"); + + debug_printf("LinkClient::Register\r\n"); + + RegisterMessage reg; + reg.User = Buffer(Sys.ID, 16).ToHex(); + + // 原始密码作为注册密码 + reg.Pass = Cfg->Pass(); + + auto now = Sys.Ms(); + reg.Salt = Buffer(&now, 8); + + reg.Show(true); + + TokenMessage msg(7); + reg.WriteMessage(msg); + Send(msg); +} + +void LinkClient::OnRegister(TokenMessage& msg, TokenController* ctrl) +{ + if (!msg.Reply) return; + + TS("LinkClient::OnRegister"); + + if (msg.Error) + { + ErrorMessage em; + em.Read(msg); + + debug_printf("注册失败,错误码 0x%02X!", em.ErrorCode); + em.ErrorContent.Show(true); + + return; + } + + auto cfg = Cfg; + + RegisterMessage reg; + reg.ReadMessage(msg); + cfg->User() = reg.User; + cfg->Pass() = reg.Pass; + + cfg->Show(); + cfg->Save(); + + Status = 0; + + Sys.SetTask(_task, true, 0); +} + +// 登录 +void LinkClient::Login() +{ + TS("LinkClient::Login"); + + LoginMessage login; + + auto cfg = Cfg; + login.User = cfg->User(); + + // 原始密码对盐值进行加密,得到登录密码 + // auto now = Sys.Ms(); + auto now = DateTime::Now().TotalMs(); + auto arr = Buffer(&now, 8); + ByteArray bs; + bs = arr; + // login.Salt = arr; + RC4::Encrypt(arr, cfg->Pass()); + // 散列明文和密码连接在一起 + auto pass = bs.ToHex(); + pass += arr.ToHex(); + login.Pass = pass; + + login.Cookie = Cookie; + + TokenMessage msg(2); + login.WriteMessage(msg); + login.Show(true); + + Send(msg); +} + +bool LinkClient::OnLogin(TokenMessage& msg, TokenController* ctrl) +{ + if (!msg.Reply) return false; + + TS("LinkClient::OnLogin"); + + Stream ms(msg.Data, msg.Length); + + LoginMessage logMsg; + logMsg.ReadMessage(msg); + logMsg.Show(true); + + if (logMsg.Error) + { + // 登录失败,清空令牌 + byte result = logMsg.ErrorCode; + + // 未登录错误,马上重新登录 + if (result == 0xF7) + { + // 任何错误,重新握手 + Status = 1; + Token = 0; + Register(); + return false; + } + + Token = 0; + Status = 0; + + if (result == 0x7F) Sys.SetTask(_task, true, 0); + } + else + { + Status = 2; + debug_printf("登录成功! "); + + Token = logMsg.Token; + + if (ctrl) ctrl->Token = Token; + + debug_printf("令牌:0x%08X ", Token); + if (logMsg.Key.Length()) + { + if (ctrl) ctrl->Key = logMsg.Key; + + debug_printf("通信密码:"); + logMsg.Key.Show(); + } + + debug_printf("\r\n"); + + // 登录成功后加大心跳间隔 + Sys.SetTaskPeriod(_task, 60000); + } + + return true; +} + +void LinkClient::Reset(const String& reason) +{ + auto time = DateTime::Now().TotalMs(); + + MemoryStream ms; + BinaryPair bp(ms); + bp.Set("time", (UInt64)time); + bp.Set("reason", reason); + + auto buf = Buffer(ms.GetBuffer(), ms.Position()); + + Invoke("Gateway/Reset", buf); + + debug_printf("设备500ms后重置\r\n"); + + Sys.Sleep(500); + + Config::Current->RemoveAll(); + Sys.Reboot(); +} + +void LinkClient::Reboot(const String& reason) +{ + auto time = DateTime::Now().TotalMs(); + + MemoryStream ms; + BinaryPair bp(ms); + bp.Set("time", (UInt64)time); + bp.Set("reason", reason); + + auto buf = Buffer(ms.GetBuffer(), ms.Position()); + + Invoke("Gateway/Reboot", buf); +} + +// Ping指令用于保持与对方的活动状态 +void LinkClient::Ping() +{ + TS("LinkClient::Ping"); + + if (LastActive > 0 && LastActive + 300000 < Sys.Ms()) + { + // 30秒无法联系,服务端可能已经掉线,重启Hello任务 + debug_printf("300秒无法联系,服务端可能已经掉线,重新开始握手\r\n"); + + Master->Key.SetLength(0); + + Status = 0; + + Sys.SetTaskPeriod(_task, 5000); + + return; + } + + // 30秒内发过数据,不再发送心跳 + if (LastSend > 0 && LastSend + 60000 > Sys.Ms()) return; + + TokenPingMessage pm; + pm.Data = &Store.Data; + + TokenMessage msg(3); + pm.WriteMessage(msg); + + Send(msg); +} + +bool LinkClient::OnPing(TokenMessage& msg, TokenController* ctrl) +{ + TS("LinkClient::OnPing"); + + if (!msg.Reply) return false; + + TokenPingMessage pm; + pm.ReadMessage(msg); + + int cost = (int)(DateTime::Now().TotalMs() - pm.LocalTime); + + if (Delay) + Delay = (Delay + cost) / 2; + else + Delay = cost; + + debug_printf("心跳延迟 %dms / %dms \r\n", cost, Delay); + + // 同步本地时间 + if (pm.ServerTime > 1000) ((TTime&)Time).SetTime(pm.ServerTime / 1000); + + return true; +} + +/******************************** 数据区 ********************************/ + +void LinkClient::Read(int start, int size) +{ + TokenDataMessage dm; + dm.Start = start; + dm.Size = size; + + TokenMessage msg; + msg.Code = 0x05; + dm.WriteMessage(msg); + + Send(msg); +} + +void LinkClient::Write(int start, const Buffer& bs) +{ + TokenDataMessage dm; + dm.Start = start; + dm.Data = bs; + + TokenMessage msg; + msg.Code = 0x06; + dm.WriteMessage(msg); + + Send(msg); + + // 主动上报发给服务器的同时,也发给内网已登录用户 + auto& cs = Controls; + for (int i = 0; i < cs.Count(); i++) + { + auto ss = (TokenSession*)cs[i]; + if (ss && ss->Status >= 2) + { + debug_printf("ss[%d] ", i); + ss->Send(msg); + } + } +} + +void LinkClient::Write(int start, byte dat) +{ + Write(start, Buffer(&dat, 1)); +} + +// 异步上传并等待响应 +int LinkClient::WriteAsync(int start, const Buffer& bs, int msTimeout) +{ + // 如果别的任务正在上传,此次写入失败 + if (_Expect) return 0; + + WaitHandle handle; + handle.State = (void*)start; + + _Expect = &handle; + Write(start, bs); + handle.WaitOne(msTimeout); + if (_Expect == &handle) _Expect = nullptr; + + // 可能失败 + if (!handle.Result) return 1; + + return (int)handle.State; +} + +void LinkClient::ReportAsync(int start, uint length) +{ + if (start + (int)length > Store.Data.Length()) + { + debug_printf("布置异步上报数据失败\r\n"); + debug_printf("sta:%d len:%d data.len:%d\r\n", start, length, Store.Data.Length()); + return; + } + + NextReport = start; + ReportLength = length; + + // 延迟上报,期间有其它上报任务到来将会覆盖 + Sys.SetTask(_task, true, 20); +} + +bool LinkClient::CheckReport() +{ + TS("LinkClient::CheckReport"); + + auto offset = NextReport; + int len = ReportLength; + + if (offset < 0) return false; + + // 检查索引,否则数组越界 + auto& bs = Store.Data; + if (bs.Length() >= offset + len) + { + if (len == 1) + Write(offset, bs[offset]); + else + Write(offset, Buffer(&bs[offset], len)); + } + NextReport = -1; + + return true; +} + +/* +请求:起始 + 大小 +响应:起始 + 数据 +*/ +void LinkClient::OnRead(const TokenMessage& msg, TokenController* ctrl) +{ + if (msg.Reply) return; + if (msg.Length < 2) return; + + auto rs = msg.CreateReply(); + + TokenDataMessage dm; + dm.ReadMessage(msg); + // dm.Show(true); + + bool rt = true; + if (dm.Start < 64) + rt = dm.ReadData(Store); + else if (dm.Start < 128) + { + dm.Start -= 64; + rt = dm.ReadData(Cfg->ToArray()); + dm.Start += 64; + } + + if (!rt) + { + debug_printf("rt == false\r\n"); + rs.Error = true; + auto ms = rs.ToStream(); + BinaryPair bp(ms); + bp.Set("ErrorCode", (byte)0x01); + rs.Length = ms.Position(); + } + else + { + debug_printf("Read "); + dm.Show(true); + dm.WriteMessage(rs); + } + + Reply(rs, ctrl); +} + +/* +请求:1起始 + N数据 +响应:1起始 + 1大小 + N数据 +错误:错误码2 + 1起始 + 1大小 +*/ +void LinkClient::OnWrite(const TokenMessage& msg, TokenController* ctrl) +{ + if (msg.Length < 2) return; + + TokenDataMessage dm; + dm.ReadMessage(msg); + debug_printf("Write "); + dm.Show(true); + + if (msg.Reply) { + // 拦截给同步方法 + auto handle = (WaitHandle*)_Expect; + if (handle) { + auto start = (int)handle->State; + if (start == dm.Start) { + // 设置事件,通知等待任务退出循环 + handle->State = (void*)dm.Size; + handle->Set(); + } + } + + return; + } + + bool rt = true; + if (dm.Start < 64) + { + rt = dm.WriteData(Store, true); + + // 读取响应里面,一次性把数据全部读取出来 + if (rt) + { + dm.Start = 0; + dm.Size = Store.Data.Length(); + //dm.Size = 0; + dm.Data = Store.Data; + } + } + else if (dm.Start < 128) + { + dm.Start -= 64; + auto bs = Cfg->ToArray(); + rt = dm.WriteData(bs, true); + dm.Start += 64; + + Cfg->Save(); + } + + auto rs = msg.CreateReply(); + if (!rt) + rs.Error = true; + else + dm.WriteMessage(rs); + + Reply(rs, ctrl); + + if (dm.Start >= 64 && dm.Start < 128) + { + debug_printf("\r\n 配置区被修改,200ms后重启\r\n"); + Sys.Sleep(200); + Sys.Reboot(); + } + + // 再解析一次,发给本地 + dm.ReadMessage(msg); + LocalSend(dm.Start, dm.Data); +} + +/******************************** 远程调用 ********************************/ + +void LinkClient::Invoke(const String& action, const Buffer& bs) +{ + TokenMessage msg(8); + + MemoryStream ms; + BinaryPair bp(ms); + bp.Set("Action", action); + ms.Write(bs); + msg.SetData(Buffer(ms.GetBuffer(), ms.Position())); + + // auto ms = msg.ToStream(); + // BinaryPair bp(ms); + // bp.Set("Action", action); + // ms.Write(bs); + Send(msg); +} + +void LinkClient::OnInvoke(const TokenMessage& msg, TokenController* ctrl) +{ + auto rs = msg.CreateReply(); + auto ms2 = rs.ToStream(); + BinaryPair rsbp(ms2); + // 考虑到结果可能比较大,允许扩容 + //auto ms = rs.ToStream(); + //ms.CanResize = true; + + MemoryStream ms; // 不能用 rs.Data 数据区 后面Set数据会先写Result 这样就破坏了数据区 + BinaryPair bp(msg.ToStream()); + + String action; + if (!bp.Get("Action", action) || !action) + { + //rs.SetError(0x01, "请求错误"); + rsbp.Set("ErrorCode", (byte)0x01); + } + else + { + // 传入参数名值对以及结果缓冲区引用,业务失败时返回false并把错误信息放在结果缓冲区 + MemoryStream result; + if (!OnInvoke(action, bp, result)) + { + if (result.Position() > 0) + { + // rs.SetError(0x03, (cstring)result.GetBuffer()); + rsbp.Set("ErrorCode", (byte)0x03); + } + else + { + //rs.SetError(0x02, "执行出错"); + rsbp.Set("ErrorCode", (byte)0x02); + } + rs.Length = ms2.Position(); + } + else + { + // 执行成功 + result.SetPosition(0); + + BinaryPair bprs(ms); + bprs.Set("Result", Buffer(result.GetBuffer(), result.Length)); + + // 数据流可能已经扩容 + rs.Data = ms.GetBuffer(); + rs.Length = ms.Position(); + } + } + + Reply(rs, ctrl); +} + +bool LinkClient::OnInvoke(const String& action, const Pair& args, Stream& result) +{ + + IDelegate* dlg = nullptr; + if (!Routes.TryGetValue(action.GetBuffer(), dlg) || !dlg) return false; + + auto inv = (InvokeHandler)dlg->Method; + + return inv(dlg->Target, args, result); +} + +void LinkClient::Register(cstring action, InvokeHandler handler, void* param) +{ + if (handler) + { + auto dlg = new IDelegate(); + dlg->Method = (void*)handler; + dlg->Target = param; + + Routes.Add(action, dlg); + } + else + { + IDelegate* dlg = nullptr; + if (Routes.TryGetValue(action, dlg)) + { + delete dlg; + + Routes.Remove(action); + } + } +} + +// 快速建立令牌客户端,注册默认Api +LinkClient* LinkClient::CreateFast(const Buffer& store) +{ + auto tc = new LinkClient(); + + // 重启 + tc->Register("Gateway/Restart", &LinkClient::InvokeRestart, tc); + // 重置 + tc->Register("Gateway/Reset", &LinkClient::InvokeReset, tc); + // 设置远程地址 + tc->Register("Gateway/SetRemote", &LinkClient::InvokeSetRemote, tc); + // 获取远程配置信息 + tc->Register("Gateway/GetRemote", &LinkClient::InvokeGetRemote, tc); + // 获取所有Invoke命令 + tc->Register("Api/All", &LinkClient::InvokeGetAllApi, tc); + + if (store.Length()) tc->Store.Data.Set(store.GetBuffer(), store.Length()); + + return tc; +} + +bool LinkClient::InvokeRestart(void * param, const Pair& args, Stream& result) +{ + + result.Write((byte)01); + debug_printf("1000ms后重启\r\n"); + // Sys.AddTask([](void * param) {Sys.Reboot(); }, nullptr, 1000, 0, "Restart"); + Sys.Reboot(1000); + + return true; +} + +bool LinkClient::InvokeReset(void * param, const Pair& args, Stream& result) +{ + BinaryPair res(result); + result.Write((byte)01); + Config::Current->RemoveAll(); + + debug_printf("1000ms后重置\r\n"); + //Sys.Sleep(500); + //Sys.Reboot(); + //Sys.AddTask([](void * param) {Sys.Reset(); }, nullptr, 1000, 0, "RestBoot"); + + Sys.Reboot(1000); + + return true; +} + +bool LinkClient::InvokeSetRemote(void * param, const Pair& args, Stream& result) +{ + BinaryPair res(result); + + String remote; + byte fixd; + // 远程地址 + if (!args.Get("remote", remote)) + { + res.Set("SetRemote", (byte)0); + return false; + } + // 是否用久跳转 + if (!args.Get("fixd", fixd)) + { + res.Set("SetRemote", (byte)0); + return false; + } + + NetUri uri(remote); + auto client = (LinkClient*)param; + debug_printf("远程地址设置\r\n"); + uri.ToString().Show(); + + // 永久改变地址 + if (fixd) + { + auto cfg = client->Cfg; + cfg->Show(); + + cfg->Protocol = uri.Type; + cfg->Server() = uri.Host; + cfg->ServerPort = uri.Port; + + cfg->Save(); + cfg->Show(); + } + + res.Set("SetRemote", (byte)1); + client->ChangeIPEndPoint(uri); + + return true; +} + +bool LinkClient::InvokeGetRemote(void * param, const Pair& args, Stream& result) +{ + debug_printf("获取远程地址\r\n"); + auto client = (LinkClient*)param; + + String remote; + + auto cfg = client->Cfg; + cfg->Show(); + + String type = cfg->Protocol == NetType::Tcp ? "Tcp://" : "Udp://"; + auto host = cfg->Server(); + auto port = cfg->ServerPort; + + remote = type + host + ":" + port; + result.Write(remote); + return true; +} + +bool LinkClient::InvokeGetAllApi(void * param, const Pair& args, Stream& result) +{ + debug_printf("获取Apis\r\n"); + auto client = (LinkClient*)param; + + String apis; + + auto& keys = client->Routes.Keys(); + for (int i = 0; i < keys.Count(); i++) + { + if (i > 0)apis += ','; + apis = apis + String(keys[i]);// keys[i]; + } + + //BinaryPair bp(result); + //bp.Set("Apis", apis); + result.Write(apis); + + return true; +} diff --git a/Link/LinkClient.h b/Link/LinkClient.h new file mode 100644 index 00000000..6d857b54 --- /dev/null +++ b/Link/LinkClient.h @@ -0,0 +1,111 @@ +#ifndef __LinkClient_H__ +#define __LinkClient_H__ + +#include "Kernel\Sys.h" + +#include "Message\DataStore.h" +#include "Message\Json.h" + +// 物联客户端 +class LinkClient +{ +public: + bool Opened; + int Status; // 状态。0准备、1握手完成、2登录后 + + UInt64 LoginTime; // 登录时间ms + UInt64 LastSend; // 最后发送时间ms + UInt64 LastActive; // 最后活跃时间ms + int Delay; // 心跳延迟。一条心跳指令从发出到收到所花费的时间 + int MaxNotActive; // 最大不活跃时间ms,超过该时间时重启系统。默认0 + + DataStore Store; // 数据存储区 + Dictionary Routes; // 路由集合 + + LinkClient(); + + void Open(); + void Close(); + + // 发送消息 + bool Send(TokenMessage& msg, TokenController* ctrl = nullptr); + bool Reply(TokenMessage& msg, TokenController* ctrl = nullptr); + void OnReceive(TokenMessage& msg, TokenController& ctrl); + void OnReceiveLocal(TokenMessage& msg, TokenController& ctrl); + void LocalSend(int start, const Buffer& bs); + + // 收到功能消息时触发 + MessageHandler Received; + void* Param; + + // 常用系统级消息 + // 握手广播 + void SayHello(bool broadcast); + + // 注册 + void Register(); + + // 登录 + void Login(); + // 重置并上报 + void Reset(const String& reason); + void Reboot(const String& reason); + + // Ping指令用于保持与对方的活动状态 + void Ping(); + + void Read(int start, int size); + void Write(int start, const Buffer& bs); + void Write(int start, byte dat); + + // 异步上传并等待响应,返回实际上传字节数 + int WriteAsync(int start, const Buffer& bs, int msTimeout); + + // 必须满足 start > 0 才可以。 + void ReportAsync(int start, uint length = 1); + + // 远程调用 + void Invoke(const String& action, const Buffer& bs); + + // 远程调用委托。传入参数名值对以及结果缓冲区引用,业务失败时返回false并把错误信息放在结果缓冲区 + typedef bool(*InvokeHandler)(void* param, const Pair& args, Stream& result); + // 注册远程调用处理器 + void Register(cstring action, InvokeHandler handler, void* param = nullptr); + // 模版支持成员函数 + template + void Register(cstring action, bool(T::*func)(const Pair&, Stream&), T* target) + { + Register(action, *(InvokeHandler*)&func, target); + } + + static LinkClient* Current; + +private: + // 跳转 + bool OnRedirect(HelloMessage& msg); + + bool OnLogin(TokenMessage& msg, TokenController* ctrl); + + bool OnPing(TokenMessage& msg, TokenController* ctrl); + + bool ChangeIPEndPoint(const NetUri& uri); + + void OnRead(const TokenMessage& msg, TokenController* ctrl); + void OnWrite(const TokenMessage& msg, TokenController* ctrl); + + void OnInvoke(const TokenMessage& msg, TokenController* ctrl); + bool OnInvoke(const String& action, const Pair& args, Stream& result); + +private: + uint _task; + void* _Expect; // 等待内容 + + int NextReport = -1; // 下次上报偏移,-1不动 + byte ReportLength; // 下次上报数据长度 + + void LoopTask(); + bool CheckReport(); + void CheckNet(); +}; + +#endif diff --git a/Tool/Build_SmartOS_M3.cs b/Tool/Build_SmartOS_M3.cs index c6af2fb0..f331b84a 100644 --- a/Tool/Build_SmartOS_M3.cs +++ b/Tool/Build_SmartOS_M3.cs @@ -17,6 +17,7 @@ build.AddFiles("..\\Test"); build.AddFiles("..\\TinyIP", "*.c;*.cpp", false, "HttpClient"); build.AddFiles("..\\TinyNet"); build.AddFiles("..\\TokenNet"); +build.AddFiles("..\\Link"); build.AlwaysBuild = "Sys.cpp"; build.Libs.Clear(); //build.Preprocess = true; diff --git a/vs/SmartOS.vcxproj b/vs/SmartOS.vcxproj index 1ff2686c..0ccdfaf2 100644 --- a/vs/SmartOS.vcxproj +++ b/vs/SmartOS.vcxproj @@ -106,6 +106,7 @@ + diff --git a/vs/SmartOS.vcxproj.filters b/vs/SmartOS.vcxproj.filters index 1c9044c6..aa8c0052 100644 --- a/vs/SmartOS.vcxproj.filters +++ b/vs/SmartOS.vcxproj.filters @@ -49,6 +49,9 @@ {5148c153-2531-43dd-b3d0-c6e56c956568} + + {f7ce12fa-0f45-4e6e-bc63-a16c0fd2ca7e} + @@ -581,5 +584,8 @@ Message + + Link + \ No newline at end of file