多路复用Select技术
多路复用:就是同时处理多路信号。比如同时检测多个Socket的状态。
微软官方文档,Select的原型如下:
public static void Select(
IList checkRead;
IList checkWrite;
IList checkError;
int microSeconds;
)
说实话官方文档都是英文翻译的看着很别扭。Select的主要功能是在microSeconds秒内检测列表,三个参数分别对应三种检测类型。第一个参数传入会检测是否可读(是否有消息发过来),第二个检测是否可写(是否可以发消息过去),第三个检测是否出错(如断开连接之类的)。
至于这个时间嘛,我觉得它内部的实现并没有那么简单。比如仅检测可读时,假如设置超时时间10s,那么如果一直没有检测到可读就会阻塞程序10s。但是如果在10s内检测到可读,可能不会第一时间返回(不然永远只能返回一个可读的Socket),我估计会再检测一遍其他Socket状态是否可读,然后返回。
其实也没必要在意那么多,程序本身运行速度就非常快,说不定早点结束返回再过0.1s又运行一次Select函数,所以没必要纠结那么多,本身网络游戏需要响应迅速,比如我们需要接收信息,那也只是在一定范围内(比如0.05s内检测),有就收没有就算了反正下一次也很快。当设置microSeconds为-1时无限超时直到有Socket可读,设置其为0表示检测一遍所有Socket不等待(不阻塞)。
服务端程序
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
namespace Solve.Properties
{
public class ClientState
{
public Socket socket;
public byte[] readBuff = new byte[1024];
}
class MainClass
{
//监听Socket listenfd
private static Socket listenfd;
//连接服务端的客户端列表(使用字典是为了方便查找)
private static Dictionary<Socket, ClientState> clients =
new Dictionary<Socket, ClientState>();
public static void Main(string[] args)
{
//创建监听Socket
listenfd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//注册Bind
IPAddress ipAdr = IPAddress.Parse("192.168.43.203");
IPEndPoint ipEp = new IPEndPoint(ipAdr, 8888);
listenfd.Bind(ipEp);
//设置并开始监听
listenfd.Listen(0);
Console.WriteLine("[服务器]启动成功");
//传入回调函数的状态信息是:监听Socket
List<Socket> checkRead = new List<Socket>();
while (true)
{
checkRead.Clear();
checkRead.Add(listenfd);
foreach (ClientState s in clients.Values)
{
checkRead.Add(s.socket);
}
Socket.Select(checkRead, null, null, 1000);
foreach(Socket s in checkRead)
{
if (s == listenfd)
ReadListenfd(s);
else
ReadClientfd(s);
}
}
}
public static void ReadListenfd(Socket listenfd)
{
Console.WriteLine("Accept");
Socket clientfd = listenfd.Accept();
ClientState state = new ClientState();
state.socket = clientfd;
clients.Add(clientfd, state);
}
public static bool ReadClientfd(Socket clientfd)
{
ClientState state = clients[clientfd];
int count = 0;
try
{
count = clientfd.Receive(state.readBuff);
}catch(SocketException ex)
{
clientfd.Close();
clients.Remove(clientfd);
Console.WriteLine("Receive SocketExeception"+ex.ToString());
return false;
}
if(count == 0)
{
clientfd.Close();
clients.Remove(clientfd);
Console.WriteLine("Socket Close");
return false;
}
string recvStr = System.Text.Encoding.Default.GetString(state.readBuff,0,count);
Console.WriteLine("Receive"+recvStr);
string sendStr = clientfd.RemoteEndPoint.ToString() + ":" + recvStr;
byte[] sendBytes = System.Text.Encoding.Default.GetBytes(sendStr);
foreach(ClientState cs in clients.Values)
{
cs.socket.Send(sendBytes);
}
return true;
}
}
}