有一些现成的Socket服务端可以供我们使用,例如SuperSocket、FastSocket,甚至是DotNetty。DotNetty是很庞大的,另外两个会小一些。但如果我们并不是要做一个多强大的服务器,只是想简单收发一下数据,能自动重连,能稳定运行就够,那我们不必使用上面的第三方库。
最基础的服务端模板
下面的代码实现了最基础的服务端功能:
ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
ServerSocket.Bind(IP,端口);
ServerSocket.Listen(20);
ServerSocket.BeginAccept(AcceptCallback, ServerSocket);
private void AcceptCallback(IAsyncResult asyncresult)
{
//处理客户端连接
}
但是在实际的使用过程中,会出现各种问题,主要就是网络中断之后发送、接收、连接会出现的异常。只要我们把这些异常处理好,Socket Server就能稳定运行。
创建、绑定和侦听
Socket的创建、绑定和侦听,每一步都有可能抛出异常。但是对于一个正常的网络来说,上述步骤出现异常是非常罕见的。而且一旦出现异常,说明计算机的网络存在问题,要么是硬件,要么是网络配置,这些都需要在程序的外部去处理,不是重启程序多试几次就能解决的。所以对于上述步骤,如果出现异常,可以直接停止程序,让用户处理好网络问题之后再去启动。
客户端管理
我们在AcceptCallback函数里,可以得到客户端对象。如果要跟客户端通信,必须依靠这些对象。如果不是用一次丢一次,就需要把这些对象保存起来。在Accept的时候添加客户端,在Close的时候删除所有客户端,在收发数据的过程中,判断客户端是否仍连接,把已经没有连接的客户端删除。
由于对客户端的处理是一个异步操作,有必要处理一些线程冲突问题。
(1)可以使用ConcurrentBag这些线程安全容器去存放客户端。
(2)或者使用List这些线程不安全的容器存放,但是在操作的前后加上线程锁:
Monitor.Enter(LockObject);
//操作
Monitor.Exit(LockObject);
关闭
对于服务端的关闭,要完成以下工作:
(1)关闭所有连接的客户端。
for (int i = 0; i < ClientSockets.Count; i++)
{
try
{
ClientSockets[i].Close();
}
catch (Exception ex)
{
//处理异常
}
}
ClientSockets.Clear();
(2)如果服务端Socket是连接着的,需要先断开连接再关闭。由于每一步都会抛出异常,有必要保存每一步都会执行到。
try
{
if (ServerSocket.Connected)
{
ServerSocket.Disconnect(false);
}
}
catch (Exception ex)
{
//
}
try
{
ServerSocket.Close();
}
catch (Exception ex)
{
//
}
finally
{
ServerSocket = null;
}
回调函数中处理服务端关闭
采用异步接受客户端连接和接收客户端的数据,回调函数分别是AcceptCallback和ReceiveCallback。除了正常的接收外,在服务端关闭时,这两个函数也会被调用。这时候如果访问服务端,就会抛出异常。
所以在调用Socket前,可以先判断Socket是否已经关闭了。没有一个属性指明Socket关闭,可以调用一个属性抛出异常的方法判断。
try
{
ServerSocket.Available;
}
catch
{
//Socket关闭了
}
自动重连
由于服务端固定使用一个端口,反复重连会导致连接不上。所以在重连时,需要等待一段时间,让系统回收一些资源。实际上,除了用户主动关闭服务端,服务端关闭是很罕见的。那么,怎么监测服务端被动关闭呢?
我们在上面已经提到,服务端被关闭时,AcceptCallback和ReceiveCallback都会被调用,我们可以在AcceptCallback函数中完成对服务端的重启。重启时需要注意先把Socket关闭,等一段时间,再去开。