尚硅谷Java入门视频教程第十四章——网络编程
第14章:网络编程
14.1 网络编程概述
- Java是 Internet 上的语言,它从语言级上提供了对网络应用程序的支持,程序员能够很容易开发常见的网络应用程序。
- Java提供的网络类库,可以实现无痛的网络连接,联网的底层细节被隐藏在 Java 的本机安装系统里,由 JVM 进行控制。并且 Java 实现了一个跨平台的网络库,程序员面对的是一个统一的网络编程环境
- 计算机网络:
把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、共享硬件、软件、数据信息等资源。
- 网络编程的目的:
直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯。 - 网络编程中有两个主要的问题:
- 如何准确地定位网络上一台或多台主机;定位主机上的特定的应用
- 找到主机后如何可靠高效地进行数据传输
14.2 网络通信要素概述
- 如何实现网络中的主机互相通信
- 要素一:通信双方地址
- IP
- 端口号
- 要素二:一定的规则(即:网络通信协议。有两套参考模型)
- OSI参考模型:模型过于理想化,未能在因特网上进行广泛推广
- TCP/IP参考模型(或TCP/IP协议):事实上的国际标准
- 要素一:通信双方地址
14.3 通信要素1:IP和端口号
-
IP 地址:InetAddress:唯一的标识 Internet 上的计算机(通信实体)
- 本地回环地址(hostAddress):127.0.0.1,主机名(hostName):localhost
- IP地址分类方式1:IPV4 和 IPV6
- IPV4:4个字节组成,4个0-255。大概42亿,30亿都在北美,亚洲4亿。2011年初已经用尽。以点分十进制表示,如192.168.0.1
- IPV6:128位(16个字节),写成8个无符号整数,每个整数用四个十六进制位表示,数之间用冒号(:)分开,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984
- IP地址分类方式2:公网地址(万维网使用)和私有地址(局域网使用)。
192.168.开头的就是私有址址,范围即为192.168.0.0–192.168.255.255,专门为组织机构内部使用 - 特点:不易记忆
-
InetAddress类
- Internet上的主机有两种方式表示地址:
- 域名(hostName):www.atguigu.com
- IP 地址(hostAddress):202.108.35.210
- InetAddress类主要表示IP地址,两个子类:Inet4Address、Inet6Address。
- InetAddress 类 对 象 含 有 一 个 Internet 主 机 地 址 的 域 名 和 IP 地 址 :www.atguigu.com 和 202.108.35.210。
- 域名容易记忆,当在连接网络时输入一个主机的域名后,域名服务器(DNS)负责将域名转化成IP地址,这样才能和主机建立连接。 -------域名解析
- InetAddress类没有提供公共的构造器,而是提供了如下几个静态方法来获取InetAddress实例
public static InetAddress getLocalHost() //获取本地ip
public static InetAddress getByName(String host)
- InetAddress提供了如下几个常用的方法
public String getHostAddress()
:返回 IP 地址字符串(以文本表现形式)public String getHostName()
:获取此 IP 地址的主机名public boolean isReachable(int timeout)
:测试是否可以达到该地址
package com.atguigu.java1; import java.net.InetAddress; import java.net.UnknownHostException; public class InetAddressTest { public static void main(String[] args) { try { //File file = new File("hello.txt"); InetAddress inet1 = InetAddress.getByName("192.168.10.14"); System.out.println(inet1);// /192.168.10.14 InetAddress inet2 = InetAddress.getByName("www.atguigu.com"); System.out.println(inet2);// www.atguigu.com/125.76.247.133 InetAddress inet3 = InetAddress.getByName("127.0.0.1"); System.out.println(inet3);// /127.0.0.1 //获取本地ip InetAddress inet4 = InetAddress.getLocalHost(); System.out.println(inet4);// DESKTOP-PUAJMV4/10.15.22.217(显示的是局域网ip,所以不是127.0.0.1) //getHostName() System.out.println(inet2.getHostName());// www.atguigu.com //getHostAddress() System.out.println(inet2.getHostAddress());// 125.76.247.133 } catch (UnknownHostException e) { e.printStackTrace(); } } }
- Internet上的主机有两种方式表示地址:
-
端口号标识正在计算机上运行的进程(程序)
- 不同的进程有不同的端口号
- 被规定为一个 16 位的整数 0~65535。
- 端口分类:
- 公认端口:0~1023。被预先定义的服务通信占用(如:HTTP占用端口80,FTP占用端口21,Telnet占用端口23)
- 注册端口:1024~49151。分配给用户进程或应用程序。(如:Tomcat占用端口8080,MySQL占用端口3306,Oracle占用端口1521等)。
- 动态/私有端口:49152~65535。
-
端口号与IP地址的组合得出一个网络套接字:Socket
14.4 通信要素2:网络协议
-
网络通信协议
计算机网络中实现通信必须有一些约定,即通信协议,对速率、传输代码、代码结构、传输控制步骤、出错控制等制定标准。- 问题:网络协议太复杂
计算机网络通信涉及内容很多,比如指定源地址和目标地址,加密解密,压缩解压缩,差错控制,流量控制,路由控制,如何实现如此复杂的网络协议呢? - 通信协议分层的思想
在制定协议时,把复杂成份分解成一些简单的成份,再将它们复合起来。最常用的复合方式是层次方式,即同层间可以通信、上一层可以调用下一层,而与再下一层不发生关系。 各层互不影响,利于系统的开发和扩展。
- 问题:网络协议太复杂
-
TCP/IP协议簇
- 传输层协议中有两个非常重要的协议:
- 传输控制协议TCP(Transmission Control Protocol)
- 用户数据报协议UDP(User Datagram Protocol)
- TCP/IP 以其两个主要协议:传输控制协议(TCP)和网络互联协议(IP) 而得名,实际上是一组协议,包括多个具有不同功能且互为关联的协议。
- IP(Internet Protocol)协议是网络层的主要协议,支持网间互连的数据通信。
- TCP/IP协议模型从更实用的角度出发,形成了高效的四层体系结构,即物理链路层、IP层、传输层和应用层
- 传输层协议中有两个非常重要的协议:
-
TCP 和 UDP
-
TCP协议:
-
使用TCP协议前,须先建立TCP连接,形成传输数据通道
-
传输前,采用“三次握手”方式,点对点通信,是可靠的
-
TCP协议进行通信的两个应用进程:客户端、服务端。
-
在连接中可进行大数据量的传输
-
传输完毕,需释放已建立的连接,效率低
-
TCP三次握手
-
TCP四次挥手
-
-
UDP协议:
- 将数据、源、目的封装成数据包,不需要建立连接
- 每个数据报的大小限制在64K内
- 发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
- 可以广播发送
- 发送数据结束时无需释放资源,开销小,速度快
-
14.5 TCP网络编程
- Java语言的基于套接字编程分为服务端编程和客户端编程,其通信模型如图所示:
- 例题1:客户端发送信息给服务端,服务端将数据显示在控制台上
package com.atguigu.java1; import org.junit.Test; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; public class TCPTest1 { //客户端 @Test public void client() { Socket socket = null; OutputStream os = null; try { //1.创建Socket对象,指明服务器端的ip和端口号 InetAddress inet = InetAddress.getByName("192.168.14.100"); socket = new Socket(inet,8899); //2.获取一个输出流,用于输出数据 os = socket.getOutputStream(); //3.写出数据的操作 os.write("你好,我是客户端mm".getBytes()); } catch (IOException e) { e.printStackTrace(); } finally { //4.资源的关闭 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 ss = null; Socket socket = null; InputStream is = null; ByteArrayOutputStream baos = null; try { //1.创建服务器端的ServerSocket,指明自己的端口号 ss = new ServerSocket(8899); //2.调用accept()表示接收来自于客户端的socket socket = ss.accept(); //3.获取输入流 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.print(str); // } //4.读取输入流中的数据 baos = new ByteArrayOutputStream(); byte[] buffer = new byte[5]; int len; while((len = is.read(buffer)) != -1){ baos.write(buffer,0,len); } System.out.println(baos.toString()); System.out.println("收到了来自于:" + socket.getInetAddress().getHostAddress() + "的数据"); } catch (IOException e) { e.printStackTrace(); } finally { if(baos != null){ //5.关闭资源 try { baos.close(); } catch (IOException e) { e.printStackTrace(); } } if(is != null){ try { is.close(); } catch (IOException e) { e.printStackTrace(); } } if(socket != null){ try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } if(ss != null){ try { ss.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
- 例题2:从客户端发送文件给服务端,服务端保存到本地。并返回“发送成功”给客户端,并关闭相应的连接。
package com.atguigu.java; import org.junit.Test; import java.io.*; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; /** * @author IvyYi * @create 2022-06-18 12:18 */ public class TCPTest3 { /* 这里涉及到的异常,应该使用try-catch-finally处理,此处为步骤清晰未使用 */ @Test public void client() throws IOException { //1.创建Socket对象,指明服务器端的ip和端口号 Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9090); //2.向服务器端发送图片 //2.1 获取一个输出流,用于输出数据 OutputStream os = socket.getOutputStream(); //2.2 读取要输出的数据 FileInputStream fis = new FileInputStream("海洋馆.jpg"); //2.3 写出数据的操作 byte[] buffer = new byte[1024]; int len; while((len = fis.read(buffer)) != -1){ os.write(buffer, 0, len); } //3.发送结束,关闭数据输出 socket.shutdownOutput(); //4.接收来自于服务器端的数据,并显示到控制台上 InputStream is = socket.getInputStream(); ByteArrayOutputStream 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()); //5.资源关闭 baos.close(); is.close(); fis.close(); os.close(); socket.close(); } /* 这里涉及到的异常,应该使用try-catch-finally处理,此处为步骤清晰未使用 */ @Test public void server() throws IOException { //1.创建服务器端的ServerSocket,指明自己的端口号 ServerSocket ss = new ServerSocket(9090); //2.调用accept()表示接收来自于客户端的socket Socket socket = ss.accept(); //3.接收来自于客户端的图片 //3.1 获取客户端的输入流 InputStream is = socket.getInputStream(); //3.2 创建输出流以保存至文件中 FileOutputStream fos = new FileOutputStream("海洋馆2.jpg"); //3.3 读取输入流中的数据写入输出流中 byte[] buffer = new byte[1024]; int len; while((len = is.read(buffer)) != -1){ fos.write(buffer, 0, len); } System.out.println("图片传输完成!"); //4.服务器端给予客户端反馈 OutputStream os = socket.getOutputStream(); os.write("图片已收到!".getBytes()); //5.资源关闭 os.close(); fos.close(); is.close(); socket.close(); ss.close(); } }
14.6 UDP网络编程
-
类 DatagramSocket 和 DatagramPacket 实现了基于 UDP 协议网络程序。
-
UDP数据报通过数据报套接字DatagramSocket 发送和接收,系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。
-
DatagramPacket 对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号。
-
UDP协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和接收方的连接。如同发快递包裹一样。
-
例题
package com.atguigu.java1; import org.junit.Test; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; /** * UDPd协议的网络编程 */ public class UDPTest { //发送端 @Test public void sender() throws IOException { DatagramSocket socket = new DatagramSocket(); String str = "我是UDP方式发送的导弹"; byte[] data = str.getBytes(); InetAddress inet = InetAddress.getLocalHost(); DatagramPacket packet = new DatagramPacket(data,0,data.length,inet,9090); socket.send(packet); socket.close(); } //接收端 @Test public void receiver() throws IOException { DatagramSocket socket = new DatagramSocket(9090); byte[] buffer = new byte[100]; DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length); socket.receive(packet); System.out.println(new String(packet.getData(),0,packet.getLength())); socket.close(); } }
14.7 URL编程
-
URL(Uniform Resource Locator):统一资源定位符,它表示 Internet 上某一资源的地址。 它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。
-
通过 URL 我们可以访问 Internet 上的各种网络资源,比如最常见的 www,ftp 站点。浏览器通过解析给定的 URL 可以在网络上查找相应的文件或其他资源。
-
URL的基本结构由5部分组成:
<传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表- 例如:
http://192.168.1.100:8080/helloworld/index.jsp#a?username=shkstart&password=123 - #片段名:即锚点,例如看小说,直接定位到章节
- 参数列表格式:参数名=参数值&参数名=参数值…
- 例如:
-
URL类构造器
为了表示URL,java.net 中实现了类 URL,可以通过下面的构造器来初始化一个 URL 对象:
-
URL类的构造器都声明抛出非运行时异常,必须要对这一异常进行处理,通常使用 try-catch 语句进行捕获。
-
URL类常用方法
package com.atguigu.java1; import java.net.MalformedURLException; import java.net.URL; /** * URL网络编程 * 1.URL:统一资源定位符,对应着互联网的某一资源地址 * 2.格式: * http://localhost:8080/examples/beauty.jpg?username=Tom * 协议 主机名 端口号 资源地址 参数列表 */ public class URLTest { public static void main(String[] args) { try { URL url = new URL("http://localhost:8080/examples/beauty.jpg?username=Tom"); // public String getProtocol( ) 获取该URL的协议名 System.out.println(url.getProtocol());//http // public String getHost( ) 获取该URL的主机名 System.out.println(url.getHost());//localhost // public String getPort( ) 获取该URL的端口号 System.out.println(url.getPort());//8080 // public String getPath( ) 获取该URL的文件路径 System.out.println(url.getPath());// /examples/beauty.jpg // public String getFile( ) 获取该URL的文件名 System.out.println(url.getFile());// /examples/beauty.jpg?username=Tom // public String getQuery( ) 获取该URL的查询名 System.out.println(url.getQuery());//username=Tom } catch (MalformedURLException e) { e.printStackTrace(); } } }
-
例题:从服务器端下载文件
package com.atguigu.java1; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; public class URLTest1 { public static void main(String[] args) { HttpURLConnection urlConnection = null; InputStream is = null; FileOutputStream fos = null; try { URL url = new URL("http://localhost:8080/examples/beauty.jpg"); urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.connect(); is = urlConnection.getInputStream(); fos = new FileOutputStream("day10\\beauty3.jpg"); byte[] buffer = new byte[1024]; int len; while((len = is.read(buffer)) != -1){ fos.write(buffer,0,len); } System.out.println("下载完成"); } catch (IOException e) { e.printStackTrace(); } finally { //关闭资源 if(is != null){ try { is.close(); } catch (IOException e) { e.printStackTrace(); } } if(fos != null){ try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } if(urlConnection != null){ urlConnection.disconnect(); } } } }
-URI、URL和URN的区别
URI,是uniform resource identifier,统一资源标识符,用来唯一的标识一个资源。而URL是uniform resource locator,统一资源定位符,它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。而URN,uniform resource name,统一资源命名,是通过名字来标识资源,比如mailto:java-net@java.sun.com。也就是说,URI是以一种抽象的,高层次概念定义统一资源标识,而URL和URN则是具体的资源标识的方式。URL和URN都是一种URI。