Untiy 网络编程-深入了解TCP
1.从TCP到物理层
应用层:
应用层是给应用程序提供功能的。在发送{hello}的例子里面,计算机会把hello转化为二进制然后发送到传输层。
传输层:
在收到二进制数据后,传输层协议会对它进行一系列的加工,并提供数据流传送、可靠性校验,流量控制。又由于网络层的IP包大小有限制(65535),所以还需要有数据拆分的机制,把TCP包拆分为多个IP包。
网络层:
这一层会把IP包根据目的地址把本地的包路由到目的地。
网络接口层:
IP协议会继续封装成链路层协议做一些数据校验后,再通过物理介质传输到接收方。
2.数据传输流程
1)TCP连接的建立
TCP连接的过程也称为三次握手。
值得注意的是,在第三次握手时,可能由于网络原因,导致第三次握手要等待很久才能返回,因为底层的超时重传机制会导致Server没有收到第三次握手会不断重复发送第二次握手数据包。直到连接成功或者超出重试次数。当双方都ESTABLISHED则连接成功。
2)TCP数据传输
TCP传输,在发送方并不能确保数据被对方收到,于是发送会等待接收方回应。如果太长时间没有收到回应也会启用重传机制重发数据。
同时,发送数据时,TCP会考虑对方缓存区的容量,当对方满时会暂时停止发送数据。同时TCP还会根据返回数据的时间判断当前网络是否“通畅”,以减缓发送报文的间隔。
3)TCP连接终止
TCP的连接终止称为“四次挥手”
3.常用的TCP参数
1)ReceiveBufferSize:
指定了接收端缓冲区的大小,当接收端缓冲区满的时候,发送端会暂停发送数据。较大的缓冲区可以减少发送端暂停的概率,提高发送效率。
2)SendBufferSize:
指定了发送端缓冲区的大小。对于没有“完整发送数据的项目”(前一个博客),可以使用扩大发送区的方式优化项目性能。
3)NoDelay:
对于实时性要求较高的游戏,需要将该值设置为true。Socket在默认情况下使用的是Nagle算法。
对于传统的TCP连接,假设我们要发送{hello}。经过TCP/IP协议封装之后就成为了{ IP头|TCP头|hello }。总数据量变成了20+20+5=45个字节。假设现在用户频繁的一个一个的发送数据,总数据量就变成了41*5=205字节。比原来多了160字节。
Nagle算法机制在于,如果发送端多次发送包含少量字节的数据包时,发送端不会立马发送数据,而是累积到一定数量后再发送。这样虽然会提高网络传输效率,但是对实时性也会有影响。所以实时网络游戏要关闭Nagle算法,即打开NoDelay。
4)TTL:
TTL表示IP包所能经过的最大跳数。TTL的最大作用是防止IP包在网络中无限循环。在游戏开发中为了防止偏远地区的用户无法接收数据,可以尝试提高TTL值来解决问题。
5)ReuseAddress:
端口复用,即让同一个端口可以被多个socket使用。一般情况下,一个端口只能由一个进程独占,假设服务端程序都绑定了1234端口,若开启两个服务端程序,虽然,第一个程序能够成功绑定并监听,但是第二个会无法绑定并释放端口。我们知道TCP释放端口会经过大量的时间(四次挥手并等待重发)。或者当服务器崩溃时,Socket也不会立即释放,会等待大量时间重启。
而我们想要要求,当服务器崩溃时,能够立即重启。端口复用的用途就是,防止服务器重启时,之前绑定的端口还未释放或者程序突然退出导致没有释放的端口。如果使用端口复用就可以启动新的服务器进程时重新绑定新的端口。使用如下:
socket.SetSocketOption(SocketOptionLevel.Socket,SocketOptionName.ReuseAddress,true);
6)LingerState:
这个关键字是用来设置套接字的保持时间的。即四次挥手时,第二次到第三次之间的等待发送数据的时间。
4.心跳机制
TCP连接的正常端口是需要接收FIN报文的,但是如果网络状况不好,虽然已经断开了连接,但是对端依然会认为连接有效,依然占有资源。我们需要使用心跳机制来解决这个问题。
Socket默认的心跳机制只用将KeepAlive开启即可。但是该方法很鸡肋,默认的等待时间是两个小时。
socket.SetSocketOption(SocketOptionLevel.Socket,SocketOptionName.KeepAlive,true);