握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。断开连接时服务器和客户端均可以主动发起断开TCP连接的请求,断开过程需要经过“四次握手”(过程就不细写了,就是服务器和客户端交互,最终确定断开)
什么是心跳
刚才说到长连接建立连接后,理想状态下是不会断开的,但是由于网络问题,可能导致一方断开后,另一方仍然在发送数据,或者有些客户端长时间不发送消息,服务器还维持这他的客户端不必要的引用,增加了服务器的负荷。因此我们引入了心跳机制。
心跳包之所以叫心跳包是因为:它像心跳一样每隔固定时间发一次,以此来告诉服务器,这个客户端还活着。事实上这是为了保持长连接,至于这个包的内容,是没有什么特别规定的,不过一般都是很小的包,或者只包含包头的一个空包。
总的来说,心跳包主要也就是用于长连接的保活和断线处理。一般的应用下,判定时间在30-40秒比较不错。如果实在要求高,那就在6-9秒。
怎么发送心跳?
1:轮询机制
轮询:概括来说是服务端定时主动的与客户端通信,询问当前的某种状态,客户端返回状态信息,客户端没有返回,则认为客户端已经宕机,然后服务端把这个客户端的宕机状态保存下来,如果客户端正常,那么保存正常状态。如果客户端宕机或者返回的是定义的失效状态那么当前的客户端状态是能够及时的监控到的,如果客户端宕机之后重启了那么当服务端定时来轮询的时候,还是可以正常的获取返回信息,把其状态重新更新。2:心跳机制
心跳:最终得到的结果是与轮询一样的但是实现的方式有差别,心跳不是服务端主动去发信息检测客户端状态,而是在服务端保存下来所有客户端的状态信息,然后等待客户端定时来访问服务端,更新自己的当前状态,如果客户端超过指定的时间没有来更新状态,则认为客户端已经宕机。
心跳比起轮询有两个优势:
1.避免服务端的压力
2.灵活好控制
代码演示
服务器端
private ServerManager()
{
IPAddress address = IPAddress.Parse(IP);
listener = new TcpListener(address, Port);
//绑定ip和port,进行侦听
listener.Start();
Console.WriteLine("开始侦听");
//异步监听客户端连接
listener.BeginAcceptTcpClient(OnAccecpt,null);
//开启定时器System.Threading.Timer
Timer timer = new Timer(Callback,null, HeartInterval, HeartInterval);
//定时器System.Timers
//System.Timers.Timer t = new System.Timers.Timer();
//t.Interval = 1000;//定时器间隔
//t.Elapsed += delegate (object sender, System.Timers.ElapsedEventArgs e){ };
//t.Enabled = true;//开启定时器
}
void Callback(object state)
{
List<string> keys = new List<string>();
foreach (var item in dic.Keys)
{
keys.Add(item);
}
for (int i = 0; i < keys.Count; i++)
{
SocketClient client = dic[keys[i]];
client.SendMessage(Protocol.HeartBeat);
client.timeOut++;
if(client.timeOut>5)//4次心跳超时,服务器比客户端多判断一次
{
Console.WriteLine(client.userdata.username+ "心跳超时,断开链接");
RemoveClient(client);
}
}
}
//接收客户端消息,并进行分发
public void OnMessage(SocketClient client,int protocol,string msg)
{
switch (protocol)
{
case Protocol.Login_CMD:
break;
case Protocol.HeartBeat:
//接收到心跳,重置timeOut
client.timeOut = 0;
break;
default:
break;
}
}
客户端
//接收到服务器心跳
void OnHeatBeat()
{
lastHeartTime = Time.time;
count = 0;
//返回心跳
SendMessage(Protocol.HeartBeat);
}
float lastHeartTime = 0;
int count = 0;
void CheckHeat()
{
if(client.online && client.isloggin)
{
if (Time.time - lastHeartTime > 5)//心跳超时
{
count++;
lastHeartTime = Time.time;
Debug.Log("心跳超时一次");
if (count >= 3)//心跳超时3次
{
count = 0;
Debug.Log("心跳超时3次,断线处理");
//断线处理
client.OnClose();
}
}
}
}