Unity3D C# Socket通信详解之连接池
上一篇文章Unity3D C# Socket通信详解之基础介绍中,我们介绍了Socket通信的基本知识,文中给的demo有几个需要优化的地方,第一个就是需要使用连接池来处理客户端的连接。这里就来填这个坑。
1 原理
连接池的原理很简单,就是程序刚开始运行的时候,先实例化一个连接的数组,然后有客户端连接上来的时候就把这个连接分配给客户端。这样就避免了每次有客户端连接都需要去重新new一个连接对象(例子中的Conn类),因为new一个对象需要去申请和分配内存,比较耗时(尽管是很短很短的时间)。
连接池依然是用”空间换时间”,使用这种思想的地方有很多很多,比如游戏中的对象池,Unity3D中的LOD、MipMap等等,如果我们的程序需要频繁实例化相同的类,不妨考虑使用“池”的思想来优化代码。
2 例子
原理这么简单,那就废话少说,直接上码了。
Conn类,每次有新客户端连接就从连接池中取出一个Conn对象,里面存放了客户端对应的Socket和读取数据的缓存数组。
class Conn
{
public const int BufferSize = 1024;
private Socket m_Socket;
private bool m_IsUse;
private byte[] m_ReadBuff;
private int m_BuffCount = 0;
public Socket Socket => m_Socket;
public bool IsUse => m_IsUse;
public byte[] ReadBuff => m_ReadBuff;
public Conn()
{
m_ReadBuff = new byte[BufferSize];
}
public void Init(Socket socket)
{
m_Socket = socket;
m_IsUse = true;
}
public string GetAddress()
{
if (!m_IsUse)
{
return "无法获取地址";
}
return m_Socket.RemoteEndPoint.ToString();
}
public void Close()
{
if (!m_IsUse) return;
Console.WriteLine($"[ 断开连接 ] {GetAddress()}");
m_Socket.Close();
m_IsUse = false;
}
}
服务端,这里是收到什么就向客户端回复什么,如果有什么不清楚可参考Unity3D C# Socket通信详解之基础介绍。
class Serv
{
private const int MaxConn = 50;
private Socket m_ListenSocket;
private Conn[] m_Conns;
public int NewIndex()
{
if (m_Conns == null)
{
return -1;
}
for (int i = 0; i < m_Conns.Length; i++)
{
if (m_Conns[i] == null)
{
m_Conns[i] = new Conn();
return i;
}
if (!m_Conns[i].IsUse)
{
return i;
}
}
return -1;
}
public void Start(string host, int port)
{
m_Conns = new Conn[MaxConn];
for (int i = 0; i < MaxConn; i++)
{
m_Conns[i] = new Conn();
}
m_ListenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ipAddress = IPAddress.Parse(host);
IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, port);
m_ListenSocket.Bind(ipEndPoint);
m_ListenSocket.Listen(MaxConn);
m_ListenSocket.BeginAccept(AcceptCb, m_ListenSocket);
Console.WriteLine("服务器启动...");
}
private void AcceptCb(IAsyncResult ar)
{
try
{
Socket socket = m_ListenSocket.EndAccept(ar);
int index = NewIndex();
if (index < 0)
{
socket.Close();
Console.WriteLine("[ 警告 ] 连接已满");
}
else
{
Conn conn = m_Conns[index];
conn.Init(socket);
string addr = conn.GetAddress();
Console.WriteLine("客户端连接 [ {addr} ]");
// 开始接收数据
conn.Socket.BeginReceive(conn.ReadBuff, 0, Conn.BufferSize, SocketFlags.None, ReceiveCb, conn);
}
// 继续等待其他连接
m_ListenSocket.BeginAccept(AcceptCb, m_ListenSocket);
}
catch (Exception e)
{
Console.WriteLine("AcceptCb 失败: {e.Message}");
}
}
private void ReceiveCb(IAsyncResult ar)
{
Conn conn = (Conn)ar.AsyncState;
try
{
int cnt = conn.Socket.EndReceive(ar);
if (cnt <= 0)
{
Console.WriteLine("收到 [ {conn.GetAddress()} ] 断开连接");
conn.Close();
return;
}
string msg = Encoding.Default.GetString(conn.ReadBuff, 0, cnt);
Console.WriteLine("收到 [ {conn.GetAddress()} ] 数据: {msg}");
// 收到什么回复什么
Send(conn, msg);
// 继续接收
conn.Socket.BeginReceive(conn.ReadBuff, 0, Conn.BufferSize, SocketFlags.None, ReceiveCb, conn);
}
catch (Exception e)
{
Console.WriteLine("收到 [ {conn.GetAddress()} ] 断开连接");
conn.Socket.Close();
}
}
private void Send(Conn conn, string msg)
{
Socket socket = conn.Socket;
byte[] msgBytes = Encoding.Default.GetBytes(msg);
socket.BeginSend(msgBytes, 0, msgBytes.Length, SocketFlags.None, SendCb, conn);
}
private void SendCb(IAsyncResult ar)
{
Conn conn = (Conn) ar.AsyncState;
Socket socket = conn.Socket;
try
{
socket.EndSend(ar);
}
catch (Exception e)
{
Console.WriteLine("收到 [ {conn.GetAddress()} ] 断开连接");
conn.Close();
}
}
}
项目工程放到这儿了。提取码:dtry
ps:项目中的vs是vs2017,低版本的vs有些语法不支持会报错。
博主个人博客本文链接。