基于TCP协议的socket服务器

 

          由于公司需要做一个网络数据服务器,接收各个客户机发过来的数据,对于什么协议的由于需求方面还没有太明确,考虑可能TCP、UDP都有可能用到;不管怎么样先把TCP的服务器做出来再说,之前也曾搜集一些相关的资料,像什么完成端口模型、重叠IO等之类的,不过发现好像都不太理想,可能是我技术没到那一块 奋斗;后来参考了网上的一个架构《可扩展多线程异步Socket服务器框架》做了类似的一个服务器,以界面的形式运行后没什么问题,不过给做成服务的形式后死活不能运行,具体原因有待探究;于是就结合这个框架的思路,自己重新写一个比较通用的类库。

         具体思路是这样的,分出三大线程,用一个线程用来监听客户端请求,称为数据监听线程,用一个线程组来处理数据,称为数据处理线程,最后用一个线程来管理服务器与客户端的会话,如超时,中断之类的,称为会话处理线程。由于数据处理占用的时间是最多的,所以这一块的处理对整个服务器的性能影响最大,因此根据不同的机器,分出不同的线程数处理数据以达到最好的效果;像单核机器你可以分出两三个线程,双核的分出8-10个都可以,这样就具有比较大的灵活性。下面是部分代码:

 

       private Thread threadListen;//监听客户端连接线程
        private Thread[] threadDataHandler;//数据处理线程
        private Thread threadSessionHandler;//终端Session维护线程
       private int threadDataHandlerNum = 1;//数据处理线程数

/// <summary>         /// 启动服务器         /// </summary>         public void Start()         {             if (!m_serverClosed)             {                 return;             }             this.m_serverClosed = true;             try             {                 if (this.dataOperater == null)                 {                     //给出数据接口未初始化的通知                     if (this.ServerException != null) this.ServerException(null, new EventArgsException(new Exception("服务器启动失败,数据接口未初始化!")));                     return;                 }                 if (this.isNeedStore)                 {                     if (!this.dataOperater.Open())                     {                         //给出数据库打开失败的通知                         if(this.DatabaseException !=null) this.DatabaseException(null, new EventArgsException(new Exception("服务器启动失败,数据库打开失败,请检查!")));                         return;                     }                 }                 if (!this.CreateServerSocket()) return;                 //启动线程                 this.threadListen = new Thread(new ThreadStart(this.StartServerListen));//终端监听线程                 this.threadListen.Start();                 this.threadDataHandler = new Thread[this.threadDataHandlerNum];                 for (int i = 0; i < this.threadDataHandlerNum;i++ )                 {                     this.threadDataHandler[i] = new Thread(new ThreadStart(this.DataHandler));//数据处理线程                     this.threadDataHandler[i].Start();                 }                 this.threadSessionHandler = new Thread(new ThreadStart(this.SessionHandler));//终端Session维护线程                 this.threadSessionHandler.Start();

                m_serverClosed = false;                 if (this.ServerException != null) this.ServerException(null, new EventArgsException(new Exception("服务器启动成功")));             }             catch (Exception err)             {                 if (this.ServerException != null) this.ServerException(null, new EventArgsException(new Exception("服务器启动失败!详细信息:" + err.Message)));             }         }

            对于数据处理线程,最关键还有一点就是加入数据缓冲区,对于接收到的数据进行简单的处理后放入数据缓冲区,那么数据处理线程都从缓冲区中取数据进行处理。

            在整个系统中还具有以下功能:

              预留数据接口,可进行数据的存储、分析、显示、终端相关参数设置(包括接收和发送缓冲区、起始结束标识字符串等)。

             代码如下:

    /// <summary>
    /// 数据操作接口,供外部程序使用,实现存储
    /// </summary>
    public interface IDataOperater
    {
        /// <summary>
        /// 打开数据库
        /// </summary>
        /// <returns></returns>
        bool Open();
        /// <summary>
        /// 执行数据库存储
        /// </summary>
        /// <returns></returns>
        bool Store(DataHandlerEventArgs e);
        /// <summary>
        /// 关闭数据库
        /// </summary>
        void Close();
        /// <summary>
        /// 分析接收到的数据并显示
        /// </summary>
        /// <param name="e"></param>
        void Display(DataHandlerEventArgs e);
        /// <summary>
        /// 设置客户端的相关参数
        /// </summary>
        /// <param name="Ip"></param>
        /// <returns></returns>
        ClientArgs ClientArgsSet(string ip);
    }



              预留数据回复接口,服务器接收到数据时,可设置是否向客户回复及回复内容。

          

    public interface IReplyToClient
    {
        /// <summary>
        /// 根据接收到的数据给出向终端回复信息
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        string GetReplyData(string ip, byte[] data);
    }



              对数据包进行接包处理,基本上能保证接收数据的完整性,实现不丢包。

             代码如下:

        /// <summary>
        /// 将有些中断的包前后合并组成完整的包
        /// </summary>
        private void ResolveSessionBuffer(byte[] receiveData)
        {
            if (receiveData == null) return;
            try
            {
                //搜索第一个起始字符和最后一个结束字符,其中间数据认为是正常数据,放入缓冲区中,起始字符前的数据丢掉
                //尾数据保留,下次分析时可用
                int headIndex = -1;
                int tailIndex = -1;
                string receiveStr = Encoding.ASCII.GetString(receiveData);
                if (lastTailData != null)//如果有尾包数据,则将其合并
                {
                    string tailStr = Encoding.ASCII.GetString(this.lastTailData);
                    string str = Encoding.ASCII.GetString(receiveData);
                    receiveStr = tailStr + str;//合并数据
                }
                //查找数据包的头尾
                if (receiveStr.Contains(this.clientArgs.M_PackageBeginStr))
                {
                    headIndex = receiveStr.IndexOf(this.clientArgs.M_PackageBeginStr);//找到头的位置
                }
                if (receiveStr.Contains(this.clientArgs.M_PackageEndStr))
                {
                    tailIndex = receiveStr.LastIndexOf(this.clientArgs.M_PackageEndStr);//找到尾的位置
                }
                
                //查找完整的数据包,放入缓冲区
                if (headIndex >-1 && tailIndex > -1 && headIndex < tailIndex)
                {
                    //取出头尾之间的数据
                    string str = receiveStr.Substring(headIndex, tailIndex - headIndex + this.clientArgs.M_PackageEndStr.Length);
                    //把尾标识用头标识替换
                    str = str.Replace(this.clientArgs.M_PackageEndStr, this.clientArgs.M_PackageBeginStr);
                    //两个头标识用一个头标识替换,全部用头标识好分割
                    str = str.Replace(this.clientArgs.M_PackageBeginStr + this.clientArgs.M_PackageBeginStr, this.clientArgs.M_PackageBeginStr);
                    //分割数据出完整的数据包
                    string[] token = str.Split(this.clientArgs.M_PackageBeginStr.ToCharArray());
                    foreach (string s in token)
                    {
                        if (s.Trim() != string.Empty)
                        {
                            byte[] tempData = Encoding.ASCII.GetBytes(s.Trim());
                            lock (this.receiveBuffer)
                            {
                                this.receiveBuffer.Enqueue(new BufferData(DateTime.Now, tempData));//将完整的数据包放入缓冲区
                            }
                        }
                    }
                    if (tailIndex < receiveStr.Length - 1)//找到尾包
                    {
                        string tailStr = receiveStr.Substring(tailIndex + this.clientArgs.M_PackageEndStr.Length, receiveStr.Length - tailIndex - this.clientArgs.M_PackageEndStr.Length);
                        this.lastTailData = Encoding.ASCII.GetBytes(tailStr);
                    }
                    else//没有尾包数据,说明数据包是完整数据包
                    {
                        this.lastTailData = null;
                    }
                }
                else//合并后的数据包还不是完整数据包,则当作尾包数据处理
                {
                    this.lastTailData = Encoding.ASCII.GetBytes(receiveStr);
                }             
            }
            catch(Exception ex)//程序异常
            {
                if(this.ProgramException !=null) this.ProgramException(null, new EventArgsProgram("ClientSession", "ResolveSessionBuffer", ex));
            }
        }



              设置服务器异常处理事件、数据异常处理事件、会话异常处理事件、程序异常处理事件。

               

       //事件成员
        public event EventHandler<EventArgsException> ServerException;//服务器异常信息
        public event EventHandler<EventArgsException> DatabaseException;//数据库异常信息
        public event EventHandler<EventArgsException> DataHandlerException;//数据异常信息
        public event EventHandler<EventArgsException> SessionException;//终端Session异常信息
        public event EventHandler<EventArgsClientLink> AcceptedClient;//已经连接上的客户端
        public event EventHandler<EventArgsClientLink> ClosedClient;//已经断开连接的客户端
        public event EventHandler<EventArgsProgram> ProgramException;//程序异常信息



              可以向指定客户端发送数据,也可向全体客户端发送数据。

               代码如下:

       /// <summary>
       /// 向所有的终端发送数据
        /// </summary>
        /// <param name="data"></param>
        public void SendDataToAllClient(string data)
        {
            lock (this.m_sessions)
            {
                foreach (ClientSession session in this.m_sessions)
                {
                    session.SendDatagram(data);
                }
            }
        }
        /// <summary>
        /// 向指定终端发送数据
        /// </summary>
        /// <param name="ip">客户端IP</param>
        /// <param name="data">数据</param>
        public void SendDataToClient(string ip, string data)
        {
            lock (this.m_sessions)
            {
                foreach (ClientSession session in this.m_sessions)
                {
                    if (session.IP == ip)
                    {
                        session.SendDatagram(data);
                        return;
                    }
                }
            }
        }


            本人C#这一块也是初学,网络编程也正在努力学习中,呵呵,希望这些思路能给一些和我一样的初学者提供一点点的帮助得意,那我也就很高兴了,呵呵。

           源代码会在http://download.csdn.net/detail/wdx888/中给出,希望有更好想法的朋友也能一起来交流学习,呵呵!

         

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值