📄 subsmgserver.cs
字号:
/* jll(2005)版权所有,保留所有权力
* 文件名:SubSMGServer.cs
* 用 途:作为二级sp的SMG, 它接收二级sp发送的命令,将这些命令转发给联通smg, 再将联通smg返回的应答转发给二级sp
* 作 者:jll
* 完成日期:2005年8月3日
* 修订记录:
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace JLL.SGIP
{
public abstract class ListenServer
{
protected class StateObject
{
public string LoginName;
public Socket WorkSocket;
public Timer Timer;
public byte[] Buffer;
public StateObject(string strLogin, Socket socket, TimerCallback callback)
{
LoginName = strLogin;
WorkSocket = socket;
this.Timer = new Timer(callback, this, Timeout.Infinite, Timeout.Infinite);
Buffer = new byte[SgipConfig.MaxBufferCount];
}
/// <summary>
/// 强行关闭socket和定时器, 注意这里它将会把Socket也一并关闭.
/// </summary>
public void Dispose()
{
if (WorkSocket != null)
{
SgipHelper.ShutdownAndCloseSocket(WorkSocket);
WorkSocket = null;
}
if (this.Timer != null)
{
this.Timer.Dispose();
this.Timer = null;
}
}
}
private Socket _server = null;
private IPAddress _listenIP;
private int _listenPort;
private int _answerTimeOut; //命令应答时间, 这里保存的是毫秒值
private int _idleTimeOut; //最长的空闲时间, 这里保存的是毫秒值
private UserSockets _userConnection; //用户名--所建立的socket连接
/// <summary>
/// 用于保存在等待unbind_resp命令时设置的计时器
/// </summary>
private Dictionary<StateObject, Timer> _waitUnbindRespTimer = new Dictionary<StateObject, Timer>();
/// <summary>
/// 最多的二级sp的个数, 这是个估计值, 精确值可以用SubspManager.Manager.Count来判断
/// </summary>
protected const int MaxSubspCount = 128;
/// <summary>
/// 用于转发Report命令.
/// 当收到需要状态报告的submit命令时, 记下命令序列号(及发送命令的二级sp的用户名, LoginName), 之后在收到Report命令时好查找二级sp的ip、端口, 转发给二级sp
/// </summary>
protected static TimeoutDictionary<string, string> s_reportDict = new TimeoutDictionary<string, string>(300/*5分钟检查一次*/, 300/*5分钟过期*/);
/// <summary>
/// 用于转发Trace_Resp命令, 二级sp通过mt端口发送Trace命令后, smg会通过mo端口传来trance_resp命令.
/// 要记录下Trace命令的序列号(及发送该命令的二级sp用户名, LoginName), 收到trace_resp时好查找到对应二级sp的ip地址和端口.
/// 一条trace应答最多为5分钟
/// </summary>
protected static TimeoutDictionary<string, string> s_traceDict = new TimeoutDictionary<string, string>(360/*6分钟*/, 360/*6分钟*/);
/// <summary>
///
/// </summary>
/// <param name="serverIP">所监听的IP</param>
/// <param name="serverPort">所监听的端口</param>
/// <param name="maxConnection">每一个用户所能建立的最大的连接</param>
/// <param name="anaswerTimeout">命令应答之间的最长间隔, 以秒为单位</param>
/// <param name="idleTimeout">一个连接的空闲时间, 超过这个时间没有收到这个连接的命令就主动断开这个连接</param>
public ListenServer(IPAddress listenIP, int listenPort, int maxPerUserConnection, int answerTimeout, int idleTimeout)
{
_listenIP = listenIP;
_listenPort = listenPort;
_userConnection = new UserSockets(maxPerUserConnection);
_answerTimeOut = answerTimeout * 1000;
_idleTimeOut = idleTimeout * 1000;
_waitUnbindRespTimer = new Dictionary<StateObject, Timer>();
}
/// <summary>
/// 起动服务并开始监听
/// </summary>
public void Start()
{
if (_server == null)
{
SubSPManager.BuildManager();
_server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_server.Bind(new IPEndPoint(_listenIP, _listenPort));
_server.Listen(1000);
_server.BeginAccept(new AsyncCallback(OnAcceptSocket), _server);
}
}
/// <summary>
/// 停止监听,并且释放socket
/// </summary>
public void Stop()
{
if (_server != null)
{
SgipHelper.ShutdownAndCloseSocket(_server);
_server = null;
}
}
/// <summary>
/// 判断是否为正确的登录, 即验证用户名和密码. 继承类必须实现这个方法
/// </summary>
/// <param name="client"></param>
/// <param name="strLoginName"></param>
/// <param name="strPassword"></param>
/// <returns></returns>
protected abstract bool IsValidateLogin(Socket client, Bind bindBody);
/// <summary>
/// 处理除unbind, unbind_resp之外的命令, 要注意使用obj参数的有效时间, 即不要在新线程中使用obj参数, 否则在新线程中的obj可能已改变
/// </summary>
/// <param name="obj"></param>
/// <param name="cmd"></param>
protected abstract void ProcessOtherCommand(StateObject obj, Command cmd);
/// <summary>
/// 一个socket(由obj.WorkSocket指定)所发送的命令是否已满?
/// 一个socket发送的命令需要转发到服务器, 本程序收到客户端的命令之后将存放于发送队列, 如果一个socket的发送队列满了, 则需要暂停直至发送队列有空间为此
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
protected abstract bool IsSocketQueueFull(StateObject obj);
/// <summary>
/// 接收客户端的连接
/// </summary>
/// <param name="ar"></param>
private void OnAcceptSocket(IAsyncResult ar)
{
Socket server = (Socket)ar.AsyncState;
try
{
Socket client = server.EndAccept(ar);
ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessClient), client);
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e);
}
finally
{
server.BeginAccept(new AsyncCallback(OnAcceptSocket), server);
}
}
/// <summary>
/// 用线程池中的线程来处理二级sp发过来的连接
/// </summary>
/// <param name="state"></param>
private void ProcessClient(object state)
{
Socket client = (Socket)state;
string strLoginUser = null;
if (_userConnection.IsUserSocketFull((client.RemoteEndPoint as IPEndPoint).Address) ||
(!WaitForClientLogin(client, ref strLoginUser)))
{
SgipHelper.ShutdownAndCloseSocket(client);
return;
}
ReceiveClientData(new StateObject(strLoginUser, client, new TimerCallback(OnClientWaitTooLong)));
}
/// <summary>
/// 向客户端发送一个bind_resp应答,ErrorCode指出相应的错误代码
/// </summary>
/// <param name="client"></param>
/// <param name="ErrorCode"></param>
/// <param name="bindCommand">客户端的bind命令</param>
private void SendBind_Resp(Socket client, byte ErrorCode, Command bindCommand)
{
Bind_Resp body = new Bind_Resp();
body.Result = ErrorCode;
Command cmd = Command.CreateCommand(bindCommand.Head.SequenceNumber, body);
client.Send(cmd.GetBytes());
}
/// <summary>
/// 等待客户端登录,如果超时、传的不是Bind命令、或者超出客户端所允许建立的连接数(会向客户端返回相关的应答),则返回false, 否则为true
/// </summary>
/// <param name="client"></param>
/// <param name="buffer"></param>
/// <param name="strLoginUserName">如果登录成功,返回所登录的用户名, 否则为null</param>
/// <returns></returns>
private bool WaitForClientLogin(Socket client, ref string strLoginUser)
{
strLoginUser = null;
try
{
byte[] buffer = new byte[SgipConfig.MaxBufferCount];
client.ReceiveTimeout = _idleTimeOut;
client.Receive(buffer);
Command cmd = Command.ToCommand(buffer, 0);
if (cmd.Head.CommandID != Command.SGIP_BIND)
{
return false;
}
Bind body = (Bind)cmd.Body;
if (!IsValidateLogin(client, body))
{
SendBind_Resp(client, Command.ERR_InvalidLogin, cmd);
return false;
}
//判断是否超过了连接数量限制
if (_userConnection.IsUserSocketFull(body.LoginName))
{
SendBind_Resp(client, Command.ERR_TooMuchLink, cmd);
return false;
}
_userConnection.Login(body.LoginName, client);
strLoginUser = body.LoginName;
SendBind_Resp(client, Command.ERR_Success, cmd);
return true;
}catch{
return false;
}
}
/// <summary>
/// 客户端发送Unbind命令要断开连接, 则返回Unbind_Resp应答,并断开连接
/// </summary>
/// <param name="client"></param>
/// <param name="unbindCommand"></param>
private void ProcessUnbindCommand(StateObject obj, Command cmd)
{
_userConnection.Logout(obj.LoginName, obj.WorkSocket);
try
{
obj.WorkSocket.Send(Command.CreateCommand(cmd.Head.SequenceNumber, new Unbind_Resp()).GetBytes());
}catch (Exception e){
System.Diagnostics.Debug.WriteLine(e);
}
finally
{
obj.Dispose();
}
}
/// <summary>
/// 在等待客户端unbind_resp命令时超时,或接收到unbind_resp命令,断开连接
/// </summary>
/// <param name="state"></param>
private void ProcessUnbind_respCommand(object state)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -