文章目录
传输层:TCP和UDP
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)
TCP编程Demo
客户端
@Test
public void client() {
Socket socket = null;
OutputStream os = null;
try {
InetAddress inetAddress = InetAddress.getByName("localhost");
socket = new Socket(inetAddress, 8889);
os = socket.getOutputStream();
os.write("哈喽哈喽".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
服务端
@Test
public void server() {
ServerSocket serverSocket = null;
Socket socket = null;
InputStream inputStream = null;
ByteArrayOutputStream baos = null;
try {
serverSocket = new ServerSocket(8889);
socket = serverSocket.accept();
inputStream = socket.getInputStream();
// byte[] bytes = new byte[1024];
// int len = 0;
// if ((len = inputStream.read(bytes)) != -1) {
// String s = new String(bytes, 0, len);
// System.out.print(s);
// }
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[5];
int len = 0;
while ((len = inputStream.read(buffer)) != -1) {
baos.write(buffer,0,len);
}
System.out.println(baos.toString());
System.out.println("来自客户端" + socket.getInetAddress().getHostName() + "发来的消息");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (baos != null) {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
执行结果:
注意先启动服务端,再启动客户端
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位,必须为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位窗口
TCP的流量控制由连接的每一端通过声明窗口的大小来提供,窗口的大小为字节数,起始与确认序号字段确认的值,这个值是接收端期望接收的字节,是一个16个bit字段,窗口的大小最大65565字节 -
16位校验和
用于对分段的首部和数据进行校验,正常情况下一般为0,用于传输层差错校验 -
16位紧急指针
只有当标志位URG置为1时有效,紧急指针是一个正的偏移量,和序号字段中的值相加表示紧急指针的最后一个字节的序号,TCP的紧急方式是发送端向另一端发送紧急数据的一种方式
TCP是面向连接的协议,因此每个TCP连接都有3个阶段:连接建立,数据传输和连接释放。
三次握手
数据传输之前的连接建立过程就是三次握手
假设运行在一台主机(客户)上的一个进程想与另一台主机(服务器)上的一个进程建立一条连接,客户应用进程首先通知客户TCP,他想建立一个与服务器上某个进程之间的连接,客户中的TCP会用以下步骤与服务器中的TCP建立一条TCP连接:
第一次握手
客户端发送连接请求报文到服务端,客户端进入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:三次握手可以改成两次握手吗?
答:不能,防止已经失效的连接请求再次传送到服务端,因而产生错误,三次握手的最主要目的就是保证连接是双工的。
解释1: 假定是两次握手,如果客户端A向服务端B发送一个SYN连接请求后,突然进入close状态,当B收到连接请求后,恢复一个SYN并确认完成两次握手,但由于A已经关闭,A和B之间并不能进行通信。
解释2: 假定是两次握手,如果客户端A向服务端B发送了一个SYN连接请求,但是由于网络路由问题经过一段时间迟迟没有到达B,此时A又重新发送了一个连接请求,B接收到第二次的连接请求后对A进行回复确认,完成两次握手建立起连接,经过几次通信后,A和B四次挥手断开连接,断开连接后,B又收到了迟到的第一次连接请求,对于B来说,它不知道这是一个失效的连接请求,再次对A进行回复确认,A收到回复确认后,而此时A知道这是一个失效的连接请求,就不予理睬,但是对于B来说以为已经建立好的连接,就会一直等待A发信息,B的苦苦等待浪费了资源。
问题2:为什么需要TIME-WAIT状态?
TIME-WAIT是需要等待2MSL
1、为实现TCP这种全双工的连接的可靠的释放
这样可以防止让TCP最后发送的ACK包丢失。这种2msl等待的另一个结果是这个TCP连接在2msl等待期间,连接的端口是不能再被使用,只能在2msl结束后才能使用。
2、为使旧的数据包在网络因过期而消失
每个TCP报文段都有最大生存时间:MSL,任何报文段被丢弃前在网络中的最长的生存时间。
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中会包含自己的接收窗口的大小,并且利用大小来控制发送方的数据发送。
A向B发送数据,连接建立时,B告诉A:“我的rwnd=400(字节)”,设每一个报文段100B,报文段序号初始值为1。
这里面涉及到一种情况,如果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协议特点
UDP(User Datagram Protocol):用户数据报服务,是属于传输层的无连接的协议
UDP特点:
1、无连接
2、尽最大努力交付
3、面向报文
4、无拥塞控制方案
5、支持一对一、一对多、多对多的交互通信
6、首部开销小,只有四个字段,占8个字节
UDP包的长度问题
UDP包的长度按照首部长度能表示的范围216-1个字节,由于首部固定占8个字节,在网络层IP包首部占20字节,UDP的包理论大小长度:2^16-1-8-20=65507。
长度其实还受下层IP层和数据链路层的制约
MTU概念(最大传输单元)在链路层长度在46-1500字节之间,如果数据包过大,网络允许在IP层进行分片。
UDP编程
服务端
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class Server {
public static void main(String[] args) throws Exception {
//1、构建发送对象
DatagramSocket socket = new DatagramSocket();
//2、将数据打包成数据报
String info = "hello,我是UDP发来导弹~~~";
DatagramPacket packet = new DatagramPacket(info.getBytes(), info.getBytes().length, InetAddress.getByName("localhost"), 8888);
//3、发送数据报
socket.send(packet);
System.out.println("服务端发送数据报成功!");
}
}
客户端
import java.io.IOException;
import java.net.*;
public class Client {
public static void main(String[] args) throws IOException {
//1、创建UDP Socket接收服务端的数据
DatagramSocket socket = new DatagramSocket(8888);
byte[] buffer = new byte[4096];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
System.out.println("客户端等待接收数据!");
//2、接收数据
socket.receive(packet);
//3、读取数据
String info = new String(packet.getData(), 0, packet.getLength());
System.out.println(info);
}
}
测试结果
UDP首部格式
UDP校验
伪首部
只有在计算检验和时才出现,不向下传送也不向上递交。
17
:封装UDP报文的IP数据报首部协议字段是17。
UDP长度
: UDP首部8B+数据部分长度(不包括伪首部)。
在发送端:
1. 填上伪首部
2. 全o填充检验和字段
3. 全o填充数据部分(UDP数据报要看成许多4B的字串接起来)
4. 伪首部+首部+数据部分采用二进制反码求和
5. 把和求反码填入检验和字段
6. 去掉伪首部,发送
在接收端:
1. 填上伪首部
2. 伪首部+首部+数据部分采用:进制反码求和
3. 结果全为1则无差错,否则丢弃数据报/交给应用层附上出差错的警告
TCP和UDP的区别
TCP
:面向连接、可靠的,流式服务
UDP
:无连接、不可靠的,数据报服务
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/95476.html