Unity网络游戏编程学习(四)
学习《Unity3D网络游戏实战》(第二版)时的学习笔记
上一篇: Unity网络游戏编程学习(三)
多路复用Select
多路复用就是同时处理多路信号,比如同时检测多个Socket的状态,可以用这个方法解决Poll服务端中CPU占用率过高的问题。
Select可以确定一个或多个Socket对象的状态。使用它是,需要先将一个或多个套接字放入IList中。再调用Select后,Select将修改IList列表,仅保留那些满足条件的套接字。当没有任何可读的Socket时,程序将会阻塞,不占用CPU资源。
示例程序
class ClientState
{
public Socket socket;
public byte[] recvBuffer = new byte[1024];
public string recvStr = "";
}
服务端使用主循环结构while(true){ },不断调用Select检测Socket状态,步骤如下:
- 将监听Socket(listenfd)和客户端Socket(遍历clients列表)添加到带检测Socket可读状态的列表checkList中。
- 调用Select,程序中设置超时时间为1秒,若一秒内没有任何可读信息,Select将checkList列表变为空列表,然后返回。
- 对SelectSelect处理后的每个Socket做处理,如果监听Socket(listenfd)可读,说明有客户端连接,需要调用Accep。如果客户端Socket可读,说明客户端发送了消息(或关闭),将消息广播给所有的客户端。
class MainClass
{
static byte[] sendBuffer = new byte[1024];
static string sendStr = "";
static Socket listenfd;
static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();
static void Main(string[] args)
{
//Socket
listenfd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//Bind
IPAddress ipAdr = IPAddress.Parse("127.0.0.1");
IPEndPoint iPEndPoint = new IPEndPoint(ipAdr, 8888);
listenfd.Bind(iPEndPoint);
//Listen
listenfd.Listen(0);
Console.WriteLine("[服务器启动成功]");
//定义要监听的Socket链表
List<Socket> checkRead = new List<Socket>();
while (true)
{
//填充checkRead列表
checkRead.Clear();
checkRead.Add(listenfd);
foreach(ClientState cs in clients.Values)
{
checkRead.Add(cs.socket);
}
//Select,多路复用,同时检测多个Socket的状态
Socket.Select(checkRead, null, null, 1000);
//检查可读对象
foreach (Socket so in checkRead)
{
if (so == listenfd)
{
ReadListenfd(so);
}
else
{
ReadClientfd(so);
}
}
}
}
/// <summary>
/// 读取listenfd,开始Accept
/// </summary>
/// <param name="listenfd"></param>
public static void ReadListenfd(Socket listenfd)
{
Console.WriteLine("有一个客户端接入");
Socket clientfd = listenfd.Accept();
ClientState clientState = new ClientState();
clientState.socket = clientfd;
clients.Add(clientfd, clientState);
}
public static bool ReadClientfd(Socket clientfd)
{
ClientState clientState = clients[clientfd];
//接收
int count = 0;
try
{
count = clientfd.Receive(clientState.recvBuffer);
}
catch (SocketException ex)
{
clientfd.Close();
Console.WriteLine("Receive SocketException " + ex.ToString());
return false;
}
//客户端关闭
if (count == 0)
{
clientfd.Close();
clients.Remove(clientfd);
Console.WriteLine("有一个客户端断开连接");
return false;
}
clientState.recvStr = Encoding.UTF8.GetString(clientState.recvBuffer, 0, count);
Console.WriteLine("Receive" + clientState.recvStr);
//远端IP+接收到的内容
sendStr = clientfd.RemoteEndPoint.ToString() + ":" + clientState.recvStr;
sendBuffer = Encoding.UTF8.GetBytes(sendStr);
foreach (ClientState cs in clients.Values)
{
cs.socket.Send(sendBuffer);
}
return true;
}
}