You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

486 lines
16 KiB
C#

using HslCommunication.Core.Net;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using HslCommunication.Core;
using System.Net;
/**********************************************************************************
*
*
*
*
*
*********************************************************************************/
namespace HslCommunication.Enthernet
{
/// <summary>
/// 发布订阅服务器的类,支持按照关键字进行数据信息的订阅
/// </summary>
/// <remarks>
/// 详细的使用说明,请参照博客<a href="http://www.cnblogs.com/dathlin/p/8992315.html">http://www.cnblogs.com/dathlin/p/8992315.html</a>
/// </remarks>
/// <example>
/// 此处贴上了Demo项目的服务器配置的示例代码
/// <code lang="cs" source="TestProject\PushNetServer\FormServer.cs" region="NetPushServer" title="NetPushServer示例" />
/// </example>
public class NetPushServer : NetworkServerBase
{
#region Constructor
/// <summary>
/// 实例化一个对象
/// </summary>
public NetPushServer()
{
dictPushClients = new Dictionary<string, PushGroupClient>( );
dictSendHistory = new Dictionary<string, string>( );
dicHybirdLock = new SimpleHybirdLock( );
dicSendCacheLock = new SimpleHybirdLock( );
sendAction = new Action<AppSession, string>( SendString );
hybirdLock = new SimpleHybirdLock( );
pushClients = new List<NetPushClient>( );
}
#endregion
#region Server Override
/// <summary>
/// 当接收到了新的请求的时候执行的操作
/// </summary>
/// <param name="socket">异步对象</param>
/// <param name="endPoint">终结点</param>
protected override void ThreadPoolLogin( Socket socket, IPEndPoint endPoint )
{
// 接收一条信息,指定当前请求的数据订阅信息的关键字
OperateResult<int, string> receive = ReceiveStringContentFromSocket( socket );
if (!receive.IsSuccess) return;
// 判断当前的关键字在服务器是否有消息发布
//if(!IsPushGroupOnline(receive.Content2))
//{
// SendStringAndCheckReceive( socket, 1, StringResources.Language.KeyIsNotExist );
// LogNet?.WriteWarn( ToString( ), StringResources.Language.KeyIsNotExist );
// socket?.Close( );
// return;
//}
// 确认订阅的信息
OperateResult check = SendStringAndCheckReceive( socket, 0, "" );
if (!check.IsSuccess)
{
socket?.Close( );
return;
}
// 允许发布订阅信息
AppSession session = new AppSession
{
KeyGroup = receive.Content2,
WorkSocket = socket
};
try
{
session.IpEndPoint = (System.Net.IPEndPoint)socket.RemoteEndPoint;
session.IpAddress = session.IpEndPoint.Address.ToString( );
}
catch (Exception ex)
{
LogNet?.WriteException( ToString( ), StringResources.Language.GetClientIpaddressFailed, ex );
}
try
{
socket.BeginReceive( session.BytesHead, 0, session.BytesHead.Length, SocketFlags.None, new AsyncCallback( ReceiveCallback ), session );
}
catch (Exception ex)
{
LogNet?.WriteException( ToString( ), StringResources.Language.SocketReceiveException, ex );
return;
}
LogNet?.WriteDebug( ToString( ), string.Format( StringResources.Language.ClientOnlineInfo, session.IpEndPoint ) );
PushGroupClient push = GetPushGroupClient( receive.Content2 );
if (push != null)
{
System.Threading.Interlocked.Increment( ref onlineCount );
push.AddPushClient( session );
dicSendCacheLock.Enter( );
if (dictSendHistory.ContainsKey( receive.Content2 ))
{
if (isPushCacheAfterConnect) SendString( session, dictSendHistory[receive.Content2] );
}
dicSendCacheLock.Leave( );
}
}
/// <summary>
/// 关闭服务器的引擎
/// </summary>
public override void ServerClose( )
{
base.ServerClose( );
}
#endregion
#region Public Method
/// <summary>
/// 主动推送数据内容
/// </summary>
/// <param name="key">关键字</param>
/// <param name="content">数据内容</param>
public void PushString( string key, string content )
{
dicSendCacheLock.Enter( );
if (dictSendHistory.ContainsKey( key ))
{
dictSendHistory[key] = content;
}
else
{
dictSendHistory.Add( key, content );
}
dicSendCacheLock.Leave( );
AddPushKey( key );
GetPushGroupClient( key )?.PushString( content, sendAction );
}
/// <summary>
/// 移除关键字信息,通常应用于一些特殊临时用途的关键字
/// </summary>
/// <param name="key">关键字</param>
public void RemoveKey( string key )
{
dicHybirdLock.Enter( );
if (dictPushClients.ContainsKey( key ))
{
int count = dictPushClients[key].RemoveAllClient( );
for (int i = 0; i < count; i++)
{
System.Threading.Interlocked.Decrement( ref onlineCount );
}
}
dicHybirdLock.Leave( );
}
/// <summary>
/// 创建一个远程服务器的数据推送操作,以便推送给子客户端
/// </summary>
/// <param name="ipAddress">远程的IP地址</param>
/// <param name="port">远程的端口号</param>
/// <param name="key">订阅的关键字</param>
public OperateResult CreatePushRemote( string ipAddress, int port, string key )
{
OperateResult result;
hybirdLock.Enter( );
if (pushClients.Find( m => m.KeyWord == key ) == null)
{
NetPushClient pushClient = new NetPushClient( ipAddress, port, key );
result = pushClient.CreatePush( GetPushFromServer );
pushClients.Add( pushClient );
}
else
{
result = new OperateResult( StringResources.Language.KeyIsExistAlready );
}
hybirdLock.Leave( );
return result;
}
#endregion
#region Public Properties
/// <summary>
/// 在线客户端的数量
/// </summary>
public int OnlineCount
{
get => onlineCount;
}
/// <summary>
/// 在客户端上线之后是否推送缓存的数据默认设置为true
/// </summary>
public bool PushCacheAfterConnect
{
get { return isPushCacheAfterConnect; }
set { isPushCacheAfterConnect = value; }
}
#endregion
#region Private Method
private void ReceiveCallback( IAsyncResult ar )
{
if (ar.AsyncState is AppSession session)
{
try
{
Socket client = session.WorkSocket;
int bytesRead = client.EndReceive( ar );
// 正常下线退出
LogNet?.WriteDebug( ToString( ), string.Format( StringResources.Language.ClientOfflineInfo, session.IpEndPoint ) );
RemoveGroupOnlien( session.KeyGroup, session.ClientUniqueID );
}
catch (Exception ex)
{
if (ex.Message.Contains( StringResources.Language.SocketRemoteCloseException ))
{
// 正常下线
LogNet?.WriteDebug( ToString( ), string.Format( StringResources.Language.ClientOfflineInfo, session.IpEndPoint ) );
RemoveGroupOnlien( session.KeyGroup, session.ClientUniqueID );
}
else
{
LogNet?.WriteException( ToString( ), string.Format( StringResources.Language.ClientOfflineInfo, session.IpEndPoint ), ex );
RemoveGroupOnlien( session.KeyGroup, session.ClientUniqueID );
}
}
}
}
/// <summary>
/// 判断当前的关键字订阅是否在服务器的词典里面
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
private bool IsPushGroupOnline(string key)
{
bool result = false;
dicHybirdLock.Enter( );
if (dictPushClients.ContainsKey( key )) result = true;
dicHybirdLock.Leave( );
return result;
}
private void AddPushKey(string key)
{
dicHybirdLock.Enter( );
if (!dictPushClients.ContainsKey( key ))
{
dictPushClients.Add( key, new PushGroupClient( ) );
}
dicHybirdLock.Leave( );
}
private PushGroupClient GetPushGroupClient(string key)
{
PushGroupClient result = null;
dicHybirdLock.Enter( );
if (dictPushClients.ContainsKey( key ))
{
result = dictPushClients[key];
}
else
{
result = new PushGroupClient( );
dictPushClients.Add( key, result );
}
dicHybirdLock.Leave( );
return result;
}
/// <summary>
/// 移除客户端的数据信息
/// </summary>
/// <param name="key">指定的客户端</param>
/// <param name="clientID">指定的客户端唯一的id信息</param>
private void RemoveGroupOnlien( string key, string clientID )
{
PushGroupClient push = GetPushGroupClient( key );
if (push != null)
{
if (push.RemovePushClient( clientID ))
{
// 移除成功
System.Threading.Interlocked.Decrement( ref onlineCount );
}
}
}
private void SendString(AppSession appSession, string content)
{
System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(m =>
{
PushSendAsync(appSession, HslProtocol.CommandBytes(0, Token, content));
}), null);
}
#region Send Bytes Async
/// <summary>
/// 发送数据的方法
/// </summary>
/// <param name="session">通信用的核心对象</param>
/// <param name="content">完整的字节信息</param>
internal void PushSendAsync( AppSession session, byte[] content )
{
try
{
// 进入发送数据的锁,然后开启异步的数据发送
session.HybirdLockSend.Enter( );
// 启用另外一个网络封装对象进行发送数据
AsyncStateSend state = new AsyncStateSend( )
{
WorkSocket = session.WorkSocket,
Content = content,
AlreadySendLength = 0,
HybirdLockSend = session.HybirdLockSend,
Key = session.KeyGroup,
ClientId = session.ClientUniqueID,
};
state.WorkSocket.BeginSend(
state.Content,
state.AlreadySendLength,
state.Content.Length - state.AlreadySendLength,
SocketFlags.None,
new AsyncCallback( PushSendCallBack ),
state );
}
catch (ObjectDisposedException)
{
// 不操作
session.HybirdLockSend.Leave( );
RemoveGroupOnlien( session.KeyGroup, session.ClientUniqueID );
}
catch (Exception ex)
{
session.HybirdLockSend.Leave( );
if (!ex.Message.Contains( StringResources.Language.SocketRemoteCloseException ))
{
LogNet?.WriteException( ToString( ), StringResources.Language.SocketSendException, ex );
}
RemoveGroupOnlien( session.KeyGroup, session.ClientUniqueID );
}
}
/// <summary>
/// 发送回发方法
/// </summary>
/// <param name="ar">异步数据</param>
internal void PushSendCallBack( IAsyncResult ar )
{
if (ar.AsyncState is AsyncStateSend stateone)
{
try
{
stateone.AlreadySendLength += stateone.WorkSocket.EndSend( ar );
if (stateone.AlreadySendLength < stateone.Content.Length)
{
// 继续发送
stateone.WorkSocket.BeginSend( stateone.Content,
stateone.AlreadySendLength,
stateone.Content.Length - stateone.AlreadySendLength,
SocketFlags.None,
new AsyncCallback( PushSendCallBack ),
stateone );
}
else
{
stateone.HybirdLockSend.Leave( );
// 发送完成
stateone = null;
}
}
catch (ObjectDisposedException)
{
stateone.HybirdLockSend.Leave( );
RemoveGroupOnlien( stateone.Key, stateone.ClientId );
}
catch (Exception ex)
{
LogNet?.WriteException( ToString( ), StringResources.Language.SocketEndSendException, ex );
stateone.HybirdLockSend.Leave( );
RemoveGroupOnlien( stateone.Key, stateone.ClientId );
}
}
}
#endregion
private void GetPushFromServer( NetPushClient pushClient, string data )
{
// 推送给其他的客户端,当然也有可能是工作站
PushString( pushClient.KeyWord, data );
}
#endregion
#region Private Member
private Dictionary<string, string> dictSendHistory; // 词典缓存的数据发送对象
private Dictionary<string, PushGroupClient> dictPushClients; // 系统的数据词典
private SimpleHybirdLock dicHybirdLock; // 词典锁
private SimpleHybirdLock dicSendCacheLock; // 缓存数据的锁
private Action<AppSession, string> sendAction; // 发送数据的委托
private int onlineCount = 0; // 在线客户端的数量,用于监视显示
private List<NetPushClient> pushClients; // 客户端列表
private SimpleHybirdLock hybirdLock; // 客户端列表的锁
private bool isPushCacheAfterConnect = true; // 在客户端上线之后,是否推送缓存的数据
#endregion
#region Object Override
/// <summary>
/// 返回表示当前对象的字符串
/// </summary>
/// <returns>字符串</returns>
public override string ToString()
{
return "NetPushServer";
}
#endregion
}
}