《Unity3D网络游戏实战》深入了解TCP

从TCP到铜线

应用层

应用层功能是应用程序(游戏程序)提供的功能。在给客户端发送“hello”的例子中,程序把“hello”转化成二进制流传递给传输层(传送给send方)​。操作系统会对二进制数据做一系列加工,使它适合于网络传输。

传输层

收到二进制数据后,传输层协议会对它做一系列加工,并提供数据流传送、可靠性校验、流量控制等功能。

网络层

IP协议会给TCP数据添加本地地址、目的地地址等信息

数据传输流程

TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议,与TCP相对应的UDP协议是无连接的、不可靠的协议,但传输效率比TCP高。

TCP连接的建立

在TCP/IP协议中,TCP协议提供可靠的连接服务,连接是通过三次握手进行初始化的。三次握手的目的是同步连接双方的序列号和确认号并交换TCP窗口的大小信息

连接方调用Connect后,Client(连接方)向Server(监听方)发送一个数据包SYN, SYN包含了序列号seq,这是以后传送数据时要使用的。Server收到数据包后由标志位SYN知道Client请求建立连接,Server将SYN/ACK数据包发送给Client以确认连接请求。Clients收到SYN/ACK数据包后Connect返回,连接成功

TCP的数据传输

发送一个数据后,发送方并不能确保数据被对方接收。于是发送方会等待接收方的回应,如果太长时间没有收到回应,发送方会重新发送数据。发送数据时,TCP会考虑对方缓冲区的容量,当对方缓冲区满时,会暂停发送数据,防止对端溢出。TCP还会根据数据返回的时间判断网络是否拥堵,如果网络拥堵就减慢发送的速度,以求“道路畅通”​。

TCP连接终止

TCP通过“四次挥手”确保双端释放socket资源

  • 第一次挥手:主机1(可以是客户端也可以是服务端)向主机2发送一个终止信号(FIN)​,此时,主机1进入FIN_WAIT_1状态,它没有需要发送的数据,等待着主机2的回应。
  • 第二次挥手:主机2收到了主机1发送的终止信号(FIN)​,向主机1回应一个ACK。收到ACK的主机1进入FIN_WAIT_2状态。
  • 第三次挥手:在主机2把所有数据发送完毕后,主机2向主机1发送终止信号(FIN)​,请求关闭连接。
  • 第四次挥手:主机1收到主机2发送的终止信号(FIN)​,向主机2回应ACK。然后主机1进入TIME_WAIT状态(等待一段时间,以便处理主机2的重发数据)​。主机2收到主机1的回应后,关闭连接。至此,TCP的四次挥手便完成了,主机1和主机2都关闭了连接

常用TCP参数

ReceiveBufferSize

ReceiveBufferSize指定了操作系统读缓冲区的大小,默认值是8192,可以通过 socket. ReceiveBufferSize = 8 这样来指定缓冲区的长度

SendBufferSize

SendBufferSize指定了操作系统写缓冲区的大小,默认值也是8192

NoDelay

指定发送数据时是否使用Nagle算法,对于实时性要求高的游戏,该值需要设置成false。Nagle是一种节省网络流量的机制,默认情况下,TCP会使用Nagle算法去发送数据。

TTL

TTL指发送的IP数据包的生存时间值(Time To Live, TTL)​。TTL是IP头部的一个值,该值表示一个IP数据报能够经过的最大的路由器跳数。发送数据时,TTL默认为64

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

ReuseAddress

ReuseAddress即端口复用,让同一个端口可被多个socket使用。一般情况下,一个端口只能由一个进程独占,假设服务端程序都绑定了1234端口,若开启两个服务端程序,虽然,第一个开启的程序能够成功绑定端口并监听,但第二个程序会提示“端口已经在使用中”​,无法绑定端口。

当服务端程序崩溃,但它持有的Socket不会被立马释放,这时候重启服务器就会遇到“端口已经在使用中”的情形。等到Socket被释放后(这个过程可能要十几分钟时间)​,服务端才能成功重启。

设置端口复用使用socket的SetSocketOption方法,代码如下所示。

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

LingerState

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

客户端调用Close()关闭Socket连接(客户端或服务端关闭连接都是同样的流程,服务端主动关闭连接同理)​,这时,客户端会给服务端发送FIN信号(①)​,然后进入等待。当服务端收到FIN信号时,会返回一个长度为0的数据,然后向客户端回应信息(②)​。这也是为什么关闭连接时,对端Receive会收到0个数据。如果服务端不做处理,客户端将会持续等待。

服务端中,会使用下面的代码处理客户端主动关闭连接,即在收到长度为0的消息后,调用clientfd.Close()关闭连接。

    public static void ReceiveCallback(IAsyncResult ar){
        ……
        int count = clientfd.EndReceive(ar);
        //客户端关闭
        if(count == 0){
            clientfd.Close();
            ……
            return;
        }
        ……
    }

服务端在调用Close后,它向客户端发送FIN信号(③)​,然后等待客户端回应。当服务端收到客户端的回应信息时,它会释放socket资源,真正完成关闭连接的流程。对客户端来说,它在收到服务端的FIN(③)信号后,会进入一个称为TIME_WAIT的状态,等待一段时间后(Windows下默认为4分钟)​,才会释放socket资源,真正完成关闭连接的流程。TIME_WAIT状态的意义在于,如果网络状况不好,服务端迟迟没有收到客户端回应的信号(④)​,那它会重发FIN信号(③)​,客户端socket需要维持一段时间,以回应重发的信号,确保对方有很大概率能够收到回应信号(④)​。

这种机制可以让服务端在关闭连接前处理尚未完成的事情,例如,假设收到客户端FIN信号时,服务端socket处于图片显示的状态,即发送缓冲区还有尚未发送的数据,那么直接调用Close关闭连接,缓冲区中的数据将被丢弃。这种关闭方式很暴力,因为对端可能还需要这些数据

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

其中的LingerOption带有两个参数。第一个参数是LingerState.Enabled,代表是否启用LingerState,只有设置为true才能生效。第二个参数是LingerState.LingerTime,指定超时时间。如果超时时间大于0(比如10秒)​,操作系统会尝试发送缓冲区中的数据,但如果网络状况不好,超过10秒还没有发完,它还是会强制关闭连接。如果LingerState.LingerTime设置为0,系统会一直等到数据发完才关闭连接,无论等待多长时间。开启LingerOption能够在一定程度上保证发送数据的完整性

心跳机制

TCP有一个连接检测机制,就是如果在指定的时间内没有数据传送,会给对端发送一个信号(通过SetSocketOption的KeepAlive选项开启)​。对端如果收到这个信号,回送一个TCP的信号,确认已经收到,这样就知道此连接通畅。如果一段时间没有收到对方的响应,会进行重试,重试几次后,会认为网络不通,关闭socket。

    Socket.SetSocketOption(SocketOptionLevel.Socket,  SocketOptionName.KeepAlive,
true)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值