|
|
using HslCommunication.Core;
|
|
|
using HslCommunication.Core.Net;
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Linq;
|
|
|
using System.Net;
|
|
|
using System.Net.Sockets;
|
|
|
using System.Text;
|
|
|
using System.Threading;
|
|
|
|
|
|
namespace HslCommunication.Enthernet
|
|
|
{
|
|
|
/// <summary>
|
|
|
/// 高性能的异步网络服务器类,适合搭建局域网聊天程序,消息推送程序
|
|
|
/// </summary>
|
|
|
/// <remarks>
|
|
|
/// 详细的使用说明,请参照博客<a href="http://www.cnblogs.com/dathlin/p/8097897.html">http://www.cnblogs.com/dathlin/p/8097897.html</a>
|
|
|
/// </remarks>
|
|
|
/// <example>
|
|
|
/// 此处贴上了Demo项目的服务器配置的示例代码
|
|
|
/// <code lang="cs" source="TestProject\ComplexNetServer\FormServer.cs" region="NetComplexServer" title="NetComplexServer示例" />
|
|
|
/// </example>
|
|
|
public class NetComplexServer : NetworkServerBase
|
|
|
{
|
|
|
#region Constructor
|
|
|
|
|
|
/// <summary>
|
|
|
/// 实例化一个网络服务器类对象
|
|
|
/// </summary>
|
|
|
public NetComplexServer()
|
|
|
{
|
|
|
appSessions = new List<AppSession>( );
|
|
|
lockSessions = new SimpleHybirdLock( );
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Private Member
|
|
|
|
|
|
private int connectMaxClient = 1000; // 允许同时登录的最大客户端数量
|
|
|
private List<AppSession> appSessions = null; // 所有客户端连接的对象信息
|
|
|
private SimpleHybirdLock lockSessions = null; // 对象列表操作的锁
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Public Properties
|
|
|
|
|
|
/// <summary>
|
|
|
/// 所支持的同时在线客户端的最大数量,商用限制1000个,最小10个
|
|
|
/// </summary>
|
|
|
public int ConnectMax
|
|
|
{
|
|
|
get { return connectMaxClient; }
|
|
|
set
|
|
|
{
|
|
|
if (value >= 10 && value < 1001)
|
|
|
{
|
|
|
connectMaxClient = value;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 获取或设置服务器是否记录客户端上下线信息
|
|
|
/// </summary>
|
|
|
public bool IsSaveLogClientLineChange { get; set; } = true;
|
|
|
|
|
|
/// <summary>
|
|
|
/// 所有在线客户端的数量
|
|
|
/// </summary>
|
|
|
public int ClientCount => appSessions.Count;
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region NetworkServerBase Override
|
|
|
|
|
|
/// <summary>
|
|
|
/// 初始化操作
|
|
|
/// </summary>
|
|
|
protected override void StartInitialization()
|
|
|
{
|
|
|
Thread_heart_check = new Thread( new ThreadStart( ThreadHeartCheck ) )
|
|
|
{
|
|
|
IsBackground = true,
|
|
|
Priority = ThreadPriority.AboveNormal
|
|
|
};
|
|
|
Thread_heart_check.Start( );
|
|
|
base.StartInitialization( );
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 关闭网络时的操作
|
|
|
/// </summary>
|
|
|
protected override void CloseAction()
|
|
|
{
|
|
|
Thread_heart_check?.Abort( );
|
|
|
ClientOffline = null;
|
|
|
ClientOnline = null;
|
|
|
AcceptString = null;
|
|
|
AcceptByte = null;
|
|
|
|
|
|
//关闭所有的网络
|
|
|
appSessions.ForEach( m => m.WorkSocket?.Close( ) );
|
|
|
base.CloseAction( );
|
|
|
}
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// 异常下线
|
|
|
/// </summary>
|
|
|
/// <param name="session">会话信息</param>
|
|
|
/// <param name="ex">异常</param>
|
|
|
internal override void SocketReceiveException( AppSession session, Exception ex )
|
|
|
{
|
|
|
if (ex.Message.Contains( StringResources.Language.SocketRemoteCloseException ))
|
|
|
{
|
|
|
//异常掉线
|
|
|
TcpStateDownLine( session, false );
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// 正常下线
|
|
|
/// </summary>
|
|
|
/// <param name="session">会话信息</param>
|
|
|
internal override void AppSessionRemoteClose( AppSession session )
|
|
|
{
|
|
|
TcpStateDownLine( session, true );
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Client Online Offline
|
|
|
|
|
|
private void TcpStateUpLine( AppSession state )
|
|
|
{
|
|
|
lockSessions.Enter( );
|
|
|
appSessions.Add( state );
|
|
|
lockSessions.Leave( );
|
|
|
|
|
|
// 提示上线
|
|
|
ClientOnline?.Invoke( state );
|
|
|
|
|
|
AllClientsStatusChange?.Invoke( ClientCount );
|
|
|
// 是否保存上线信息
|
|
|
if (IsSaveLogClientLineChange)
|
|
|
{
|
|
|
LogNet?.WriteInfo( ToString( ), $"[{state.IpEndPoint}] Name:{ state?.LoginAlias } { StringResources.Language.NetClientOnline }" );
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void TcpStateClose( AppSession state )
|
|
|
{
|
|
|
state?.WorkSocket?.Close( );
|
|
|
}
|
|
|
|
|
|
private void TcpStateDownLine( AppSession state, bool is_regular, bool logSave = true )
|
|
|
{
|
|
|
lockSessions.Enter( );
|
|
|
bool success = appSessions.Remove( state );
|
|
|
lockSessions.Leave( );
|
|
|
|
|
|
if (!success) return;
|
|
|
// 关闭连接
|
|
|
TcpStateClose( state );
|
|
|
// 判断是否正常下线
|
|
|
string str = is_regular ? StringResources.Language.NetClientOffline : StringResources.Language.NetClientBreak;
|
|
|
ClientOffline?.Invoke( state, str );
|
|
|
AllClientsStatusChange?.Invoke( ClientCount );
|
|
|
// 是否保存上线信息
|
|
|
if (IsSaveLogClientLineChange && logSave)
|
|
|
{
|
|
|
LogNet?.WriteInfo( ToString( ), $"[{state.IpEndPoint}] Name:{ state?.LoginAlias } { str }" );
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Event Handle
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// 客户端的上下限状态变更时触发,仅作为在线客户端识别
|
|
|
/// </summary>
|
|
|
public event Action<int> AllClientsStatusChange;
|
|
|
|
|
|
/// <summary>
|
|
|
/// 当客户端上线的时候,触发此事件
|
|
|
/// </summary>
|
|
|
public event Action<AppSession> ClientOnline;
|
|
|
/// <summary>
|
|
|
/// 当客户端下线的时候,触发此事件
|
|
|
/// </summary>
|
|
|
public event Action<AppSession, string> ClientOffline;
|
|
|
/// <summary>
|
|
|
/// 当接收到文本数据的时候,触发此事件
|
|
|
/// </summary>
|
|
|
public event Action<AppSession, NetHandle, string> AcceptString;
|
|
|
/// <summary>
|
|
|
/// 当接收到字节数据的时候,触发此事件
|
|
|
/// </summary>
|
|
|
public event Action<AppSession, NetHandle, byte[]> AcceptByte;
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Login Server
|
|
|
|
|
|
/// <summary>
|
|
|
/// 当接收到了新的请求的时候执行的操作
|
|
|
/// </summary>
|
|
|
/// <param name="socket">异步对象</param>
|
|
|
/// <param name="endPoint">终结点</param>
|
|
|
protected override void ThreadPoolLogin( Socket socket, IPEndPoint endPoint )
|
|
|
{
|
|
|
// 判断连接数是否超出规定
|
|
|
if (appSessions.Count > ConnectMax)
|
|
|
{
|
|
|
socket?.Close( );
|
|
|
LogNet?.WriteWarn( ToString( ), StringResources.Language.NetClientFull );
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 接收用户别名并验证令牌
|
|
|
OperateResult result = new OperateResult( );
|
|
|
OperateResult<int, string> readResult = ReceiveStringContentFromSocket( socket );
|
|
|
if (!readResult.IsSuccess) return;
|
|
|
|
|
|
// 登录成功
|
|
|
AppSession session = new AppSession( )
|
|
|
{
|
|
|
WorkSocket = socket,
|
|
|
LoginAlias = readResult.Content2,
|
|
|
};
|
|
|
|
|
|
|
|
|
try
|
|
|
{
|
|
|
session.IpEndPoint = (IPEndPoint)socket.RemoteEndPoint;
|
|
|
session.IpAddress = ((IPEndPoint)socket.RemoteEndPoint).Address.ToString( );
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
LogNet?.WriteException( ToString( ), StringResources.Language.GetClientIpaddressFailed, ex );
|
|
|
}
|
|
|
|
|
|
if (readResult.Content1 == 1)
|
|
|
{
|
|
|
// 电脑端客户端
|
|
|
session.ClientType = "Windows";
|
|
|
}
|
|
|
else if (readResult.Content1 == 2)
|
|
|
{
|
|
|
// Android 客户端
|
|
|
session.ClientType = "Android";
|
|
|
}
|
|
|
|
|
|
|
|
|
try
|
|
|
{
|
|
|
session.WorkSocket.BeginReceive( session.BytesHead, session.AlreadyReceivedHead,
|
|
|
session.BytesHead.Length - session.AlreadyReceivedHead, SocketFlags.None,
|
|
|
new AsyncCallback( HeadBytesReceiveCallback ), session );
|
|
|
TcpStateUpLine( session );
|
|
|
Thread.Sleep( 100 );// 留下一些时间进行反应
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
// 登录前已经出错
|
|
|
TcpStateClose( session );
|
|
|
LogNet?.WriteException( ToString( ), StringResources.Language.NetClientLoginFailed, ex );
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region SendAsync Support
|
|
|
|
|
|
/// <summary>
|
|
|
/// 服务器端用于数据发送文本的方法
|
|
|
/// </summary>
|
|
|
/// <param name="session">数据发送对象</param>
|
|
|
/// <param name="customer">用户自定义的数据对象,如不需要,赋值为0</param>
|
|
|
/// <param name="str">发送的文本</param>
|
|
|
public void Send( AppSession session, NetHandle customer, string str )
|
|
|
{
|
|
|
SendBytes( session, HslProtocol.CommandBytes( customer, Token, str ) );
|
|
|
}
|
|
|
/// <summary>
|
|
|
/// 服务器端用于发送字节的方法
|
|
|
/// </summary>
|
|
|
/// <param name="session">数据发送对象</param>
|
|
|
/// <param name="customer">用户自定义的数据对象,如不需要,赋值为0</param>
|
|
|
/// <param name="bytes">实际发送的数据</param>
|
|
|
public void Send( AppSession session, NetHandle customer, byte[] bytes )
|
|
|
{
|
|
|
SendBytes( session, HslProtocol.CommandBytes( customer, Token, bytes ) );
|
|
|
}
|
|
|
|
|
|
private void SendBytes( AppSession session, byte[] content )
|
|
|
{
|
|
|
SendBytesAsync( session, content );
|
|
|
}
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// 服务端用于发送所有数据到所有的客户端
|
|
|
/// </summary>
|
|
|
/// <param name="customer">用户自定义的命令头</param>
|
|
|
/// <param name="str">需要传送的实际的数据</param>
|
|
|
public void SendAllClients( NetHandle customer, string str )
|
|
|
{
|
|
|
for (int i = 0; i < appSessions.Count; i++)
|
|
|
{
|
|
|
Send( appSessions[i], customer, str );
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 服务端用于发送所有数据到所有的客户端
|
|
|
/// </summary>
|
|
|
/// <param name="customer">用户自定义的命令头</param>
|
|
|
/// <param name="data">需要群发客户端的字节数据</param>
|
|
|
public void SendAllClients( NetHandle customer, byte[] data )
|
|
|
{
|
|
|
for (int i = 0; i < appSessions.Count; i++)
|
|
|
{
|
|
|
Send( appSessions[i], customer, data );
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// 根据客户端设置的别名进行发送消息
|
|
|
/// </summary>
|
|
|
/// <param name="Alias">客户端上线的别名</param>
|
|
|
/// <param name="customer">用户自定义的命令头</param>
|
|
|
/// <param name="str">需要传送的实际的数据</param>
|
|
|
public void SendClientByAlias( string Alias, NetHandle customer, string str )
|
|
|
{
|
|
|
for (int i = 0; i < appSessions.Count; i++)
|
|
|
{
|
|
|
if (appSessions[i].LoginAlias == Alias)
|
|
|
{
|
|
|
Send( appSessions[i], customer, str );
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// 根据客户端设置的别名进行发送消息
|
|
|
/// </summary>
|
|
|
/// <param name="Alias">客户端上线的别名</param>
|
|
|
/// <param name="customer">用户自定义的命令头</param>
|
|
|
/// <param name="data">需要传送的实际的数据</param>
|
|
|
public void SendClientByAlias( string Alias, NetHandle customer, byte[] data )
|
|
|
{
|
|
|
for (int i = 0; i < appSessions.Count; i++)
|
|
|
{
|
|
|
if (appSessions[i].LoginAlias == Alias)
|
|
|
{
|
|
|
Send( appSessions[i], customer, data );
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region DataProcessingCenter
|
|
|
|
|
|
/// <summary>
|
|
|
/// 数据处理中心
|
|
|
/// </summary>
|
|
|
/// <param name="session">会话对象</param>
|
|
|
/// <param name="protocol">消息的代码</param>
|
|
|
/// <param name="customer">用户消息</param>
|
|
|
/// <param name="content">数据内容</param>
|
|
|
internal override void DataProcessingCenter( AppSession session, int protocol, int customer, byte[] content )
|
|
|
{
|
|
|
if (protocol == HslProtocol.ProtocolCheckSecends)
|
|
|
{
|
|
|
BitConverter.GetBytes( DateTime.Now.Ticks ).CopyTo( content, 8 );
|
|
|
SendBytes( session, HslProtocol.CommandBytes( HslProtocol.ProtocolCheckSecends, customer, Token, content ) );
|
|
|
session.HeartTime = DateTime.Now;
|
|
|
}
|
|
|
else if (protocol == HslProtocol.ProtocolClientQuit)
|
|
|
{
|
|
|
TcpStateDownLine( session, true );
|
|
|
}
|
|
|
else if (protocol == HslProtocol.ProtocolUserBytes)
|
|
|
{
|
|
|
//接收到字节数据
|
|
|
AcceptByte?.Invoke( session, customer, content );
|
|
|
}
|
|
|
else if (protocol == HslProtocol.ProtocolUserString)
|
|
|
{
|
|
|
//接收到文本数据
|
|
|
string str = Encoding.Unicode.GetString( content );
|
|
|
AcceptString?.Invoke( session, customer, str );
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
// 其他一概不处理
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Heart Check
|
|
|
|
|
|
private Thread Thread_heart_check { get; set; } = null;
|
|
|
|
|
|
private void ThreadHeartCheck()
|
|
|
{
|
|
|
while (true)
|
|
|
{
|
|
|
Thread.Sleep( 2000 );
|
|
|
|
|
|
try
|
|
|
{
|
|
|
for (int i = appSessions.Count - 1; i >= 0; i--)
|
|
|
{
|
|
|
if (appSessions[i] == null)
|
|
|
{
|
|
|
appSessions.RemoveAt( i );
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
if ((DateTime.Now - appSessions[i].HeartTime).TotalSeconds > 1 * 8)//8次没有收到失去联系
|
|
|
{
|
|
|
LogNet?.WriteWarn( ToString( ), StringResources.Language.NetHeartCheckTimeout + appSessions[i].IpAddress.ToString( ) );
|
|
|
TcpStateDownLine( appSessions[i], false, false );
|
|
|
continue;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
LogNet?.WriteException( ToString( ), StringResources.Language.NetHeartCheckFailed, ex );
|
|
|
}
|
|
|
|
|
|
|
|
|
if (!IsStarted) break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Object Override
|
|
|
|
|
|
/// <summary>
|
|
|
/// 获取本对象的字符串表示形式
|
|
|
/// </summary>
|
|
|
/// <returns>字符串</returns>
|
|
|
public override string ToString()
|
|
|
{
|
|
|
return "NetComplexServer";
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
}
|
|
|
}
|