TCP与UDP
通信类的协议比较复杂。
java.net
包中包含的类和解耦,提供低层次的通信细节,可以直接使用这些类和接口,来专注于网络程序考法,而不考虑通信的细节
java.net
包中提供了两种常见的网络协议的支持:
UDP
用户数据报协议(User Datagram Protocol)TCP
传输控制协议(Transmission Control Protocol)
TCP协议与UDP协议
TCP协议:
- TCP协议进行通信的两个应用进程:客户端、服务端。
- 使用TCP协议前,须先
建立TCP连接
,形成基于字节流的传输数据通道 - 传输前,采用“三次握手”方式,点对点通信,是
可靠的
- TCP协议使用
重发机制
,当一个通信实体发送一个消息给另一个通信实体后,需要收到另一个通信实体确认信息,如果没有收到另一个通信实体确认信息,则会再次重复刚才发送的消息。
- TCP协议使用
- 在连接中可进行
大数据量的传输
- 传输完毕,需
释放已建立的连接,效率低
UDP协议:
- UDP协议进行通信的两个应用进程:发送端、接收端。
- 将数据、源、目的封装成数据包(传输的基本单位),
不需要建立连接
- 发送不管对方是否准备好,接收方收到也不确认,不能保证数据的完整性,故是
不可靠的
- 每个数据报的大小限制在
64K
内 - 发送数据结束时
无需释放资源,开销小,通信效率高
- 适用场景:音频、视频和普通数据的传输。例如视频会议
TCP生活案例:打电话
UDP生活案例:发送短信、发电报
三次握手
- 第一次握手,客户端向服务器端发送TCP连接的请求
- 第二次握手,服务器端发送针对客户端TCP连接请求的确认
- 第三次握手,客户端发送确认的确认。
1、客户端会随机一个初始序列号seq=x,设置SYN=1 ,表示这是SYN握手报文。然后就可以把这个 SYN 报文发送给服务端了,表示向服务端发起连接,之后客户端处于
同步已发送
状态。2、服务端收到客户端的 SYN 报文后,也随机一个初始序列号(seq=y),设置ack=x+1,表示收到了客户端的x之前的数据,希望客户端下次发送的数据从x+1开始。
设置 SYN=1 和 ACK=1。表示这是一个SYN握手和ACK确认应答报文。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于同步已接收
状态。3、客户端收到服务端报文后,还要向服务端回应最后一个应答报文,将ACK置为 1 ,表示这是一个应答报文
ack=y+1 ,表示收到了服务器的y之前的数据,希望服务器下次发送的数据从y+1开始。
最后把报文发送给服务端,这次报文可以携带数据,之后客户端处于 连接已建立 状态。服务器收到客户端的应答报文后,也进入连接已建立
状态。
完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛,例如下载文件、浏览网页等
四次挥手
TCP协议中,在发送数据结束后,释放连接时需要经过四次挥手。
- 第一次挥手:客户端向服务器端提出结束连接,
让服务器做最后的准备工作
。此时,客户端处于半关闭状态,即表示不再向服务器发送数据了,但是还可以接受数据。 - 第二次挥手:服务器接收到客户端释放连接的请求后,
会将最后的数据发给客户端
。并告知上层的应用进程不再接收数据。 - 第三次挥手:服务器发送完数据后,会给客户端
发送一个释放连接的报文
。那么客户端接收后就知道可以正式释放连接了。 - 第四次挥手:客户端接收到服务器最后的释放连接报文后,要
回复一个彻底断开的报文
。这样服务器收到后才会彻底释放连接。这里客户端,发送完最后的报文后,会等待2MSL,因为有可能服务器没有收到最后的报文,那么服务器迟迟没收到,就会再次给客户端发送释放连接的报文,此时客户端在等待时间范围内接收到,会重新发送最后的报文,并重新计时。如果等待2MSL后,没有收到,那么彻底断开。
1、客户端打算断开连接,向服务器发送FIN报文(FIN标记位被设置为1,1表示为FIN,0表示不是),FIN报文中会指定一个序列号,之后客户端进入FIN_WAIT_1状态。也就是客户端发出连接释放报文段(FIN报文),指定序列号seq = u,主动关闭TCP连接,等待服务器的确认。
2、服务器收到连接释放报文段(FIN报文)后,就向客户端发送ACK应答报文,以客户端的FIN报文的序列号 seq+1 作为ACK应答报文段的确认序列号ack = seq+1 = u + 1。接着服务器进入CLOSE_WAIT(等待关闭)状态,此时的TCP处于半关闭状态(下面会说什么是半关闭状态),客户端到服务器的连接释放。客户端收到来自服务器的ACK应答报文段后,进入FIN_WAIT_2状态。
3、服务器也打算断开连接,向客户端发送连接释放(FIN)报文段,之后服务器进入LASK_ACK(最后确认)状态,等待客户端的确认。服务器的连接释放(FIN)报文段的FIN=1,ACK=1,序列号seq=m,确认序列号ack=u+1。
4、客户端收到来自服务器的连接释放(FIN)报文段后,会向服务器发送一个ACK应答报文段,以连接释放(FIN)报文段的确认序号 ack 作为ACK应答报文段的序列号 seq,以连接释放(FIN)报文段的序列号 seq+1作为确认序号ack。
之后客户端进入TIME_WAIT(时间等待)状态,服务器收到ACK应答报文段后,服务器就进入CLOSE(关闭)状态,到此服务器的连接已经完成关闭。客户端处于TIME_WAIT状态时,此时的TCP还未释放掉,需要等待2MSL后,客户端才进入CLOSE状态。
通信模型
客户端和服务端实现文本的传输
@Test
//客户端
public void TCPTest1() throws IOException {
//创建一个Socket
InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
int port = 8989; //声明端口号
Socket socket = new Socket(inetAddress,port);
//发送数据
OutputStream os = socket.getOutputStream();
os.write("这里是客户端".getBytes());//getBytes将字符串转换为字符数组
//关闭Socket
socket.close();
os.close();
}
@Test
//服务端
public void TCPTest2() throws IOException {
//创建一个ServerSocket
int port = 8989;
ServerSocket serverSocket = new ServerSocket(port);
//调用accept(),接受客户端的Socket
Socket socket = serverSocket.accept(); //阻塞式方法
System.out.println("服务端已经开启");
//接受数据
InputStream is = socket.getInputStream();
byte [] buffer = new byte[1024];
int len;
while((len = is.read(buffer)) != -1){
String str = new String(buffer,0,len);
System.out.println(str);
}
System.out.println("\n数据接受完毕");
//关闭Socket、ServerSocket
socket.close();
serverSocket.close();
is.close();
}
客户端发送文件给服务端,服务端将文件保存到本地
@Test
//客户端
public void TcpTest2() throws IOException {
//创建Socket
InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
int port = 9090;
try (Socket socket = new Socket(inetAddress, port)) {
//创建File的实例、FileInputStream的实例
File file = new File("test.png");
FileInputStream fis = new FileInputStream(file);
//通过Socket获取输出流
OutputStream os = socket.getOutputStream();
}
//关闭Socket和相关的流
}
@Test
//服务端
public void server() throws IOException {
//创建ServerSocket
int port = 9090;
ServerSocket serverSocket = new ServerSocket(port);
//接受来自于客户端的socket.accept()
Socket socket = serverSocket.accept();
//通过Socket获取一个输入流
InputStream is = socket.getInputStream();
//创建File类的实例、FileOutputStream
File file = new File("test_copy1.png");
FileOutputStream fos = new FileOutputStream(file);
//读写过程,进行保存
byte []buffer = new byte[1024];
int len;
while((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
}
System.out.println("数据接收完毕");
//关闭相关的Socket和流
fos.close();
is.close();
socket.close();
}