通向码农的道路(enet开源翻译计划 二)

Posted lytwajue

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了通向码农的道路(enet开源翻译计划 二)相关的知识,希望对你有一定的参考价值。


  • QQ 324186207群 enet交流技术,主要是为了研究tcp内部执行机制。欢迎大家增加探讨。小弟水平有限,翻译难免有误。


    http://enet.bespin.org  

  • 解析enet 双向链表(无placement new) enet本身就已经局限了4095 在线人数   假设有10000人同一时候在线。enet使用list来维护每次收发,不断的销毁,释放内存,性能实在太低。

    enent写的根本不严谨,无论什么结构都存储双向链表,收一个包,我也须要去遍历,究竟获取某peer。 

  • enet_host_service  每次事件抛出机制,实在太粗糙。说明enet  很多其它偏向就是一个学习项目,假设大量数据传输,高并发,高负载,无法满足需求
  • 顺便提下。为何server好多地方不使用stl  
  • 问题一:不能确定STL怎样管理内存。如果就依照STL默认的方法来管理内容。则server在长时间的分配和释放内存后,easy导致内存碎片。对server的稳定有影响。然而,如果没有阅读过STL的源代码。谁又能确切地得知STL是怎样管理内存的呢?尽管可以在STL中使用自己的分配器,又可以确认STL的某些部分不会在分配器之外进行内存分配呢?

        当然,内存管理问题不是STL带来的问题,不论什么server程序本身都要考虑这个问题。原因在极少有人去阅读STL的源代码。去了解STL的内部实现机理。由于不了解,所以怀疑。由于怀疑,所以不轻易使用。

        问题二:不能确定STL在海量数据下的表现。

    server一般都是海量的内存,为了提高性能大量数据保存在内存中。

    在很大的规模的数据下。STL一定可以满足稳定性和效率的需求吗?

        问题三:不能确定STL在多线程环境下的表现。

    以上的内存和规模的问题能够通过加深对STL的了解和測试来解决,可是多线程下的并发问题就不是那么easy攻克了。

    STL不是线程安全的。在多线程环境下。对STL容器的操作都要加锁来确保正确。然后。部分高性能的场合,须要对“读-读”条件下并发进行优化,以及某些锁无关的特殊条件能够不加锁,甚至是採用流行的RCU机制…………在这个倡导多核和并发的时代,STL显得有些落后了。

  • 传统的双向链表 普通情况我们套上 T *data 


  • /*双链表结构*/ 
  • template<class T> 
  • typedef struct node  
  • {  
  •     T  *data;  
  •     struct node *prior;  
  •     struct node *next;  
  •   
  • }DNode;

  • 下面enet双向链表结构,发现结构体内无T *data ,奇怪吧?那么我们数据存储到哪里?
  • #list.h

  • typedef struct _ENetListNode
  • {
  •     struct _ENetListNode * next;  
  •     struct _ENetListNode * previous;  
  • } ENetListNode;

  • typedef ENetListNode * ENetListIterator;

  • typedef struct _ENetList
  • {
  •     ENetListNode sentinel;  //标记当前处于某个节点位置
  • } ENetList;


  • extern void enet_list_clear( ENetList * ); 

  • extern ENetListIterator enet_list_insert( ENetListIterator , void * ); 
  • extern void * enet_list_remove( ENetListIterator ); 
  • extern ENetListIterator enet_list_move( ENetListIterator, void *, void * );

  • extern size_t enet_list_size( ENetList * ); 

  • #define enet_list_begin(list) ((list) -> sentinel.next)  
  • #define enet_list_end(list) (& (list) -> sentinel) 

  • #define enet_list_empty(list) (enet_list_begin (list) == enet_list_end (list))

  • #define enet_list_next(iterator) ((iterator) -> next)
  • #define enet_list_previous(iterator) ((iterator) -> previous)

  • #define enet_list_front(list) ((void *) (list) -> sentinel.next) 
  • #define enet_list_back(list) ((void *) (list) -> sentinel.previous)

  • #main.cpp

  • #define ENET_CALLBACK __cdecl
  • typedef struct _ENetCallbacks
  • {
  •     void * ( ENET_CALLBACK * malloc ) ( size_t size );
  •     void ( ENET_CALLBACK * free ) ( void * memory );
  •     void ( ENET_CALLBACK * no_memory ) ( void );
  • } ENetCallbacks;

  • static ENetCallbacks callbacks = { malloc, free, abort };

  • void * enet_malloc( size_t size )
  • {
  •     void * memory = callbacks.malloc( size );
  •     
  •     if( memory == NULL )
  •         callbacks.no_memory();
  •     
  •     return memory;
  • }
  • void  enet_free( void * memory )
  • {
  •     callbacks.free( memory );
  • }

  •  struct ENetOutgoingCommand
  • {
  •     ENetListNode list;
  •     int a;
  • };

  • int main()

  • ENetOutgoingCommand *enet_outgoing;

  • ENetList enet_test;

  • enet_list_clear(&enet_test); //重置双向链表,将头指针和尾指针指向第一个空节点
  • enet_outgoing = (ENetOutgoingCommand *)enet_malloc( sizeof( ENetOutgoingCommand ) );
  • enet_outgoint->a = 100;
  • enet_list_insert( enet_list_end( &enet_test ), enet_outgoing ); //malloc 数据插入到双向链表的尾部
  • ENetListIterator currentCommand;
  • currentCommand = enet_list_begin(&enet_test ); //这里取出双向链表头节点

  • while( currentCommand != enet_list_end( &enet_test ) ) //遍历链表
  • {
  •     ENetOutgoingCommand * t1 = (ENetOutgoingCommand *)currentCommand;  //通过强转来获取数据
  •     cout << t1->a << endl;
  •     currentCommand = enet_list_next( currentCommand ); //获取下个节点
  •     enet_list_remove( &t1->list ); //删除当前节点
  •     enet_free( t1 );//删除内存,避免内存泄漏
  •  
  •  
  • while( Exit )
  • {
  •     開始server循环机制,这个循环设计非常的糟糕,每次一但server触发事件,会调用不同类型事件。

  •     解决的方法,每个while 我用队列收一把全部数据。然后在逐个遍历,这样添加了非常小延迟,可是提高了吞吐量。
  •     每次收到数据后。又从新  enet_protocol_send_outgoing_commands 開始检查第一个玩家,应该做个全局变量,存储当前遍历       位置

  •     ENetEvent event;
  •     if( enet_host_service( server, &event, 1 ) )
  •     {
  •         switch( event.type )
  •         {
  •         case ENET_EVENT_TYPE_CONNECT:
  •         {
  •             Conneted( event.peer->connectID, event.peer->address.host, event.peer->address.port );
  •             peersConn.Insert( event.peer->connectID, event.peer );
  •             break;
  •         }
  •         case ENET_EVENT_TYPE_RECEIVE:
  •         {
  •             //bIdle = false;
  •             //Recived( event.peer->connectID, event.packet->data, event.packet->dataLength );
  •             break;
  •         }
  •         case ENET_EVENT_TYPE_DISCONNECT:
  •         {
  •             cout << "close client_fd" << endl;
  •             //Disconneted( event.connectID );
  •             break;
  •         }

  •         case ENET_EVENT_TYPE_PING:
  •         {
  •             cout << "enter reconnect:" << time( 0 ) << endl;
  •             //ReConnection(event.peer->connectID);
  •             break;
  •         }
  •         }

  •     }

  • }


  • /** Waits for events on the host specified and shuttles packets between
  •     the host and its peers.

  •     @param host    host to service
  •     @param event   an event structure where event details will be placed if one occurs
  •                    if event == NULL then no events will be delivered
  •    

    以上是关于通向码农的道路(enet开源翻译计划 二)的主要内容,如果未能解决你的问题,请参考以下文章

    码农的好助手:版本管理工具git的使用

    PHP 码农的 go 语言密码本项目开发实战 - 项目介绍

    大龄码农的新西兰移民之路

    论码农的财富修养

    从字节大佬那要来的,码农的学习书单!

    从字节大佬那要来的,码农的学习书单!