文章目录
第一部分 框架部分
NetManager
网络事件
之前写过NetManager
类,现在会丰富这个类,新加一些功能。新定义三个事件
public enum NetEvent
{
ConnectSucc = 1,
ConnectFail = 2,
Close = 3,
}
每种事件触发回调函数,类似之前的写法利用委托,之前是利用Dictionary<string,MsgListener>
,现在采用Dictionary<NetEvent,EventListener>
相应的逻辑处理,当之前的事件已经有了回调函数了,就采用+=
,这样触发回调函数的时候,会触发多个回调函数,是委托的写法。
//添加事件监听
public static void AddEventListener(NetEvent netEvent, EventListener listener){
//添加事件
if (eventListeners.ContainsKey(netEvent)){
eventListeners[netEvent] += listener;
}
//新增事件
else{
eventListeners[netEvent] = listener;
}
}
//删除事件监听
public static void RemoveEventListener(NetEvent netEvent, EventListener listener){
if (eventListeners.ContainsKey(netEvent)){
eventListeners[netEvent] -= listener;
}
}
上面只是在NetEvent
和EventListener
之间建立连接,但是并没有执行监听函数,所以当要执行回调函数的时候调用
//分发事件
private static void FireEvent(NetEvent netEvent, String err){
if(eventListeners.ContainsKey(netEvent)){
eventListeners[netEvent](err);
}
}
连接服务端
具体细分了连接的状态,未连接,正在连接,已经连接。当未连接的时候isConnecting = false
,正在连接的时候isConnecting = true
,一个参数还不能表示三个状态,socket
还自带了一个状态,当连接成功的时候socket.Connected == true
,就可以成功区分三个状态。还有一个参数设置socket.NoDelay = true;
,这个参数的意思是,在网络游戏中,如果有很多的简短的信息,每次单独发送,报头会占用太多空间,所以就等到一定大小再合并发送,但是一般实时性要求高的时候,不需要合并发送,所以把NoDelay
设置为true
。
初始化操作,首先清缓存,并且涉及到心跳机制(ping -pong),下面介绍。
最后回调的时候调用FireEvent
处理相应的事件。
//连接
public static void Connect(string ip, int port)
{
//状态判断
if(socket!=null && socket.Connected){
Debug.Log("Connect fail, already connected!");
return;
}
if(isConnecting){
Debug.Log("Connect fail, isConnecting");
return;
}
//初始化成员
InitState();
//参数设置
socket.NoDelay = true;
//Connect
isConnecting = true;
socket.BeginConnect(ip, port, ConnectCallback, socket);
}
//初始化状态
private static void InitState(){
//Socket
socket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
//接收缓冲区
readBuff = new ByteArray();
//写入队列
writeQueue = new Queue<ByteArray>();
//是否正在连接
isConnecting = false;
//是否正在关闭
isClosing = false;
//消息列表
msgList = new List<MsgBase>();
//消息列表长度
msgCount = 0;
//上一次发送PING的时间
lastPingTime = Time.time;
//上一次收到PONG的时间
lastPongTime = Time.time;
//监听PONG协议
if(!msgListeners.ContainsKey("MsgPong")){
AddMsgListener("MsgPong", OnMsgPong);
}
}
//Connect回调
private static void ConnectCallback(IAsyncResult ar){
try{
Socket socket = (Socket) ar.AsyncState;
socket.EndConnect(ar);
Debug.Log("Socket Connect Succ ");
FireEvent(NetEvent.ConnectSucc,"");
isConnecting = false;
//开始接收
socket.BeginReceive( readBuff.bytes, readBuff.writeIdx,
readBuff.remain, 0, ReceiveCallback, socket);
}
catch (SocketException ex){
Debug.Log("Socket Connect fail " + ex.ToString());
FireEvent(NetEvent.ConnectFail, ex.ToString());
isConnecting = false;
}
}
心跳机制
是在第五章提到过的机制,主要是服务端判断客户端还在不在的方法。因为在网络游戏中,服务端的socket
资源相对来说更宝贵,所以无论客户端因为什么原因,一段时间连不上服务端就要把socket释放掉。这时候就要用心跳机制。客户端每隔固定的时间(比如30s)发送PING
协议,服务端接受到PING
消息之后,会发送PONG
消息,服务端会用当前的时间与最后一次收到的PING
消息做差,一但发现时间超过某个值就会认为客户端掉线,然后释放掉对应的socket
,同时客户端这边也会判断时间,所以也会记录PONG
值,用当前的时间与最后一次收到的PONG
消息做差,一但发现时间超过某个值就会认为无法连接到服务端,就会断开连接。
//发送PING协议
private static void PingUpdate(){
//是否启用
if(!isUsePing){
return;
}
//发送PING
if(Time.time - lastPingTime > pingInterval){
MsgPing msgPing = new MsgPing();
Send(msgPing);
lastPingTime = Time.time;
}
//检测PONG时间
if(Time.time - lastPongTime > pingInterval*4){
Close();
}
}
//监听PONG协议
private static void OnMsgPong(MsgBase msgBase){
lastPongTime = Time.time;
}
关闭连接
第五章提到过的,四次挥手中对数据的处理.客户端先发送FIN
,服务端接收到FIN
之后,会发送ACK
给客户端并且会发送FIN
,自己进入等待关闭状态
,客户端收到ACK
之后会发送ACK
给服务端,自己进入等待关闭状态
,服务端收到ACK
之后,释放socket资源。但是有可能关闭之前,还有数据还没有发送,这时候要继续发送消息,同时延迟关闭。用isClosing
表示进入关闭状态,但是并不关闭,而是在SendCallback
中,发送完数据再判断isClosing
的值,并选择是否关闭。
//关闭连接
public static void Close(){
//状态判断
if(socket==null || !socket.Connected){
return;
}
if(isConnecting){
return;
}
//还有数据在发送
if(writeQueue.Count > 0){
isClosing = true;
}
//没有数据在发送
else{
socket.Close();
FireEvent(NetEvent.Close