13.1 概述
- Java是 Internet 上的语言,它从语言级上提供了对网络应用程序的支持,程序员能够很容易开发常见的网络应用程序
- Java提供的网络类库,可以实现无痛的网络连接,联网的底层细节被隐藏在 Java 的本机安装系统里,由 JVM 进行控制。并且 Java 实现了一个跨平台的网络库, 程序员面对的是一个统一的网络编程环境
- 网络编程的目的:
直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯 - 网络编程中有两个主要的问题:
- 如何准确地定位网络上一台或多台主机;定位主机上的特定的应用
- 找到主机后如何可靠高效地进行数据传输
13.2 网络通信要素概述
-
如何实现网络中的主机互相通信
- 通信双方地址:
- IP:定位每一个人的主机
- 端口号:区分一个主机上的不同应用程序
- 一定的规则:
- OSI参考模型:模型过于理想化,未能在因特网上进行广泛推广
- TCP/IP参考模型:事实上的国际标准
- 通信双方地址:
-
网络通信协议:
13.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,专门为组织机构内部使用
-
域名:IP不易记忆,用户输入域名,又域名解析器DNS解析,得到目标的IP地址
-
Internet上的主机有两种方式表示地址:
-
域名(hostName): www.baidu.com
-
IP 地址(hostAddress): 202.108.35.210
-
-
-
如何实例化InetAddress:
- 实例化InetAddress的两个方法:
getByName(String host)
,getLocalHost()
- 两个常用方法:
getHostAddress()
,getHostName()
try { //使用IP地址 InetAddress inet1 = InetAddress.getByName("192.168.10.14"); System.out.println(inet1); //使用域名 InetAddress inet2 = InetAddress.getByName("www.baidu.com"); System.out.println(inet2); //访问本机上的服务器 InetAddress inet3 = InetAddress.getLocalHost(); System.out.println(inet3); //常用方法 inet1.getHostAddress(); inet1.getHostName(); } catch (UnknownHostException e) { e.printStackTrace(); }
- 实例化InetAddress的两个方法:
-
端口号:标识正在计算机上运行的进程(程序)
-
不同的进程有不同的端口号,被规定为一个 16位的整数 0~65535。
-
端口分类:
- 公认端口: 0~1023:被预先定义的服务通信占用(如:HTTP占用端口80, FTP占用端口21, Telnet占用端口23)
- 注册端口: 1024~49151:分配给用户进程或应用程序(如:Tomcat占用端口8080, MySQL占用端口3306, Oracle占用端口1521等)
- 动态/私有端口: 49152~65535
-
-
端口号与IP地址的组合得出一个网络套接字: Socket
13.4 通信要素2:网络协议
-
网络通信协议
计算机网络中实现通信必须有一些约定,即通信协议, 对速率、传输代码、代码结构、传输控制步骤、出错控制等制定标准
-
问题:网络协议太复杂
计算机网络通信涉及内容很多,比如指定源地址和目标地址,加密解密,压缩解压缩,差错控制,流量控制,路由控制,如何实现如此复杂的网络协议呢?
-
通信协议分层的思想
在制定协议时,把复杂成份分解成一些简单的成份,再将它们复合起来。最常用的复合方式是层次方式,即同层间可以通信、上一层可以调用下一层,而与再下一层不发生关系。各层互不影响,利于系统的开发和扩展
-
传输层协议中有两个非常重要的协议:
- 传输控制协议:TCP(Transmission Control Protocol)
- 用户数据报协议:UDP(User Datagram Protocol)
-
TCP/IP 以其两个主要协议:**传输控制协议(TCP)和网络互联协议(IP)**而得名,实际上是一组协议,包括多个具有不同功能且互为关联的协议
-
TCP协议(打电话):
-
使用TCP协议前,须先建立TCP连接,形成传输数据通道
-
传输前,采用“三次握手” 方式,点对点通信, 是可靠的
-
TCP协议进行通信的两个应用进程:客户端、 服务端
-
在连接中可进行大数据量的传输
-
传输完毕,需释放已建立的连接, 效率低
三次基于可以保证可靠,但是四次以及更高次握手也是可以,只是可靠性提升不大
-
-
UDP协议(发短信):
- 将数据、源、目的封装成数据包, 不需要建立连接
- 每个数据报的大小限制在64K内
- 发送不管对方是否准备好,接收方收到也不确认, 故是不可靠的
- 可以广播发送
- 发送数据结束时无需释放资源,开销小,速度快
13.5 TCP网络编程
-
客户端给服务器发送消息:
public class inetTest { //客户端 @Test public void client(){ Socket socket = null; OutputStream os = null; try { //1.创建socke对象,指明服务器端的ip和端口号 InetAddress inetAddress = InetAddress.getByName("192.168.110.150"); socket = new Socket(inetAddress,8899); //2.获取一个输出流,用于输出数据 os = socket.getOutputStream(); //3.写出数据 os.write("你好".getBytes(StandardCharsets.UTF_8)); } catch (IOException e) { e.printStackTrace(); } finally { try { os.close(); } catch (IOException e) { e.printStackTrace(); } try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } //服务器端 @Test public void server(){ ServerSocket serverSocket = null; Socket socket = null; InputStream is = null; ByteArrayOutputStream baos = null; try { //1.创建服务器端的socke:ServerSocket serverSocket = new ServerSocket(8899); //2.调用acept()表示接受来自于客户端的socket socket = serverSocket.accept(); //3.获取输入流 is = socket.getInputStream(); //4.读取输入流中的数据 //不建议使用:使用字节流传中文会有乱码 /*byte[] bytes = new byte[1024]; int len; while ((len = is.read(bytes)) != -1 ){ String s = new String(bytes, 0, len); System.out.println(s); }*/ baos = new ByteArrayOutputStream(); byte[] bytes = new byte[5]; int len; while ((len = is.read(bytes)) != -1 ){ baos.write(bytes,0,len); } String s = baos.toString(); System.out.println(s); System.out.println("来自于"+socket.getInetAddress().getHostName()+"的消息"); } catch (IOException e) { e.printStackTrace(); } finally { try { if(baos != null) baos.close(); } catch (IOException e) { e.printStackTrace(); } try { if(is != null) is.close(); } catch (IOException e) { e.printStackTrace(); } try { if(socket != null) socket.close(); } catch (IOException e) { e.printStackTrace(); } try { if(serverSocket != null) serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } }
-
客户端发送本地文件到服务器,服务器再保存到本地
public class inetTest { //客户端 @Test public void client() throws IOException { Socket socket = new Socket(InetAddress.getByName("192.168.110.150"), 9090); OutputStream os = socket.getOutputStream(); //造一个写入流,获取要传出去的文件 FileInputStream fis = new FileInputStream(new File("D:\\123.png")); byte[] bytes = new byte[1024]; int len; while ((len = fis.read(bytes)) != -1){ os.write(bytes,0,len); } os.close(); fis.close(); socket.close(); } //服务器端 @Test public void server() throws IOException { ServerSocket ss = new ServerSocket(9090); Socket socket = ss.accept(); InputStream is = socket.getInputStream(); FileOutputStream fos = new FileOutputStream(new File("D:\\223.png")); byte[] bytes = new byte[1024]; int len; while ((len = is.read(bytes)) != -1){ fos.write(bytes,0,len); } fos.close(); is.close(); socket.close(); ss.close(); } }
-
加入反馈信息:
public class inetTest { @Test public void client() throws IOException { Socket socket = new Socket(InetAddress.getByName("192.168.110.150"), 9090); OutputStream os = socket.getOutputStream(); //造一个写入流,获取要传出去的文件 FileInputStream fis = new FileInputStream(new File("D:\\123.png")); byte[] bytes = new byte[1024]; int len; while ((len = fis.read(bytes)) != -1){ os.write(bytes,0,len); } //不再输出数据了(read()是一个阻塞时方法,需要终止,好让下面使用read()) socket.shutdownOutput(); //客户端接受服务器端的反馈信息 InputStream is = socket.getInputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int len1; while ((len1 = is.read(bytes)) != -1){ baos.write(bytes,0,len1); } System.out.println(baos.toString()); os.close(); fis.close(); socket.close(); is.close(); baos.close(); } @Test public void server() throws IOException { ServerSocket ss = new ServerSocket(9090); Socket socket = ss.accept(); InputStream is = socket.getInputStream(); FileOutputStream fos = new FileOutputStream(new File("D:\\2343.png")); byte[] bytes = new byte[1024]; int len; while ((len = is.read(bytes)) != -1){ fos.write(bytes,0,len); } //服务器端接受文件后,发送反馈信息给客户端 OutputStream os = socket.getOutputStream(); os.write("图片已经收到".getBytes(StandardCharsets.UTF_8)); fos.close(); is.close(); socket.close(); ss.close(); os.close(); } }
13.6 UDP网络编程
-
类 DatagramSocket 和 DatagramPacket 实现了基于 UDP 协议网络程序
-
UDP数据报通过数据报套接字 DatagramSocket发送和接收, 系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达(只管发出去)
-
DatagramPacket 对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号
-
UDP协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和接收方的连接。 如同发快递包裹一样
-
示例:
public class inetTest { @Test public void sender() throws IOException { DatagramSocket socket = new DatagramSocket(); String str = "UDP发送的内容"; byte[] data = str.getBytes(StandardCharsets.UTF_8); InetAddress inetAddresses = InetAddress.getByName("192.168.110.150"); //包含了发送端的IP地址和端口号以及接收端的IP地址和端口号 DatagramPacket packet = new DatagramPacket(data,0,data.length,inetAddresses,9090); socket.send(packet);//用packet封装UDP数据报 } @Test public void receiver() throws IOException { DatagramSocket socket = new DatagramSocket(9090); byte[] bytes = new byte[1024]; DatagramPacket packet = new DatagramPacket(bytes,0,bytes.length); socket.receive(packet); String s = new String(packet.getData(), 0, packet.getLength()); System.out.println(s); socket.close(); } }
13.7 URL编程
-
URL(Uniform Resource Locator):统一资源定位符,它表示 Internet 上某一资源的地址,可以用来标识一个资源,而且还指明了如何locate这个资源
-
URL的基本结构由5部分组成:
<传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表 如: http://localhost:8080/examples/123.png?username=Tom&password=123 协议 主机名 端口号 资源地址 参数
-
示例:
URL url = new URL("http://localhost:8080/examples/123.png?username=Tom&password=123"); url.getProtocol();//获取该URL的协议名 url.getHost();//获取该URL的主机名 url.getPort();//获取该URL的端口号 url.getFile();//获取该URL的文件名 url.getQuery();//获取该URL的查询码