All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.qifun.bcp.Bcp.scala Maven / Gradle / Ivy

The newest version!
/*
 * scala-bcp
 * Copyright 2014 深圳岂凡网络有限公司 (Shenzhen QiFun Network Corp., LTD)
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.qifun.bcp
import scala.concurrent.duration._
import java.nio.ByteBuffer

/**
 * BCP协议相关的数据结构和常量
 *
 * BCP(Brutal Control Protocol,残暴控制协议)是基于TCP实现的用户层传输协议。特性如下:
 *
 *  1. 基于连接
 *  1. 可靠,低延时
 *  1. 以数据包为单位,没有流
 *  1. 乱序数据包,不保证接收顺序与发送顺序一致
 *
 * 

BCP vs. TCP

* * BCP和普通TCP功能相似,重新实现了TCP的包确认重发机制。 * * 当网络条件不好,丢包频繁,TCP重传间隔时间会变得很长,从而基本不可用。 * 这种情况下,BCP会暴力杀死底层TCP连接,强制重建TCP连接,暴力重发数据,避免丢失数据并减少延时。 * * BCP和TCP都属于面向连接会话的协议。 * 但另一方面,BCP数据不是TCP那样的流,而以[[Packet]]为单位。 * BCP协议不保证接收方收取[[Packet]]的顺序和发送方发出[[Packet]]的顺序一致。 * 这是因为,一条BCP会话,可能会对应多达[[MaxConnectionsPerSession]]个底层TCP连接, * 而这几条底层TCP连接的延时可能并不相同。 * *

BCP会话的一生

* * 1. 客户端随机生成[[NumBytesSessionId]]字节的会话ID。 * 1. 客户端发起TCP连接。 * 1. TCP连接成功建立后,客户端发送[[ConnectionHead]], * 其中包括先前生成的会话ID和varint格式的底层连接ID。 * 每条会话的底层连接ID都从0开始, * 每当客户端发起新TCP连接时,底层连接ID递增1。 * 1. 客户端或服务端发送[[Data]]包,向对端发送用户数据。 * 1. 每发一个[[Data]],对端都回应[[Acknowledge]]。 * 1. 如果客户端或服务端连续[[HeartBeatDelay]]时间, * 都没有[[Packet]]需要发送,那么发送一个[[HeartBeat]]到对端。 * 1. 如果客户端或服务端连续[[ReadingTimeout]]都收不到任何数据, * 就认为这个TCP连接已经没救了,就关闭这个TCP连接。 * 1. 当客户端发送一个包,并超过[[BusyTimeout]]时间收不到对端都回应的[[Acknowledge]]时, * 就认为已有底层TCP连接速度太慢,客户端可以发起新的TCP连接, * 把一部分[[Data]]放到新的TCP连接上发送。 * 1. 新增TCP连接与首个TCP一样,仍然属于同一会话, * 客户端也需要在连接建立后发送[[ConnectionHead]]。 * 但此处的会话ID应当重用原有的会话ID,不要生成新ID。 * 1. 如果客户端发现某一会话中的TCP连接超过[[IdleTimeout]]时间没有发送任何数据, * 且该连接不是最后一条连接,那么客户端可以向服务端发送[[Finish]],关掉这条TCP连接。 * 1. 服务器收到[[Finish]]时,应当回复[[Acknowledge]],然后关掉底层连接。 * 1. 如果一个底层TCP连接因为超时或者别的原因异常中断, * 那么每一端都必须在同一会话的其他TCP连接上, * 重发那些尚未收到对应[[Acknowledge]]的[[Data]]和[[Finish]]。 * 重发的[[RetransmissionData]]和[[RetransmissionFinish]]包中, * 包含原先连接ID和原先包ID。 * 包ID是指原包的序号,从零开始计算, * 原连接每发送一个[[Data]]或[[Finish]],序号就递增1。 * 1. 当客户端和服务端希望结束BCP会话时,向对端发送[[ShutDown]], * 然后清除该会话相关的所有数据。 * 1. 对端收到[[ShutDown]]后,无须回复,直接清除会话相关的所有数据。 * * 此外,客户端可以把会话ID记录在文件中。 * 如果应用崩溃,用户在短时间内重连, * 客户端可以使用先前保存的会话ID,而不需要重新生成会话ID。 * 这种情况下,客户端应当在建立连接后立即发送[[Renew]]给服务端, * 服务端会放弃重发原会话尚未成功发送的数据, * 但同时会视为会话从未断开,不会清除原会话的服务端数据。 */ object Bcp { /** * 最多缓存多少个离线包 * * @group Constants */ final val MaxOfflinePack = 200 /** * 每个会话最多允许多少个活跃TCP连接 * * @group Constants */ final val MaxActiveConnectionsPerSession = 3 /** * 每个会话最多允许多少个TCP连接,包括活跃TCP连接和还没有收到Finish但已经关闭的僵尸连接 * * @group Constants */ final val MaxConnectionsPerSession = 5 /** * 当一个TCP连接空闲多长时间没发送任何数据时,发一个心跳包 * * @group Constants */ final val HeartBeatDelay = 3.seconds /** * 多长时间收不到任何包,就杀掉TCP连接。 * * @group Constants */ final val ReadingTimeout = 6.seconds /** * 多长时间发不出数据就杀掉TCP连接。 * * 发数据超时只可能因为TCP缓冲区满, * 只有发送超大数据包时,客户端很慢,才会发生这种情况,可能性微乎其微。 * * @group Constants */ final val WritingTimeout = 1.seconds /** * 连接在busy状态多长时间就新建链接 * * @group Constants */ final val BusyTimeout = 500.milliseconds /** * 重新建立链接时间 * * @group Constants */ final val ReconnectTimeout = 500.milliseconds /** * 连接在idle状态多长时间就关闭链接 * * @group Constants */ final val IdleTimeout = 10.seconds /** * Session ID由多少字节构成 * * @group Constants */ final val NumBytesSessionId = 16 /** * 数据包上限是多少字节 * * @group Constants */ final val MaxDataSize = 100000 /** * @group Protocols */ final case class ConnectionHead(sessionId: Array[Byte], isRenew: Boolean, connectionId: Int) /** * @group Protocols */ sealed trait Packet /** * @group Protocols */ sealed trait ServerToClient extends Packet /** * @group Protocols */ sealed trait ClientToServer extends Packet /** * 需要回复[[Acknowledge]]的协议 * * @group Protocols */ sealed trait AcknowledgeRequired extends Packet /** * 重传的数据 * * @group Protocols */ sealed trait Retransmission extends Packet { val connectionId: Int val packId: Int } /** * @group Protocols */ final case class Data(buffers: Seq[ByteBuffer]) extends ServerToClient with ClientToServer with AcknowledgeRequired /** * @group Protocols */ object Data { final val HeadByte: Byte = 0 } /** * @group Protocols */ case object Acknowledge extends ServerToClient with ClientToServer { final val HeadByte: Byte = 1 } /** * @group Protocols */ final case class RetransmissionData(connectionId: Int, packId: Int, buffers: Seq[ByteBuffer]) extends ServerToClient with ClientToServer with AcknowledgeRequired with Retransmission /** * @group Protocols */ object RetransmissionData { final val HeadByte: Byte = 2 } /** * 结束一个TCP连接,相当于TCP FIN。 * * @note 不能直接用TCP FIN是因为一方发送Finish后还可能继续发Acknowledge。 * 而在调用shutdown发送TCP FIN后,就没办法再发送Acknowledge了。 * * @group Protocols */ case object Finish extends ServerToClient with ClientToServer with AcknowledgeRequired { final val HeadByte: Byte = 3 } /** * @group Protocols */ final case class RetransmissionFinish(connectionId: Int, packId: Int) extends ServerToClient with ClientToServer with AcknowledgeRequired with Retransmission /** * @group Protocols */ object RetransmissionFinish { final val HeadByte: Byte = 4 } /** * @group Protocols */ case object ShutDown extends ServerToClient with ClientToServer { final val HeadByte: Byte = 5 } /** * @group Protocols */ case object HeartBeat extends ServerToClient with ClientToServer { final val HeadByte: Byte = 6 } /** * @group ConnectionStates */ sealed trait ConnectionState /** * @group ConnectionsStates */ case object ConnectionIdle extends ConnectionState /** * @group ConnectionsStates */ case object ConnectionBusy extends ConnectionState /** * @group ConnectionsStates */ case object ConnectionSlow extends ConnectionState }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy