zoukankan      html  css  js  c++  java
  • 基于TCP协议的socket服务器

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

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

    View Code
     1 private Thread threadListen;//监听客户端连接线程
    2 private Thread[] threadDataHandler;//数据处理线程
    3 private Thread threadSessionHandler;//终端Session维护线程
    4
    5 private int threadDataHandlerNum = 1;//数据处理线程数
    6
    7 /// <summary>
    8 /// 启动服务器
    9 /// </summary>
    10 public void Start()
    11 {
    12 if (!m_serverClosed)
    13 {
    14 return;
    15 }
    16 this.m_serverClosed = true;
    17 try
    18 {
    19 if (this.dataOperater == null)
    20 {
    21 //给出数据接口未初始化的通知
    22 if (this.ServerException != null) this.ServerException(null, new EventArgsException(new Exception("服务器启动失败,数据接口未初始化!")));
    23 return;
    24 }
    25 if (this.isNeedStore)
    26 {
    27 if (!this.dataOperater.Open())
    28 {
    29 //给出数据库打开失败的通知
    30 if(this.DatabaseException !=null) this.DatabaseException(null, new EventArgsException(new Exception("服务器启动失败,数据库打开失败,请检查!")));
    31 return;
    32 }
    33 }
    34 if (!this.CreateServerSocket()) return;
    35 //启动线程
    36 this.threadListen = new Thread(new ThreadStart(this.StartServerListen));//终端监听线程
    37 this.threadListen.Start();
    38 this.threadDataHandler = new Thread[this.threadDataHandlerNum];
    39 for (int i = 0; i < this.threadDataHandlerNum;i++ )
    40 {
    41 this.threadDataHandler[i] = new Thread(new ThreadStart(this.DataHandler));//数据处理线程
    42 this.threadDataHandler[i].Start();
    43 }
    44 this.threadSessionHandler = new Thread(new ThreadStart(this.SessionHandler));//终端Session维护线程
    45 this.threadSessionHandler.Start();</p><p> m_serverClosed = false;
    46 if (this.ServerException != null) this.ServerException(null, new EventArgsException(new Exception("服务器启动成功")));
    47 }
    48 catch (Exception err)
    49 {
    50 if (this.ServerException != null) this.ServerException(null, new EventArgsException(new Exception("服务器启动失败!详细信息:" + err.Message)));
    51 }
    52 }

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

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

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

          代码如下:

         

    /// <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;//程序异常信息

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

     代码如下:

    View Code
    /// <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#这一块也是初学,网络编程也正在努力学习中,呵呵,希望这些思路能给一些和我一样的初学者提供一点点的帮助得意,那我也就很高兴了,呵呵。

               源代码在下面给出,希望有更好想法的朋友也能一起来交流学习,呵呵!

        代码下载:https://files.cnblogs.com/rookey/%E5%9F%BA%E4%BA%8ETCP%E5%8D%8F%E8%AE%AE%E7%9A%84socket%E6%9C%8D%E5%8A%A1%E5%99%A8.rar
     

  • 相关阅读:
    centos7安装supervisor
    redis4.0 cluster搭建
    网易cetus数据库中间件安装-读写分离版本
    mongodb副本集基于centos7部署
    C# 单例模式实现
    HttpWebRequest的GET和POST方法
    C#中$的用法
    判断一个表是否存在
    C# 继承的一些解释
    C# 虚方法和抽象方法
  • 原文地址:https://www.cnblogs.com/rookey/p/2336181.html
Copyright © 2011-2022 走看看