一、关于Socket网络通讯,如果我们要同时监听多个客户端进行接收和发送消息时,直接使用Socket通讯的话就可能会出现其中一个Socket接收不到客户端发来的消息而阻塞而导致其他Socket阻塞,这就需要启用多个线程来监听每一个Socket,这样就会占用很多资源。为了解决这种问题,我们就要用到Select多路复用了,它能够同时监听多个Socket,如果一个Socket阻塞,其他Socket也能够正常运行,从而可以在一个线程中监听所有的Socket。
二、Select在C#中的定义:
public static void Select(checkRead, checkWrite, checkError, microSeconds);
其中checkPead(检查可读Socket),checkWrite(检查可写Socket),checkError (检查异常Socket)都是List类型的,可以存放多个Socket。
microSeconds表示为等待时间。
为null时:一直阻塞直到前面三个发生变化时打开。
为0时:不进行阻塞一直处于到打开状态。
为自定义值时:等待自定义值过后打开。
三、Select的用法
这里只演示Select在服务端监听Socket的用法。
服务器演示代码:
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace UnityServer
{
internal class Program
{
//创建字典用于存储负责与客户端通讯的Socket;
static Dictionary<Socket,ClientDate> dicCliSocket = new Dictionary<Socket,ClientDate>();
static void Main(string[] args)
{
//创建服务器负责监听的Socket
Socket serSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//绑定IP地址和端口号
IPEndPoint Point = new IPEndPoint(IPAddress.Parse("192.168.1.217"), 55555);
//绑定
serSocket.Bind(Point);
//设置监听数量 0为无限个
serSocket.Listen(0);
Console.WriteLine("服务器创建成功");
//创建list列表用于存储可读Socket
List<Socket> cliSocketRead = new List<Socket>();
//死循环实时监听客户端发送数据进行处理
while (true)
{
//清空可读列表
cliSocketRead.Clear();
//将服务器Socket添加到列表中用于监听客户端Socket
cliSocketRead.Add(serSocket);
//将字典中可读的Socket添加的list列表中
foreach (ClientDate date in dicCliSocket.Values)
{
cliSocketRead.Add(date.socket);
}
//创建Select用于监听可读列表中的Socket
Socket.Select(cliSocketRead, null, null, 10000);
//遍历可读列表中的Socket
foreach(Socket socket in cliSocketRead)
{
//如果列表中的Socket是服务器中的Scoket,则接收客户端Socket
if(socket == serSocket)
{
AcceptClientScoket(socket);
}
//如果列表中的Socket是服务器与客户端通讯的Socket,则接收数据并处理
else
{
SendMessage(socket);
}
}
}
}
private static void SendMessage(Socket cliSocket)
{
//将客户端与服务器通讯的Socket进行存储
ClientDate date = dicCliSocket[cliSocket];
try
{
//接收客户端发来的数据
int count = date.socket.Receive(date.readBuff);
}
//客户端断开连接
catch
{
//关闭服务器关闭与客户端通讯的Socket
cliSocket.Close();
//可读列表移除与客户端通讯的Socket
dicCliSocket.Remove(cliSocket);
Console.WriteLine("客户端断开连接");
}
//将接收到的数据进行转换
string receiveMessage = Encoding.UTF8.GetString(date.readBuff);
//服务器提示收到的客户端数据
Console.WriteLine("客户端:" + receiveMessage);
//将收到的客户端数据处理(转换)
byte[] buffer = Encoding.UTF8.GetBytes(receiveMessage);
//通过可读列表中的所有Socket向客户端发送数据
foreach (ClientDate clientDate in dicCliSocket.Values)
{
clientDate.socket.Send(buffer);
}
}
private static void AcceptClientScoket(Socket serSocket)
{
//向连接服务器的客户端分配负责通讯的Socket
Socket cliSocket = serSocket.Accept();
//服务器提示客户端链接服务器
Console.WriteLine(cliSocket.RemoteEndPoint.ToString() + "连接服务器");
//将服务器与客户端通讯的Socket放到字典中
ClientDate date = new ClientDate();
date.socket = cliSocket;
dicCliSocket.Add(cliSocket, date);
}
//创建类存储用户数据
public class ClientDate
{
public Socket socket;
public byte[] readBuff = new byte[1024];
}
}
}