QQ 324186207群 enet交流技术。主要是为了研究tcp内部执行机制,欢迎大家增加探讨。小弟水平有限。翻译难免有误。
。
Features:
ENet evolved specifically as a UDP networking layer for the multiplayer first person shooter Cube.ENet 最初衷设计为了第一人称射击类游戏。
为什么须要udp (參考,unix网络编程,假设不是为了进行多播,不要使用udp,我们应该使用tcp,让厂商来关注性能 )由于无法容忍延迟。(參考tcp v1)
眼下我们游戏来看卡牌塔防类,根本不须要udp 就算有高延迟玩家有什么不能接受的。 看看梦幻细雨。有时候一回合延迟高达10秒?? 终于我们失去了tcp的又一次分组,高速恢复算法,高速重传,延迟确认机制 ,坚持定时器........
由于看到腾讯的 t3 面试问题 怎样提网络吞吐量?enet 正是怎样回答这个问题的最佳方案。我想enet 正是学习tcp最佳路程。假设仅仅是看内核tcp源代码学习。难免会有点不自量力。
1.
channels 这个设计眼下我理解的为了进行负载均衡, 比方 client -》 zoneconnect -》 zoneserver
假设此时 client zoneconnect 1 num ,zoneconnect zoneserver 2 num , 1 num 之间通信发生了拥塞,此时通过拥塞控制,降低发包频率,假设
2 num 也在发包呢? 然而此时的 1 num 已经发生了拥塞 。那么我们的所须要发送的数据包,仅仅有异步等待 1 num发送 为了解决问题的延迟,并降低对数据包的限制,使用多通道独立的进行发送。因此此时一个通道的数据包传送状态不会影响其它通道的包传送 。
假设默觉得0 则是禁止开启流量控制。和拥塞控制。(參考 tcpv2 内核源代码)
To combat this latency and reduce
the ordering restrictions on packets, ENet provides multiple channels of communication over a given connection. Each channel is independently sequenced, and so the delivery status of a packet in one channel will not stall the delivery of other packets in another
channel.
ENetHost *
enet_host_create (const ENetAddress * address, size_t peerCount, size_t channelLimit, enet_uint32 incomingBandwidth,enet_uint32 outgoingBandwidth)
{
ENetHost * host; //server本端 一个全局变量存储数据
ENetPeer * currentPeer;//当前client就是一个peer
//ENET_PROTOCOL_MAXIMUM_PEER_ID
//
if( peerCount > ENET_PROTOCOL_MAXIMUM_PEER_ID )
return NULL;
host = (ENetHost *) enet_malloc (sizeof (ENetHost));
if (host == NULL)
return NULL;
memset(host, 0, sizeof (ENetHost));
host ->peers = (ENetPeer *) enet_malloc (peerCount * sizeof (ENetPeer));
if (host ->peers == NULL)
{
enet_free (host);
return NULL;
}
memset (host ->peers, 0, peerCount * sizeof (ENetPeer));
host ->socket = enet_socket_create (ENET_SOCKET_TYPE_DATAGRAM);
if (host ->socket == ENET_SOCKET_NULL || (address != NULL && enet_socket_bind (host ->socket, address) < 0))
{
if (host ->socket != ENET_SOCKET_NULL)
enet_socket_destroy (host ->socket);
enet_free (host ->peers);
enet_free (host);
return NULL;
}
enet_socket_set_option (host ->socket, ENET_SOCKOPT_NONBLOCK, 1); //设置非堵塞
enet_socket_set_option (host ->socket, ENET_SOCKOPT_BROADCAST, 1);//设置广播
enet_socket_set_option (host ->socket, ENET_SOCKOPT_RCVBUF, ENET_HOST_RECEIVE_BUFFER_SIZE); //设置socket 接受缓冲区
enet_socket_set_option (host ->socket, ENET_SOCKOPT_SNDBUF, ENET_HOST_SEND_BUFFER_SIZE);//设置socket发送缓冲区
if (address != NULL && enet_socket_get_address (host ->socket, & host -> address) < 0) // 绑定socket 设置地址
host -> address = * address;
if (! channelLimit || channelLimit > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT)
channelLimit = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT;
else
if (channelLimit < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT)
channelLimit = ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT;
host ->randomSeed = (enet_uint32) (size_t) host;
host ->randomSeed += enet_host_random_seed ();
host ->randomSeed = (host ->randomSeed << 16) | (host ->randomSeed >> 16);
host ->channelLimit = channelLimit;
host ->incomingBandwidth = incomingBandwidth;
host ->outgoingBandwidth = outgoingBandwidth;
host ->bandwidthThrottleEpoch = 0;
host ->recalculateBandwidthLimits = 0;
host ->mtu = ENET_HOST_DEFAULT_MTU; //通信最大包限制, 本身自带分包发送。
host ->peerCount = peerCount;
host ->commandCount = 0;
host ->bufferCount = 0;
host ->checksum = NULL;
host ->receivedAddress.host = ENET_HOST_ANY;
host ->receivedAddress.port = 0;
host ->receivedData = NULL;
host ->receivedDataLength = 0;
host ->totalSentData = 0;
host ->totalSentPackets = 0;
host ->totalReceivedData = 0;
host ->totalReceivedPackets = 0;
host ->connectedPeers = 0;
host ->bandwidthLimitedPeers = 0;
host ->duplicatePeers = ENET_PROTOCOL_MAXIMUM_PEER_ID;
host ->maximumPacketSize = ENET_HOST_DEFAULT_MAXIMUM_PACKET_SIZE;
host ->maximumWaitingData = ENET_HOST_DEFAULT_MAXIMUM_WAITING_DATA;
host ->compressor.context = NULL; // 这里开启压缩包算法
host ->compressor.compress = NULL;
host ->compressor.decompress = NULL;
host ->compressor.destroy = NULL;
host ->intercept = NULL;
enet_list_clear (& host ->dispatchQueue); //双向链表 每次必须clear 使得双指针指向头节点
for (currentPeer = host ->peers;
currentPeer < & host ->peers [host -> peerCount];
++ currentPeer)
{
currentPeer ->host = host;
currentPeer ->incomingPeerID = currentPeer - host ->peers;
currentPeer ->outgoingSessionID = currentPeer ->incomingSessionID = 0xFF;
currentPeer ->data = NULL;
enet_list_clear (& currentPeer ->acknowledgements);
enet_list_clear (& currentPeer ->sentReliableCommands);
enet_list_clear (& currentPeer ->sentUnreliableCommands);
enet_list_clear (& currentPeer ->outgoingReliableCommands);
enet_list_clear (& currentPeer ->outgoingUnreliableCommands);
enet_list_clear (& currentPeer ->dispatchedCommands);
enet_peer_reset (currentPeer);
}
return host;
}
2.
/**
Forcefully disconnects a peer.
@param peer peer to forcefully disconnect
@remarks The foreign host represented by the peer is not notified of the disconnection and will timeout
on its connection to the local host.
*/
void
enet_peer_reset (ENetPeer * peer)
{
enet_peer_on_disconnect (peer);
peer -> outgoingPeerID = ENET_PROTOCOL_MAXIMUM_PEER_ID;
peer -> connectID = 0;
peer -> state = ENET_PEER_STATE_DISCONNECTED;
peer -> incomingBandwidth = 0;
peer -> outgoingBandwidth = 0;
peer -> incomingBandwidthThrottleEpoch = 0;
peer -> outgoingBandwidthThrottleEpoch = 0;
peer -> incomingDataTotal = 0;
peer -> outgoingDataTotal = 0;
peer -> lastSendTime = 0;
peer -> lastReceiveTime = 0;
peer -> nextTimeout = 0; client下次超时时间
peer -> earliestTimeout = 0; 最早的超时时间
peer -> packetLossEpoch = 0;
peer -> packetsSent = 0;
peer -> packetsLost = 0;
peer -> packetLoss = 0;
peer -> packetLossVariance = 0;
peer -> packetThrottle = ENET_PEER_DEFAULT_PACKET_THROTTLE;
peer -> packetThrottleLimit = ENET_PEER_PACKET_THROTTLE_SCALE;
peer -> packetThrottleCounter = 0;
peer -> packetThrottleEpoch = 0;
peer -> packetThrottleAcceleration = ENET_PEER_PACKET_THROTTLE_ACCELERATION;
peer -> packetThrottleDeceleration = ENET_PEER_PACKET_THROTTLE_DECELERATION;
peer -> packetThrottleInterval = ENET_PEER_PACKET_THROTTLE_INTERVAL;
peer -> pingInterval = ENET_PEER_PING_INTERVAL; 心跳检測时间
peer -> timeoutLimit = ENET_PEER_TIMEOUT_LIMIT;
peer -> timeoutMinimum = ENET_PEER_TIMEOUT_MINIMUM;
peer -> timeoutMaximum = ENET_PEER_TIMEOUT_MAXIMUM; 最大超时时间 假设没有收到对端的ack确认,会一直重传,至道最大超时,而且踢掉玩家
peer -> lastRoundTripTime = ENET_PEER_DEFAULT_ROUND_TRIP_TIME;
peer -> lowestRoundTripTime = ENET_PEER_DEFAULT_ROUND_TRIP_TIME;
peer -> lastRoundTripTimeVariance = 0;
peer -> highestRoundTripTimeVariance = 0;
peer -> roundTripTime = ENET_PEER_DEFAULT_ROUND_TRIP_TIME; client和server通信往返时间设置,用于推断是否超时
peer -> roundTripTimeVariance = 0;
peer -> mtu = peer -> host -> mtu;
peer -> reliableDataInTransit = 0;
peer -> outgoingReliableSequenceNumber = 0;
peer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; 滑动窗体大小
peer -> incomingUnsequencedGroup = 0;
peer -> outgoingUnsequencedGroup = 0;
peer -> eventData = 0;
peer -> totalWaitingData = 0;
memset (peer -> unsequencedWindow, 0, sizeof (peer -> unsequencedWindow));
enet_peer_reset_queues (peer);
}
3.
void
enet_peer_reset_queues (ENetPeer * peer)
{
ENetChannel * channel;
if (peer -> needsDispatch)
{
enet_list_remove (& peer -> dispatchList); 收到收到的数据包都会增加到调度队列
peer -> needsDispatch = 0;
}
while (! enet_list_empty (& peer -> acknowledgements)) 对端确认协议
enet_free (enet_list_remove (enet_list_begin (& peer -> acknowledgements)));
enet_peer_reset_outgoing_commands (& peer -> sentReliableCommands); 可靠数据包协议 (每次发送send包后,须要存储到这个双向链表。目的在于存储,用于超时重传,仅仅有收到ack确认。才会删除)
enet_peer_reset_outgoing_commands (& peer -> sentUnreliableCommands); 不可靠数据包协议
enet_peer_reset_outgoing_commands (& peer -> outgoingReliableCommands); 发送数据全部都会优先增加到 进来的可靠数据包协议 然后send后。又会增加到peer -> sentReliableCommands 假设检測超时,又会从新回到peer -> outgoingReliableCommands
enet_peer_reset_outgoing_commands (& peer -> outgoingUnreliableCommands);
enet_peer_reset_incoming_commands (& peer -> dispatchedCommands); 调度协议
if (peer -> channels != NULL && peer -> channelCount > 0) 初始化通道
{
for (channel = peer -> channels;
channel < & peer -> channels [peer -> channelCount];
++ channel)
{
enet_peer_reset_incoming_commands (& channel -> incomingReliableCommands);
enet_peer_reset_incoming_commands (& channel -> incomingUnreliableCommands);
}
enet_free (peer -> channels);
}
peer -> channels = NULL;
peer -> channelCount = 0;
}