TCP协议
TCP(Transmission Control Protocol:传输控制协议),是面向连接的传输层协议,TCP层是位于IP层之上,应用层之下的中间层,不同的主机的应用层之间进程需要可靠的,向管道一样的连接,但是IP层不提供这种流机制,而提供的是不可靠的包交换。 TCP协议采用的是字节流传输数据。
TCP协议特点
1、面向连接:通信之前建立连接,通信结束断开连接。 2、每一条TCP连接只能是点对点的(一对一) 3、提供了可靠的交付服务,通过TCP连接传输的数据,无差错,不丢失,不重复。 4、提供全双工通信。 5、面向字节流,程序虽然和TCP交互是一次一个数据段,但是接收到的数据看成是连成一串无结构的字节流。 6、TCP首部20字节。
TCP编程
ServerSocket面向TCP的通信,主要是用来接收accept客户端的连接的socket Socket:面向TCP的通信,主要是客户端来连接服务端(Connection) 服务端:
ServerSocket ss =null;
try {
ss = new ServerSocket(6666);
Socket socket = ss.accept();
System.out.println("有新客户端的连接");
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (true) {
String msg = in.readLine();
System.out.println("客户端接收到的数据:"+msg);
if ("exit".equals(msg)) break;
}
in.close();
socket.close();
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
客户端:
Socket socket = new Socket();
socket.connect(new InetSocketAddress("127.0.0.1",6666));
OutputStream os = socket.getOutputStream();
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
while (true) {
System.out.print("请输入内容:");
String msg = in.readLine();
if (msg == null || "".equals(msg.trim())) continue;
os.write((msg+"\n").getBytes());
os.flush();
if ("exit".equals(msg)) break;
}
os.close();
in.close();
socket.close();
TCP报文格式
TCP首部的报文格式以及各个字段的意思: 16位的源端口: 端口号是用来表示特定主机上的唯一进程,源端口就是发送主机上应用占用的端口。 IP地址是在传输层用阿里标识网络中不同主机,端口则标识主机上的唯一应用,socket套接字就是IP地址加端口号共同组成
16位的目的端口: 端口号是用来表示特定主机上的唯一进程,目的端口就是接收主机上应用占用的端口。 端口的表示的个数2^16
32位的序号: 表示这个报文段中第一个数据字节序号,如果将字节流看做是两个应用程序间的单向流动,则TCP用序号对每个字节进行计数,用来保证到达数据顺序的编号,所以这个字段需要比较大的存储
32位的确认号: 确认序号是下一个期望接收的TCP的分段号,相当于是对对方所发送的并且已经被本方所正确接收的分段的确认,井道ACK标志=1时有效,确认号表示期望下一个字节的序号
4位数据偏移(报文首部): 以32位(4个字节)字长为单位,在不存在可选字段时报头长度是20字节,可选字段的长度可变的,以4字节为一个字长,2^4=15即最大是60个字节,
6位保留位: 6位,必须为0
标志位: 标志位占有6个比特位,有6个标志位,每个标志位值是0或1两种情况,1表示该标志位有效,依次为:URG、ACK、PSH、RST、SYN、FIN 具体说明: URG:该位为1说明表示TCP包的紧急指针域有效,用来保证TCP连接不被中断,并督促上层应用赶快处理这些数据 ACK:该位置为1表示当前包是确认包,确认号有效,一般发送方发送数据给接收方,接收方要告诉发送方已接收到数据,此时接收方就会给定一个确认,即将ACK置为1,且填充确认号表明下一个要接受的数据序号 PSH:接收方应尽快将这个报文交给应用层,即Push,push操作就是在数据包到达接收端以后,立即创送给应用层,而不是在缓冲区排队 RST:连接复位,复位因主机奔溃或者其他原因而出现了连接出错,也可以用拒绝非法的分段或者拒绝连接请求 SYN:是一个同步的序号,通常和ACK合用来建立连接,也就是常说的三次握手 FIN:在连接结束后需要断开连接,这个字段表示发送方已经到达数据末尾,及即双方数据传输完成,将标志位置为1,连接将被断开,将开始四次挥手断开连接过程
16位窗口: TCO的流量控制由连接的每一端通过声明窗口的大小来提供,窗口的大小为字节数,起始与确认序号字段确认的值,这个值是接收端期望接收的字节,是一个16个bit字段,窗口的大小最大65565字节
16位校验和: 用于对分段的首部和数据进行校验,正常情况下一般为0,用于传输层差错校验
16位紧急指针: 只有当标志位URG置为1时有效,紧急指针是一个正的偏移量,和序号字段中的值相加表示紧急指针的最后一个字节的序号,TCP的紧急方式是发送端向另一端发送紧急数据的一种方式
TCP是面向连接的协议,因此每个TCP连接都有3个阶段:连接建立,数据传输和连接释放。
三次握手
数据传输之前的连接建立过程就是三次握手 状态转换和数据包内容 第一次握手 客户端发送连接请求报文到服务端,客户端进入SYN-SENT状态,等待服务端的确认。 第一次数据包内容(SYN=1,seq=x) 第二次握手 服务端接收到连接请求报文,如果同意连接,向客户端发回确认报文段。 服务端的状态由LISTEN状态进入SYN-RECV状态 第二次数据报文首部(SYN=1,ACK=1,ack=x+1,seq=y) 第三次握手 客户端接收到服务器的确认报文后,在向服务端给出确认报文,此时客户端和服务端在收到第三次消息后都进入到ESTAB-LISHED状态,该状态表示是全双工通信,就可以正常通信了。 第三次数据报文首部(ACK=1,ack=y+1,seq=x+1)
四次挥手
在传输数据结束后需要进行断开连接,断开连接需要进行4次数据网络传输,即4次挥手: 注意:MSL:最大生存时间 ,任何一个报文段在网络中都具有生存时间,在发送方给定MSL,在网络中每进入一个路由MSL-1,当msl=0时还没有到达目的地就会丢弃。
第一次挥手: 客户端主动发起断开连接操作,用来关闭客户端到服务端的数据传输。 客户端发起第一次连接数据报文段后,状态由ESTAB-LISTHED状态进入FIN-WAIT-1状态, 第一次数据包首部信息(FIN=1,seq=u) 第二次挥手: 服务端响应客户端的请求确认消息,就进行第二次数据包发送。 服务端发起第二次数据报文段后,状态由ESTAB-LISTHED状态CLOASE-WAIT状态, 第二次数据包首部信息(ACK=1,seq=v,ack=u+1) 客户端接收到消息后状态就由FIN-WAIT-1状态进入FIN-WAIT-2状态,此时客户端无法往服务端发送消息,但服务端可以继续给客户端单向发送数据,处于单双工状态 第三次挥手: 服务端关闭客户端的连接,发送一个FIN给客户端。 服务端发起第三次数据报文段后,服务端状态由CLOASE-WAIT状态进入到LAST-ACK状态 第三次数据报文段首部信息(FIN=1,ACK=1,ack=u+1,seq=w) 第四次挥手: 客户端发回ACK报文确认。 在客户端发起第四次数据报文段后,客户端的状态FIN-WAIT-2状态进入TIME-WAIT状态,注意;并没有直接进入close状态,而是在等待2MSL时间后自动进入close状态 服务端接收到第四次数报文后状态,由LAST-ACK状态直接进入close状态 第四次数据报文段首部信息为(ACK=1,seq=u+1,ack=w+1)
扩展问题: 问题1:TCP能改成两次握手嘛? 不能,防止已经失效的连接请求有传送到服务端,因而产生错误。
第三次握手主要为了防止已失效的连接请求报文段突然又传输到了服务端,导致产生问题。
比如客户端A发出连接请求,可能因为网络阻塞原因,A没有收到确认报文,于是A再重传一次连接请求。 连接成功,等待数据传输完毕后,就释放了连接。 然后A发出的第一个连接请求等到连接释放以后的某个时间才到达服务端B,此时B误认为A又发出一次新的连接请求,于是就向A发出确认报文段。 如果不采用三次握手,只要B发出确认,就建立新的连接了,此时A不会响应B的确认且不发送数据,则B一直等待A发送数据,浪费资源。
问题2:为什么需要TIME-WAIT状态? TIME-WAIT是需要等待2MSL 1、保证A发送的最后一个ACK报文段能够到达B。 这个ACK报文段有可能丢失,B收不到这个确认报文,就会超时重传连接释放报文段,然后A可以在2MSL时间内收到这个重传的连接释放报文段,接着A重传一次确认,重新启动2MSL计时器,最后A和B都进入到CLOSED状态,若A在TIME-WAIT状态不等待一段时间,而是发送完ACK报文段后立即释放连接,则无法收到B重传的连接释放报文段,所以不会再发送一次确认报文段,B就无法正常进入到CLOSED状态。 2、防止已失效的连接请求报文段出现在本连接中。 A在发送完最后一个ACK报文段后,再经过2MSL,就可以使这个连接所产生的所有报文段都从网络中消失,使下一个新的连接中不会出现旧的连接请求报文段。
TCP扩展问题
TCP如何发送数据
消息是由发送方产生,发送方会首先将数据放入到发送缓冲区,然后发送的时候会从缓冲区中取,接着消息从发送方的用户空间传入内核空间借助于网络传输介质完成传输,消息在发送到接收方的内核空间,接收方如果想要读取是从内容空间读取到用户空间,接收方会将接收到的数据放入接收缓冲区,让后应用程序使用的时候到缓冲区去取。 TCP发送数据: 因为TCP本身传输的数据包大小就有限制,所以应用发出的消息包过大,TCP会把应用消息包拆分为多个TCP数据包发送出去。Negal算法的优化,当应用发送数据包太小,TCP为了减少网络请求次数的开销,它会等待多个消息包一起,打成一个TCP数据包一次发送出去。 TCP接收数据: 因为TCP缓冲区里的数据都是字符流的形式,没有明确的边界,因为数据没边界,所以应用从TCP缓冲区中读取数据时就没办法指定一个或几个消息一起读,而只能选择一次读取多大的数据流,而这个数据流中就可能包含着某个消息包的一部分数据。
TCP半包、粘包产生的原因: TCP是面向连接的传输协议,TCP传输的数据是以流形式传输,流数据是没有明确的开始结尾边界,所以TCP也没办法判断那一段流属于哪一个消息,所以TCP的粘包、半包都是在应用层解决。
滑动窗口协议
滑动窗口协议(Sliding window protocol):属于TCP协议的一种应用,用于网络数据传输时流量控制以避免拥塞发生,协议允许发送方在停止并等待确认前发送的多个数据分组,由于发送方不必每发一个分组就停下来等待确认,因此该协议可以加速数据传输,提高网络吞吐量。 滑动窗口本质是描述接收方的TCP数据包缓冲区大小的数据,发送方根据这个数据来计算自己最多能发送多长数据,如果发送方收到的接收方的窗口大小为0的TCP报文段,那么发送方将停止发送数据,等到接收方发送窗口大小不为0的数据包的到来。 具体说明:假如A与B建立TCP连接,滑动窗口的作用 1、B端来不及处理所有的数据(控制不同速率主机间的同步),这时,A通过B的通知的接收窗口而减弱数据的发送 2、B端来得及处理接收数据,但是在A与B之间某处C,使得AB之间的整体带宽性能较差,此时,A端根据拥塞控制处理策略(慢启动等)来更新窗口,以决定数据的发送。
TCP建立连接初期,B告诉A自己接收窗口的大小,比如为‘20’, 根据B给出的窗口,A构造出自己的窗口, A发送11字节后,发送窗口的位置不变,B接收到的数据乱序的。 A发了11个字节数据,只有当A成功发送了数据,即发送的数据得到了B的确认之后,才会移动滑动窗口离开已发送的数据;同时B则确认连续的数据分组,对于乱序的分组则先接收下来,避免网络重复传递: A收到新的确认号,窗口向前滑动: 发送窗口内的序号都属于已发送但未被确认。
流量控制,主要是接收方传送数据给发送方,告诉其发送数据不要太快或者加速,是一种端到端的控制, 主要的方式就是返回的ACK中会包含自己的接收窗口的大小,并且利用大小来控制发送方的数据发送: 这里面涉及到一种情况,如果B已经告诉A自己的缓冲区已满,于是A停止发送数据;等待一段时间后,B的缓冲区出现了富余,于是给A发送报文告诉A我的rwnd大小为400,但是这个报文不幸丢失了,于是就出现A等待B的通知||B等待A发送数据的死锁状态。为了处理这种问题,TCP引入了持续计时器(Persistence timer),当A收到对方的零窗口通知时,就启用该计时器,时间到则发送一个1字节的探测报文,对方会在此时回应自身的接收窗口大小,如果结果仍未0,则重设持续计时器,继续等待。
TCP滑动窗口技术通过动态改变窗口大小来调节两台主机间数据传输。 每个TCP/IP主机支持全双工数据传输,因此TCP有两个滑动窗口:一个用于接收数据,另一个用于发送数据。 TCP使用ACK确认技术,其确认号指的是下一个所期待的字节。 假定发送方设备以每一次三个数据包的方式发送数据,也就是说,窗口大小为3。发送方发送序列号为1、2、3的三个数据包,接收方设备成功接收数据包,用序列号4确认。发送方设备收到确认,继续以窗口大小3发送数据。当接收方设备要求降低或者增大网络流量时,可以对窗口大小进行减小或者增加,本例降低窗口大小为2,每一次发送两个数据包。当接收方设备要求窗口大小为0,表明接收方已经接收了全部数据,或者接收方应用程序没有时间读取数据,要求暂停发送。发送方接收到携带窗口号为0的确认,停止这一方向的数据传输。
拥塞控制
发送方维护了一个叫做拥塞控制(cwnd)的状态变量,大小取决于网络的拥塞情况,动态的变化这,发送方让自己的发送窗口等于拥塞窗口,如果考虑接收方的接收能力,即发送方发送窗口可能小于拥塞窗口。 发送方控制拥塞窗口的原则是:只要网络没有出现拥塞,拥塞窗口就再增大一些,以便把更多的分组发送出去,但只要网络出现拥塞,就把拥塞窗口减小一些,以减少注入到网络中的分组数。 拥塞控制就是防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。 常用的方法就是:
- 慢开始、拥塞避免
- 快重传、快恢复
慢开始、拥塞避免 发送方发送数据是从慢开始执行的 -1. 发送方维持一个叫做“拥塞窗口”的变量,该变量和接收窗口共同决定了发送者的发送窗口; -2. 当主机开始发送数据时,避免一下子将大量字节注入到网络,造成或者增加拥塞,选择发送一个1字节的试探报文; -3. 当收到第一个字节的数据的确认后,就发送2个字节的报文; -4. 若再次收到2个字节的确认,则发送4个字节,依次递增2的指数级; -5. 最后会达到一个提前预设的“慢开始门限”ssthresh,比如24,即一次发送了24个分组,此时遵循下面的条件判定: *1. cwnd < ssthresh, 继续使用慢开始算法; *2. cwnd > ssthresh,停止使用慢开始算法,改用拥塞避免算法; *3. cwnd = ssthresh,既可以使用慢开始算法,也可以使用拥塞避免算法; -6. 所谓拥塞避免算法就是:每经过一个往返时间RTT就把发送方的拥塞窗口+1,即让拥塞窗口缓慢地增大,按照线性规律增长; -7. 当出现网络拥塞,比如丢包时,将慢开始门限设为原先的一半,然后将cwnd设为1,执行慢开始算法(较低的起点,指数级增长); 上述方法的目的是在拥塞发生时循序减少主机发送到网络中的分组数,使得发生拥塞的路由器有足够的时间把队列中积压的分组处理完毕。慢开始和拥塞控制算法常常作为一个整体使用,
而快重传和快恢复则是为了减少因为拥塞导致的数据包丢失带来的重传时间,从而避免传递无用的数据到网络。 快重传的机制是: -1. 接收方建立这样的机制,如果一个包丢失,则对后续的包继续发送针对该包的重传请求; -2. 一旦发送方接收到三个一样的确认,就知道该包之后出现了错误,立刻重传该包; -3. 此时发送方开始执行“快恢复”算法: *1. 慢开始门限减半; *2. cwnd设为慢开始门限减半后的数值; *3. 执行拥塞避免算法(高起点,线性增长);
确认机制(ACK) ack表示期望下次接收到的序号。 那么ack是如何算出来的呢,就是通过收到的序号,和数据长度相加得来。 假设A收到B过来的数据(seq = 5,len = 15)。len表示数据长度。 那么A就会回复B,“刚才的数据我已经收到了,你接下来就发序号为20的包给我吧”。这样就保证了数据不会乱序。 综上,确认号就是下一次将要收到包的序号。同时也等于发送方的序号+数据长度(确认号在ACK标志位有效时才有用。)
TCP协议如何保证数据的可靠性
1、校验和 TCP检验和的计算与UDP一样,在计算时要加上12byte的伪首部,检验范围包括TCP首部及数据部分,但是UDP的检验和字段为可选的,而TCP中是必须有的。计算方法为:在发送方将整个报文段分为多个16位的段,然后将所有段进行反码相加,将结果存放在检验和字段中,接收方用相同的方法进行计算,如最终结果为检验字段所有位是全1则正确(UDP中为0是正确),否则存在错误。 2、序列号 TCP将每个字节的数据都进行了编号,这就是序列号。 序列号的作用: a) 保证可靠性(当接收到的数据总少了某个序号的数据时,能马上知道) b) 保证数据的按序到达 c) 提高效率,可实现多次发送,一次确认 d) 去除重复数据 数据传输过程中的确认应答处理、重发控制以及重复控制等功能都可以通过序列号来实现 。 3、确认应答机制(ACK) TCP通过确认应答机制实现可靠的数据传输。在TCP的首部中有一个标志位——ACK,此标志位表示确认号是否有效。接收方对于按序到达的数据会进行确认,当标志位ACK=1时确认首部的确认字段有效。进行确认时,确认字段值表示这个值之前的数据都已经按序到达了。而发送方如果收到了已发送的数据的确认报文,则继续传输下一部分数据;而如果等待了一定时间还没有收到确认报文就会启动重传机制。 4、超时重传机制/快重传 当报文发出后在一定的时间内未收到接收方的确认,发送方就会进行重传(通常是在发出报文段后设定一个闹钟,到点了还没有收到应答则进行重传) 当然,未收到确认不一定就是发送的数据包丢了,还可能是确认的ACK丢了: 当主机B返回应答,因为网络拥堵等原因在传送途中丢失,没有到达主机A。主机A会等待一段时间,若在等待的时间间隔内始终未能收到这个确认应答,主机B将第二次发送已接收数据的确认应答,由于主机B其实已经收到1~100的数据,当在有相同的数据到达时它会放弃。 5、连接管理机制连接可靠性 连接管理机制即TCP建立连接时的三次握手和断开连接时的四次挥手。 a) 拥塞控制 拥塞避免、慢开始等算法实现,上文以对拥塞控制这部分做出详细讲解,此处不叙述。 b) 流量控制 通过滑动窗口进行,上文以对滑动窗口做出详细讲解。
UDP协议
UDP(User Datagram Protocol):用户数据报服务,是属于传输层的无连接的协议。
UDP特点:
1、无连接 2、尽最大努力交付 3、面向报文 4、无拥塞控制方案 5、支持一对一、一对多、多对多的交互通信 6、首部开销小,只有四个字段,占8个字节
UDP首部格式
UDP数据报有两个字段,数据字段和首部字段,首部是8个字节,由四个字段组成,每个字段都是两个字节: 源端口(16位):在需要对方回信时可选用,不需要时可全为0 目的端口(16位):在终点交付报文时必须使用到 长度(16位):UDP的数据报长度(单位:字节),其最小值为8,仅包含首部 校验和:检验UDP用户数据报在传输中是否有错,有错则丢弃
UDP包的长度问题? UDP包的长度按照首部长度能表示的范围2^ 16-1个字节,由于首部固定栈8个字节,在网络层IP包首部占20字节,UDP的包理论大小长度:2^16-1-8-20=65507. 长度其实还受下层IP层和数据链路层的制约 MTU概念(最大传输单元)在链路层长度在46-1500字节之间,如果数据包过大,网络允许在IP层进行分片。
UDP编程
UDP编程需要使用数据报DatagramSocket 服务端:
import java.io.IOException;
import java.net.*;
public class UDPServer2022 {
public static void main(String[] args) throws IOException {
DatagramSocket datagramSocket = new DatagramSocket();
String info = "hello Java";
DatagramPacket datagramPacket = new DatagramPacket(info.getBytes(), info.length(), InetAddress.getByName("localhost"), 6666);
datagramSocket.send(datagramPacket);
System.out.println("已发送数据");
}
}
客户端:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPClient2022 {
public static void main(String[] args) throws IOException {
DatagramSocket datagramSocket = new DatagramSocket(6666);
byte[] bytes = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(bytes, 1024);
System.out.println("等待接收数据");
datagramSocket.receive(datagramPacket);
String info = new String(datagramPacket.getData(), 0, datagramPacket.getLength());
System.out.println(info);
}
}
TCP和UDP的区别?
1、TCP面向连接;UDP是无连接的,即发送数据之前不需要建立连接。 2、TCP提供可靠的服务;UDP不保证可靠交付。 3、TCP面向字节流,把数据看成一连串无结构的字节流;UDP是面向报文的。 4、TCP有拥塞控制;UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如实时视频会议等)。 5、每一条TCP连接只能是点到点的;UDP支持一对一、一对多、多对一和多对多的通信方式。 6、TCP首部开销20字节;UDP的首部开销小,只有8个字节。
TCP:面向连接、可靠的,流式服务 UDP:无连接、不可靠的,数据报服务
|