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 方 法 , 代 码 如 下 所 示 。

csharp 复制代码
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()关闭连接。代码如下:

csharp 复制代码
bool isClosing = false;

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

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

csharp 复制代码
//点击发送按钮 
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 关闭连接。代码如下:

csharp 复制代码
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消息占用了不必要的宽带; 在流量如黄金的移动网络中,会让玩家花贵更多 的流量费。

相关推荐
Abladol-aj1 小时前
HTTP 缓存技术
网络协议·http
网安-轩逸1 小时前
网络安全期末复习
网络·web安全·php
Hacker_LaoYi1 小时前
【网络安全设备系列】12、态势感知
网络·安全·web安全
IpdataCloud2 小时前
IP反向追踪技术,了解一下?
网络·tcp/ip·智能路由器
chirrupy_hamal2 小时前
网络基础 - 地址篇
网络
漫天飞舞的雪花3 小时前
gRPC 双向流(Bidirectional Streaming RPC)的使用方法
网络·网络协议·rpc·go1.19
勤奋的小王同学~3 小时前
(计算机组成原理)期末复习
网络·组成原理
幼稚的男孩°4 小时前
ubuntu 域名解析错误 ping ip 网络不可达
网络
jekc8686 小时前
Centos 相关网络配置
linux·网络·centos
爱上语文7 小时前
Http 响应协议
网络·后端·网络协议·http