网络编程
一、网络编程的了解
1.要想实现网络通信,需要解决的三个问题:
- 问题1:如何准确地定位网络上一台或多台主机
- 问题2:如何定位主机上的特定的应用
- 问题3:找到主机后,如何可靠、高效地进行数据传输
2.实现网络传输的三个要素:(对应解决三个问题)
- 使用IP地址(准确地定位网络上一台或多台主机)
- 使用端口号(定位主机上的特定的应用)
- 规范网络通信协议(可靠、高效地进行数据传输)
3.通信要素1:IP地址
3.1 作用 IP地址用来给网络中的一台计算机设备做唯一的编号 3.2 IP地址分类 IP地址分类方式1 IPv4 (占用4个字节) IPv6 (占用16个字节) IP地址分类方式2 公网地址( 万维网使用)和 私有地址( 局域网使用。以192.168开头) 3.3 本地回路地址:127.0.0.1 3.4 域名:便捷的记录ip地址 www.baidu.com www.atguigu.com www.bilibili.com www.jd.com www.mi.com www.vip.com
4. 通信要素2:端口号
- 可以唯一标识主机中的进程(应用程序)
- 不同的进程分配不同的端口号
- 范围:0~65535
InetAddress的使用
5.1 作用
InetAddress类的一个实例就代表一个具体的ip地址。
5.2 实例化方式
InetAddress getByName(String host):获取指定ip对应的InetAddress的实例
InetAddress getLocalHost():获取本地ip对应的InetAddress的实例
5.3 常用方法
getHostName()
getHostAddress()
6.通信要素3:通信协议
6.1 网络通信协议的目的
为了实现可靠而高效的数据传输。
6.2 网络参考模型
OSI参考模型:将网络分为7层,过于理想化,没有实施起来。
TCP/IP参考模型:将网络分为4层:应用层、传输层、网络层、物理+数据链路层。事实上使用的标准。
二、TCP\UDP
1. TCP与UDP网络编程对比(熟悉)
1.1TCP
package com.leon.net_tcpandudp; import org.junit.Test; import java.io.*; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; /** * ClassName:ConnectTest3 * Package:com.leon.net_tcpandudp * Description: * * @Author: leon-->ZGJ * @Create: 2023/10/6 10:42 * @Version: 1.0 */ public class ConnectTest3 { @Test public void client(){ Socket socket = null; OutputStream os = null; FileInputStream fis = null; InputStream is = null ; ByteArrayOutputStream baos = null ; try { //创建请求连接 int port = 8989; InetAddress byName = InetAddress.getByName("127.0.0.1"); socket = new Socket(byName ,port); //实现数据输出 os = socket.getOutputStream(); //获取本地数据 File file = new File("01.jpg"); fis = new FileInputStream(file); //将数据写出 byte[] buffer = new byte[1024]; int len ; while ((len = fis.read(buffer)) != -1){ os.write(buffer,0,len); } System.out.println("数据传输完毕"); //告诉服务端结束输出 socket.shutdownOutput(); //读取服务端的数据 is = socket.getInputStream(); baos = new ByteArrayOutputStream(); byte[] buffer1 = new byte[5]; int len1 ; while ((len1 = is.read(buffer1)) != -1){ baos.write(buffer1,0,len1); } System.out.println(baos.toString()); } catch (IOException e) { throw new RuntimeException(e); } finally { //关闭资源 try { if(socket != null) socket.close(); } catch (IOException e) { throw new RuntimeException(e); } try { if(os != null) os.close(); } catch (IOException e) { throw new RuntimeException(e); } try { if(fis != null) fis.close(); } catch (IOException e) { throw new RuntimeException(e); } try { if(is != null) is.close(); } catch (IOException e) { throw new RuntimeException(e); } try { if(baos != null) baos.close(); } catch (IOException e) { throw new RuntimeException(e); } } } @Test public void server(){ ServerSocket serverSocket = null; Socket accept = null; InputStream inputStream = null; FileOutputStream fos = null; try { //创建服务端 int port = 8989; serverSocket = new ServerSocket(port); //获取客户端请求 accept = serverSocket.accept(); //读取数据 inputStream = accept.getInputStream(); //读取的数据写入本地 File file = new File("01_copy.jpg"); fos = new FileOutputStream(file); byte[] buffer = new byte[1024]; int len ; while ((len = inputStream.read(buffer)) != -1){ fos.write(buffer,0,len); } System.out.println("数据接收完毕"); //向客户端输出信息 OutputStream os = accept.getOutputStream(); os.write("图片我已经收到".getBytes()); } catch (IOException e) { throw new RuntimeException(e); } finally { //关闭资源 try { if(serverSocket != null) serverSocket.close(); } catch (IOException e) { throw new RuntimeException(e); } try { if(accept != null) accept.close(); } catch (IOException e) { throw new RuntimeException(e); } try { if(inputStream != null) inputStream.close(); } catch (IOException e) { throw new RuntimeException(e); } try { if(fos != null) fos.close(); } catch (IOException e) { throw new RuntimeException(e); } } } }
1.2UDP
package com.leon.net_tcpandudp; import org.junit.Test; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; /** * ClassName:UDPTest * Package:com.leon.net_tcpandudp * Description: * * @Author: leon-->ZGJ * @Create: 2023/10/6 20:47 * @Version: 1.0 */ public class UDPTest { @Test public void sender(){ //创建发送端 DatagramSocket ds = null; try { ds = new DatagramSocket(); //创建响应端的IP地址 InetAddress inetAddress = InetAddress.getByName("127.0.0.1"); int port = 8989; //创建存储数据的数组 byte[] bytes = "我是发送端".getBytes(); //创建数据包 DatagramPacket packet = new DatagramPacket(bytes,0,bytes.length,inetAddress,port); //发送数据包 ds.send(packet); } catch (IOException e) { throw new RuntimeException(e); } finally { //关闭资源 if(ds != null) ds.close(); } } @Test public void receiver(){ DatagramSocket ds = null; try { //创建接收端对象 //端口 int port = 8989; ds = new DatagramSocket(port); //接收数据 byte[] buffer = new byte[1024*64]; DatagramPacket dp = new DatagramPacket(buffer,0,buffer.length); //接收数据包 ds.receive(dp); //将数据输出 System.out.println(new String(dp.getData(),0,dp.getLength())); } catch (IOException e) { throw new RuntimeException(e); } finally { //关闭资源 if(ds != null) ds.close(); } } }
2.TCP三次握手 与 TCP四次挥手(熟悉)
2.1 三次握手
TCP 协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保
证连接的可靠。
第一次握手,客户端向服务器端发起 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 协议可以保证传输数据的安全,所以应用十分广
泛,例如下载文件、浏览网页等。
2.2 四次挥手
TCP 协议中,在发送数据结束后,释放连接时需要经过四次挥手。
第一次挥手:客户端向服务器端提出结束连接,让服务器做最后的准备工作。此时,客户端处于半关闭状态,即表示不再向服务器发送数据了,但是还可以接受数据。
第二次挥手:服务器接收到客户端释放连接的请求后,会将最后的数据发给客户端。并告知上层的应用进程不再接收数据。
第三次挥手:服务器发送完数据后,会给客户端发送一个释放连接的报文。那么客户端接收后就知道可以正式释放连接了。
第四次挥手:客户端接收到服务器最后的释放连接报文后,要回复一个彻底断开的报文。这样服务器收到后才会彻底释放连接。这里客户端,发送完最后的报文后,会等待 2MSL,因为有可能服务器没有收到最后的报文,那么服务器迟迟没收到,就会再次给客户端发送释放连接的报文,此时客户端在等待时间范围内接收到,会重新发送最后的报文,并重新计时。如果等待 2MSL 后,没有收到,那么彻底断开。
1、客户端打算断开连接,向服务器发送 FIN 报文(FIN 标记位被设置为1,1 表示为 FIN,0 表示不是),FIN 报文中会指定一个序列号,之后客户端进入 FINWAIT1 状态。也就是客户端发出连接释放报文段(FIN报文),指定序列号 seq = u,主动关闭 TCP 连接,等待服务器的确认。
2、服务器收到连接释放报文段(FIN 报文)后,就向客户端发送 ACK 应答报文,以客户端的 FIN 报文的序列号 seq+1 作为 ACK 应答报文段的确认序列号 ack = seq+1 = u + 1。接着服务器进入 CLOSEWAIT(等待关闭)状态,此时的 TCP 处于半关闭状态(下面会说什么是半关闭状态),客户端到服务器的连接释放。客户端收到来自服务器的 ACK 应答报文段后,进入 FINWAIT_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。之后客户端进入 TIMEWAIT(时间等待)状态,服务器收到 ACK 应答报文段后,服务器就进入 CLOSE(关闭)状态,到此服务器的连接已经完成关闭。客户端处于 TIMEWAIT 状态时,此时的 TCP 还未释放掉,需要等待 2MSL 后,客户端才进入 CLOSE 状态。