想要使用多路复用Select,就必须让服务器连接的Socket都交给Select处理。
服务器获取客户端Socket
首先,在Net文件夹新建一个ClientSocket
类:
1 2 3 4 5 6 7 8 9 10
| using System.Net.Sockets;
namespace SimpleServer.Net { public class ClientSocket { public Socket? Socket { get; set; } public long LastPingTime { get; set; } } }
|
然后,在ServerSocket
声明一个字典,用来缓存所有连接到服务器的客户端Socket
1 2 3 4
| public static Dictionary<Socket,ClientSocket> m_ClientDic = new Dictionary<Socket,ClientSocket>();
private static List<Socket> m_CheckReadList = new List<Socket>();
|
最后,修改ServerSocket
,添加服务器获取客户端的逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
| public void Init() {
if (endPoint != null ) Debug.LogInfo("服务器启动监听{0}成功",endPoint); while (true) {
ResetCheckRead();
try { Socket.Select(m_CheckReadList, null, null, 1000); } catch (Exception e) {
Debug.LogError(e); } for (global::System.Int32 i = (m_CheckReadList.Count) - (1); i >= 0; i--) { Socket s = m_CheckReadList[i]; if (s == m_ListenSocket) { ReadListen(s); } else { ReadClient(s); } }
} }
public void ResetCheckRead() { m_CheckReadList.Clear(); if(m_ListenSocket != null) m_CheckReadList.Add(m_ListenSocket); foreach (Socket socket in m_ClientDic.Keys) { m_CheckReadList.Add(socket); } }
void ReadListen(Socket listenSocket) { try { Socket client = listenSocket.Accept(); ClientSocket clientSocket = new ClientSocket(); clientSocket.Socket = client; clientSocket.LastPingTime = GetTimeStamp(); m_ClientDic.Add(client, clientSocket); EndPoint? clientEP = client.LocalEndPoint; if(clientEP != null) Debug.Log("一个客户端连接:{0},当前{1}个客户端在线!",clientEP,m_ClientDic.Count); } catch (SocketException se) { Debug.LogError("Accept fail: " + se); } } void ReadClient(Socket client) { ClientSocket clientSocket = m_ClientDic[client]; } public void CloseClient(ClientSocket client) { client.Socket?.Close(); if(client.Socket != null) m_ClientDic.Remove(client.Socket); Debug.Log("一个客户端断开连接,当前{0}个客户端在线!",m_ClientDic.Count); }
public static long GetTimeStamp() { TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0); return Convert.ToInt64(ts.TotalSeconds); }
|
当进入while循环时:首先会调用ResetCheckRead
,它会清空m_CheckReadList
并将其重置。
然后再调用Socket.Select(m_CheckReadList, null, null, 1000);
这个关键函数,它将处理重置后的m_CheckReadList
,从其中找出
进入“Readable”状态的Socket。
接下来我们倒序遍历处理后的m_CheckReadList
,如果里面的“Readable”Socket是服务器的Socket,就说明此时有客户端连接进来了,那么我们就调用ReadListen
方法,将此时和服务器连接的客户端缓存进m_ClientDic
。如果里面的“Readable”Socket是客户端的Socket,我们就需要先检查一下这个客户端Socket是否已经缓存进了m_ClientDic
里面,如果在里面,就处理客户端的数据。
我们每次while循环只能缓存一个客户端Socket,在一开始循环时,没有客户端缓存过来,并且一开始时,大部分客户端也并不是“Readable”状态(客户端并不会马上向服务器发送消息),只有服务器的Socket是“Readable”状态(因为服务器要回应大量客户端的连接请求),这样客户端的Socket就会被逐渐缓存起来了。
心跳包计算
在ServerSocket
中添加心跳包间隔时间,还有已经断开连接的客户端缓存列表(用来从客户端缓存字典中移除)
1 2 3
| public static long m_PingInterval = 30;
public static List<ClientSocket> m_TempList = new List<ClientSocket>();
|
在while循环中检测心跳包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public void Init() { while (true) { long timeNow = GetTimeStamp();
m_TempList.Clear(); foreach(ClientSocket clientSocket in m_ClientDic.Values) { if (timeNow - clientSocket.LastPingTime > m_PingInterval * 4) { Debug.Log("Ping Close" + clientSocket.Socket?.RemoteEndPoint?.ToString()); m_TempList.Add(clientSocket); } } foreach (var client in m_TempList) { CloseClient(client); } m_TempList.Clear(); } }
|