⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 subsmgserver.cs

📁 联通的SGIP发送代码
💻 CS
📖 第 1 页 / 共 3 页
字号:
/* 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 + -