• TCP相关细节


    1. 常用TCP参数

    1.1 ReceiveBufferSize
    ReceiveBuffersize指定了操作系统读缓冲区的大小默认值是8192(如图5-10 所示)。在第4章的例子中,会有"假设操作系统缓冲区的长度是8" 这样的描述,可通过socket.ReceiveBufferSize= 8 实现。当接收端缓冲区满了的时候,发送端会暂停发送数据,较大的缓冲区可以减少发送端暂停的概率, 提高发送效率

    1.2 SendBufferSize
    SendBuffersize 指定了操作系统写缓冲区的大小,默认值也是8192。对于那些没有处 理 好 “ 完整发送数据 ” 的网络模块 ( 见 4 . 5 节 ), 可以将SendBuffersize设成较大的值 , 以避免因发 送不完整而带来的各种问题 ( 图 5 - 1 0 )。 笔者见过有些还算成功的游戏项目 , 虽没有处理好数据的接收问题,但将 Sen dBuffer si ze 调大10倍,也能让游戏正常运转。

    1.3 NoDelay
    指定发送数据时是否使用Nagle 算法,对于实时性要求高的游戏,该值需要设置成 true Nagle 是一种节省网络流量的机制,默认情况下,TCP 会使用Nagle 算法去发送数据。
    Nagle 算法的机制在于,如果发送端欲多次发送包含少量字节的数据包时,发送端不 会立马发送数据,而是积攒到了一定数量后再将其组成一个较大的数据包发送出去。
    启用Nagle 算法可以提升网络传输效率,但它要收集到一定长度的数据后才会把它们 一 块儿发送出 去。这样一来,就 会降低网 络的实时性, 大部分实时网络游戏都会关闭 Nagle 算法,将socket.NoDelay 设置成true

    1.4 TTL

    TTL 指发送的IP数据包的生存时间值 (Time To Live , TTL ) 。 TTL 是 IP 头部的一 个值 ,
    该值表示一个IP 数据报能够经过的最大的路由器跳数。发送数据时, TTL 默 认为64 (TTL 的默认值和操作系统有关,Windows
    Xp默认值为128,Windows7默认值为64, Window10 默认值为6 5, Lin ux 默认值为 255 )。

    数据在网络上传输, 实际上是经过多个路由器转发的。如图5- 13所示,发送端往接收端 发送一个卫数据报,初始的TTL
    为64,在经过第一个理由器时,『头部的TTL减小,变成 63;
    在经过第二个路由器时,变成了62。以此类推,直到TTL等于0,路由器就会丟弃数据。

    在这里插入图片描述
    在网络游戏中, 如果某些偏远地区用户时不时无法按收数据 , 可以尝试增大TTL值 ( socket.ttl=xxx)来解决问题。

    1.5 ReuseAddress

    Reuse Address 即端又复用 , 让同一个端又可被多个 socket 使用 。 一 般 情 况 下, 一 个 端 又只能由一个进程独占,假设服务端程序都绑定了1234端又,若开启两个服务端程序,虽 然, 第一个开启的程序能够成功绑定端又并监听,但第二个程序会提示“
    端又己经在使用 中 ” , 无 法 绑 定 端 又。 在 计 算 机 中 , 退 出 程 序 与 释 放 端 又 并 不 同 步 。 在 5. 2 . 3 节 “ T C P 连 接 的终止〞 中,我们知道TCP断开连接会经历4次挥手。4次挥手需要时间,在网络不好的情况下,程序还会多次重试。当服务端程序崩溃,但它持有的Socket 不会被立马释放,这 时候重启 服务器就会遇到“ 端 又已经在使用中”的情形。等到Socket 被释放后(这个过程 可 能 要 十 几 分钟 时 间 ) , 服 务 端 才 能 成 功 重 启 。

    对于人 气爆棚的大型网游, 十几分钟的等待时间会造成很大损失,一般要求在程序崩 溃 ( 尽 管 也 不 应 该 崩 溃, 但 人 算 不
    如 天 算 ) 后 立 刻 重 启 , 继 续 提 供 服 务 。 端 又 复 用 最 常 见 的 用途是, 防止服务器 重启时,
    之前鄉定的端又还未释放或者程序突然退出而系统没有释放 端 又。这种情况下如果设定了端又复用,则新启动的服务器进程可以直接鄉定端又。如果
    没有设 定端又复用, 绑定会失败,提示端又己经在使用中,只好等十几分钟再重试了。 设 置 端又 复 用 使 用 s o c k e t 的
    Sctsocket Option 方 法 , 代 码 如 下 所 示 。

    Socket socket= new socket(AddressFamily.InterNetwork, socketrype.stream, ProtocolType.Tcp);
    socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
    

    尽 管端又复用能解决服务端立即重启的问题,但它存在安全隐患。 主动关闭方有可能 在下次 使用时 收到上一次连接的数据包, 包括关闭连接响应包或者正常通信的数据包, 有可能出现奇怪现象

    1.6 LingerState
    LingerState 的功能是设置套接字保持连接的时间。

    在这里插入图片描述
    服务端中,会使用 下面的代码处理客户
    端主动关闭连接,即在收到长度为0 的消息
    后 , 调 用 clientfd.Close()关 闭 连 接 。
    在这里插入图片描述
    在这里插入图片描述
    发送缓冲区还有尚末发送的数据, 那么直接调用Close 关闭连接,缓冲区中的数据将被丢弃。这种关闭方式很暴力,因为对端 可 能 还 需 要 这 些 数 据。 在 服 务 端 收 到 关 闭 信 号 后 , 有没有办法先把发送缓冲区中的数据发完,再关闭连接呢 ? LingerState 就是为了解决这个问题而诞生的 。

    socket.LingerState = new LingerOption(true, 10);
    

    其中的LingerOption 带有两个参数。第一个参数是LingerState.Enabled,代表是否启用 LingerState,只有设置为true 才能生效。第二个参数是LingerState.Linger Time,指定超时 时间。如果超时时间大于0 (比如10 秒),操作系统会尝试发送缓冲区中的数据,但如果网络状况不好,超过10秒还没有发完,它还是会强制关闭连接。
    如果LingerState.LingerTime设置为0,系统会一直等到数据发完才关闭连接,无论等待多长时间。开启LingerOption能够在一定程度上保证发送数据的完整性。
    在这里插入图片描述
    服务端进入TIME_WAIT状态后,会等待一段时间再释放自由.对于高并发的服务端,过多的TIME_WAIT会占用系统资源,不是已经好事。有时候需要减小服务器的TIME_WAIT值,以求快速释放自由

    2. Close的恰当时机

    Lingerstate选项可以让程序在关闭连接前发完系统缓冲区中的数据,然而,这并不代表能将所有数据发出去。

    下面完善代码使连接关闭时,依然能够完整发送数据。
    对于主动关闭的一方(假设调用下述Close 方法关闭连接),应判断当前是否还有正在
    发送的数据 。如有 , 只将标志位 isClosing 设置为 true , 等数据发送完再关闭连 接 ; 如果没有正在发送数据,直接调用socket.Close()关闭连接。代码如下:

    bool isClosing = false;
    
    //关闭连接
    public void Close() {
        //还有数据在发送
        if(writeQueue.Count > 0) {
           isClosing = true;
        } else { //没有数据在发送
           socket.Close();
        }
    }
    

    由于设置了isClosing 标志位,在关闭连接的过程中,程序只负责将已有的数据发送 完,不会发送新的数据。可以在Send 方法中添加判断,假如程序处于Closing状态,不能发送信息。代码如下:

    //点击发送按钮 
    public void Send ( )
    {
    	if (isClosing) {
    	   return;
    	}
        // 拼接字节 , 省略组装 sendBytes 的代码
        byte[] sendBytes = 要发送的数据 ;
        ByteArray ba = new ByteArray (sendBytes);
        writeQueue.Enqueue (ba); 
        // send
        if(writeQueue.Count == 1){
            socket. BeginSend(ba.bytes, ba.readIdx, ba.length, 0, SendCallback, socket);
        }
    }        
    

    在BeginSend 回调两数中 , 还需要判断程序是否处于isClosing状 态, 如果程序发 送完写入队列的所有数据,而且处于isClosing 状态,应调用socket.Close 关闭连接。代码如下:

    public void Sendcallback(IAsyncResult ar) {
        // 获取state、Endsend 的处理
        Socket socket = (Socket) ar.AsyncState; 
        int count = socket.EndSend(ar);
        // 判断是否发送完整
        ByteArray ba= writeQueue.First (); 
        ba.readIdx+=count;
        if(count ==ba.length){ 
           //发送完整
           writeQueue. Dequeue ( );
           ba = writeQueue.First ();
        }   
    
        if(ba != null){
            //发送不完整,或发送完整且存在第二条数据 
            socket. BeginSend(ba.bytes, ba.readIdx, ba.length,
    0, SendCallback, socket);
         } else if(isClosing) {
               socket.Close ( );
         }
    }
    

    3. 心跳机制

    断开连接时, 主动方会给对端发送 F I N 信 号 , 开启4 次挥手流程 。 但在某些情况下, 比如拿着手机进人没有信号的山区,更极端的,比如有人拿剪刀把网 线剪断。虽然断开了连 接 , 但主动方无法给对端发送 FIN 信号 ( 网线剪断了还能干什么? ), 对端会认为连接有效,一直占用系统资源。

    游戏开发中,TCP默认的KeepAlive 机制很“ 鸡肋”,因为上述的“一段时间” 太长, 默认为2小时 。 一般会自行实现心跳机制 。心跳机制是指客户端定时 ( 比 如 每 隔 1 分 钟 ) 向 服务端发送P I N G 消 息 , 服 务 端 收 到 后 回 应 P O N G 消 息 。 服 务 端 会 记 录客 户 端 最 后 一 次 发 送 P I N G 消 息 的 时 间 , 如 果 很 久 没 有 收 到 (比 如 3 分 钟 ) , 就 假 定 连 接 不 通, 服 务 端 会 关 闭 连 接 , 释放系统资源

    心跳机制也有缺点,比如在短暂的故障期间,它们可能引起一个良好连接被释放; PING和PONG消息占用了不必要的宽带; 在流量如黄金的移动网络中,会让玩家花贵更多 的流量费。

  • 相关阅读:
    关于Rxjava的简单使用
    idea默认带的equals和hashcode引起的bug
    allure测试报告用例数和 pytest执行用例数不相同问题
    python 如何根据索引快速删除列表中的多个元素
    JSON注解和异常处理的使用
    python3.10 安装问题解决
    C# Winfrom Chart 图表控件 柱状图、折线图
    JavaScript基础05——字面量、变量介绍及变量基本使用
    04-jQuery动画
    模拟实现ATM系统——Java
  • 原文地址:https://blog.csdn.net/cangqiong_xiamen/article/details/139702378