zoukankan      html  css  js  c++  java
  • 高性能、高可用性Socket通讯库介绍

    前言 本人从事编程开发十余年,因为工作关系,很早就接触socket通讯编程。常言道:人在压力下,才可能出非凡的成果。我从事的几个项目都涉及到通讯,为我研究通讯提供了平台,也带来了动力。处理socket通讯对初学者而言,具有很大的挑战性。我有个梦想:能不能开发一套系统,能很好的实现性能和易用性的统一。高性能socket采用iocp(完成端口)是唯一选择。iocp像一匹烈马,虽然性能优良,但不宜驯服。本套系统为这匹烈马套上了枷锁,让他变得温顺;但是,当你需要他时,又能迸发出强劲的动力。本文就介绍该系统如何实现易用性和高性能的统一。

      此库的特点:高性能与易用性完美统一;全部自主编码,反复测试,尽最大程度做到了bug free。 

      基于本文介绍的网络模块,开发的文件快速传输系统:

      如果你的系统需要高性能网络通信,可以联系我。根据你的系统特点定制开发。

      点击下载执行程序    或加QQ群:877371250.

    系统简介

    1 系统采用c#,可以在.net core平台编译通过。所以可运行在windows、linux平台。
    2 系统有两个模块组成IocpCore,EasyNetMessage。IocpCore对完成端口进行了封装,EasyNetMessage在IocpCore基础上进一步封装,实现了易用性。可在EasyNetMessage基础上,进一步扩展,实现分布式系统(类似WCF)。
    3 系统只实现了TCP通讯,秉承simple is best的理念,不为过于冗余的功能干扰。
    4 系统突出专业性(professional)。为了测试稳定性,开发了专门的测试程序,反复对系统蹂躏,检验系统的稳定性。为了测试性能,做了精确计时,检验每个功能点的效率。
    网上也有很多第三方网络库,好像没有必要再另起炉灶。但这些库大部分无法满足专业性、易用性要求。通过对系统API封装,可以完全了解底层特性,由于所有代码都是自己亲自编写,做到了心中有数,对所有代码了然于心。即使系统出现bug,也可以很快解决。

    性能指标
    Iocp是可扩展性通讯模型,就是不随着连接数增加而导致性能下降。所支持的连接数只与平台硬件有关。本系统保守估计可以支持10万个连接。普通平台下,可以满足千兆网传输需求。

    设计思路
      如果网络库可以用到各种场景,所处理的逻辑必须与业务无关。系统采用分层处理,底层处理字节流的收发,完全与业务无关。底层的目标就是收发速度足够快。再上一层,就是对完整的数据包处理,处理的关键是如何将数据流分割成完整的数据包。再向上就是应用层,将收到的数据包转换成类,上层只需对c#类处理,不用关心底层细节。

    IocpCore 模块介绍
    本模块对iocp封装,充分挖掘iocp的潜质;可以处理字节流也可以处理一个完整的包。
    对外接口:

     public class SocketEventParam
        {
            public EN_SocketEvent SocketEvent;
            public SocketClientInfo ClientInfo;
    
            public Socket Socket;
            public byte[] Data { get; set; }
    
            public SocketEventParam(EN_SocketEvent socketEvent, Socket socket)
            {
                SocketEvent = socketEvent;
                Socket = socket;
            }
        }
     public enum EN_SocketEvent
        {
            connect,
            accept,
            close,
            read,
            send,
            packetLenError
        }

    程序接口非常简单就只有一个类。这个类对socket事件做了封装,就是告诉你socket 连接、关闭、读取这些事件。不需要关心任何底层的细节,所以使用起来非常简单。

    使用举例

      NetServer _netServer;
      _netServer = new NetServer(this, 100);
      _netServer.OnSocketPacketEvent += SocketPacketEvent;
      _netServer.AddListenPort(5668, 1);
      
      private void DealPacket(SocketEventParam socketParam)
        {
            if (socketParam.SocketEvent == EN_SocketEvent.read)
            {        
            }
            else if (socketParam.SocketEvent == EN_SocketEvent.accept)
            {         
            }
            else if (socketParam.SocketEvent == EN_SocketEvent.close)
            {   
            }
        }
        

    内部处理及优化说明

    1 可以应对突发大数据量连接
        每秒可以起送应对几千个客户端连接。接收对方监听采用AcceptAsync,也是异步操作。有单独的线程负责处理Accept。

         int MaxAcceptInPool = 20;
            private void DealNewAccept()
            {
                try
                {
                    if (_acceptAsyncCount <= MaxAcceptInPool)
                    {
                        StartAccept();
                    }
    
                    while (true)
                    {
                        AsyncSocketClient client = _newSocketClientList.GetObj();
                        if (client == null)
                            break;
    
                        DealNewAccept(client);
                    }
                }
                catch (Exception ex)
                {
                    _log.LogException(0, "DealNewAccept 异常", ex);
                }
            }

    线程会同时投递多个AcceptAsync,就是已经建立好多个socket,等待客户端连接。当客户端到达时,可以迅速生成可用socket。

    2 接收优化

    当收到接收完成消息后,立即投递下一次接收操作,再处理接收的数据。这样可以提高数据处理的实时性。

         private void ReceiveEventArgs_Completed(object sender, SocketAsyncEventArgs readArgs)
            {
                try
                {
                    bool readError = false;
                    lock (_readLock)
                    {
                        _inReadPending = false;
    
                        if (readArgs.BytesTransferred > 0
                                && readArgs.SocketError == SocketError.Success)
                        {
                          //加入到缓冲中
                            AddToReadList(readArgs.BufferList, readArgs.BytesTransferred);
                            readArgs.BufferList = null;
                        }
                        else
                        {
                            readError = true;
                        }
                    }
    
                    if (IsSocketError || readError)
                    {
                        OnReadError();
                    }
                    else
                    {
                        TryReadData();
                    }
                }
                catch (Exception ex)
                {
                    _log.LogException(0, "ReceiveEventArgs_Completed", ex);
                }
            }
            
             internal int TryReadData()
            {
                int readCount = 0;
                while (true)
                {
                    EN_SocketReadResult result = ReadNextData();
                    if (result != EN_SocketReadResult.ReadError)
                        readCount++;
    
                    if (result == EN_SocketReadResult.HaveRead)
                        continue;
                    else
                    {
                        break;
                    }
                }
                ProcessReadData();
                return readCount;
            }

     3 发送优化

     发送时,将数据先放到发送缓冲。在对多个可发送数据,一次性发送。SocketAsyncEventArgs类中有属性public IList<ArraySegment<byte>> BufferList { get; set; },可以将多个发送buffer放入该列表,一次性发送走。

     EasyNetMessage模块介绍

    1 对外接口

     public enum EasyNetEvent
        {
            connect,
            accept,
            close,
            read,
            send,
            connectError = 100,
        }
    
        public class EasyNetParam
        {
            public EasyNetEvent NetEvent { get; set; }
            public SocketClientInfo ClientInfo { get; set; }
            public Socket Socket { get; set; }
            public NetPacket Packet { get; set; }
        }

      这个接口和IocpCore有些类似。主要的区别是 public NetPacket Packet { get; set; }。NetPacket包含的不再是字节流,而是封装好的类。用户不必再处理容易出错的字节流。当然,客户端和服务器都必须使用EasyNetMessage才可以。

    NetPacket使用说明

     客户端和服务器之间传输的是NetPacket类,完全忽略底层细节。客户端构造一个NetPacket,在服务端会收到一个完全一样的NetPacket。以发送文件为例:

       --->发送端
        NetPacket  netPacket = new NetPacket();
          netPacket.AddInt("packetType", 1);        //包类型
          netPacket.AddString("fileName", fileName);//文件名字
          netPacket.AddInt("sendIndex", fileIndex); //文件块序列号
          netPacket.AddInt("sendOver", 0);          //是否发送完标志
    
          netPacket.AddBuffer("fileData", readData);//文件数据
          <---接收端
           private void DealFileRcv(NetPacket netPacket)
            {
                FileRcvInfo info = new FileRcvInfo();
                info.IsSendOver = netPacket.GetInt("sendOver") == 1;
                info.FileName = netPacket.GetString("fileName");
                info.SendIndex = netPacket.GetInt("sendIndex").Value;
                info.FileData = netPacket.GetBuffer("fileData");
               }
            }

     以上只是使用NetPacket一个简单的例子。使用EasyNetMessage,短时间内可以开发出一个高性能的文件传输系统。

     NetPacket详细定义

      public class NetPacket
        {
            public NetPacket();
    
            public List<NetValuePair> Items { get; set; }
            public int Param1 { get; set; }
            public int PacketType { get; set; }
            public int Param2 { get; set; }
    
            public void AddBuffer(string key, byte[] value);
            public void AddByte(string key, byte value);
            public void AddInt(string key, int value);
            public void AddListInt(string key, List<int> value);
            public void AddListLong(string key, List<long> value);
            public void AddListString(string key, List<string> listValue);
            public void AddLong(string key, long value);
            public void AddString(string key, string name);
            
            public List<KeyBuffer> GetAllBuffer();
            public List<KeyString> GetAllString();
            public byte[] GetBuffer(string key);
            public List<byte[]> GetBufferOfSameKey(string key);
            public byte? GetByte(string key);
            public List<byte> GetByteOfSameKey(string key);
            public int? GetInt(string key);
            public List<int> GetIntOfSameKey(string key);
            public List<int> GetListInt(string key);
            public List<long> GetListLong(string key);
            public List<string> GetListString(string key, int startIndex = 0);
            public long? GetLong(string key);
            public List<long> GetLongOfSameKey(string key);
            public string GetString(string key);
            public List<string> GetStringOfSameKey(string key);
        }

    NetPacket中数据采用key、value的方式存储。可以存储int,string,List<int>,List<string>,byte[]等类型,可以满足多种应用场景。
    处理逻辑说明
      处理的重点是NetPacket的序列化和反序列化。将NetPacket序列化为多个内存块,而不是序列化为一个单独的内存块。这样做意义就是:当NetPacket包含大量的数据(比如几百兆),如果只序列化为一个内存块,则需要系统分配连续的几百兆内存,这样很可能导致分配失败;序列化为多个小内存块就可以防止这种问题,所以NetPacket一次可以传输大量数据,而不用担心系统是否可以分配连续的大内存块。

    性能验证测试

      前文剖析了系统内部处理逻辑,系统的性能还需要现实检验。任何成功都不是一蹴而就,为了追求性能的极致,对系统做了多次优化,才达到了满意的效果。
      系统的性能有两个指标:传输量、响应时间。响应时间指的是:数据发送到对端,再从对端返回的时长。传输量、响应时间这两个指标有关联,而又不完全一样。很多系统传输量大非常大,但是响应不够及时。响应及时是开发远程过程调用的基础,是更高一个层次的要求。这里主要测试响应时间。

     主要测试数据发送到对方,再从对方返回数据所用时长。因为条件所限,客户端与服务器都在同一台机器上。

     测试平台: i5第4代cpu;

    1) 50个字节数据发送

      

    平均时间小于1毫秒,也就是说每秒可以执行1000次函数调用。

    2)1K字节数据收发

     

     和50字节调用差别不大。

    3)100K 字节数据收发

    响应时间大概为12毫秒,每秒可以执行80次调用。

    4)10000K字节数据收发 (接近10M数据)

    时间刚超过1秒。这是10M数据发送,再接收的时间。相当于占用200M带宽。

    响应时间测试总结小数据量,基本可以达到每秒1000次函数调用。10M的数据收发刚刚超过1秒。注意这还不能完全反应网络层处理的能力。因为这是单个线程调用,如果多个线程同时调用,可以达到更高的调用次数。

    传输量测试

    我使用c++写的模拟程序,对该系统测试。收发数据总计超过50M,暨占用500M带宽,cpu占用率23%。测试平台为笔记本,硬件配置比较低。如果采用高性能服务器,达到千兆带宽传输,cpu占用也不会很高。

    总结笔者从事软件开发多年,对于socket通讯编程非常有经验。 一个好的通讯模块有很多指标,比如:复用性高、耦合性低、性能高、易用性好;本系统在设计时就综合考虑了这些要求。对于如何设计好通讯层,我进行了很多思考,将其付诸于代码;公司的多款产品通讯层就是采用该系统,该系统经过了实践的检验,完全满足了多个产品的要求。

      当然,一款产品在任何条件都是最优的,这很难做到。网络层亦是如此。根据上层数据收发的特点,来调整网络层的一些配置参数,这样才能达到最优。如果你公司的产品需要高性能网络层做支持,可以联系我。我会根据产品的特点做优化。

    下载执行程序加QQ群:877371250.

  • 相关阅读:
    Atitit 趋势管理之道 attilax著
    Atitit 循环处理的新特性 for...else...
    Atitit 2017年的技术趋势与未来的大技术趋势
    atitit 用什么样的维度看问题.docx 如何了解 看待xxx
    atitit prj mnrs 项目中的几种经理角色.docx
    Atitit IT办公场所以及度假村以及网点以及租房点建设之道 attilax总结
    Atitit 工具选型的因素与方法 attilax总结
    Atitit.团队文化建设影响组织的的一些原理 法则 定理 效应 p826.v4
    Atiitt 管理方面的误区总结 attilax总结
    Atitit 未来趋势把控的书籍 attilax总结 v3
  • 原文地址:https://www.cnblogs.com/yuanchenhui/p/iocpcore.html
Copyright © 2011-2022 走看看