文章目录
14.1 网络与通信
- 网络的定义:通过利用通信线路和通信设备,把地理位置上分散的、并具有独立功能的多个计算机系统互相连接起来,按照网络协议进行数据通信,用功能完善的网络软件实现资源共享的计算机系统的集合。
- 计算机网络分类:
- 按照传输技术分类:广播式网络、点到点网络(不存在信道共享和复用)。
- 按照覆盖范围分类:局域网(Loca Area Network,LAN)、城域网(Metropolitan Area NetWork,MAN)、广域网(Wide Area Network, WAN)、个人区域网(Personal Area Network,PAN)。
- 按照网络应用与管理范围分类:因特网(Internet)、内联网(Intranet)、外联网(Extranet)。
- 网络通信:将数据通过网络从一台设备传输到另一台设备。
- java.net包下提供了一系列的类或接口,供程序员使用,完成网络通信。
14.1.1 网络通信协议
14.1.2 TCP和UDP简介
- TCP(Transmission Control Protocol,传输控制协议):是一种面向连接的、可靠的、基于字节流的传输层通信协议,TCP 层是位于 IP 层之上,应用层之下的中间层。TCP 保障了两个应用程序之间的可靠通信。通常用于互联网协议,被称 TCP / IP。
- 使用TCP协议前,需建立TCP连接,形成传输数据通道。
- 传输前,采用”三次握手“方式,是可靠的。
- TCP协议进行通信的两个应用程序:客户端、服务端。
- 在连接中可进行大数据量的传输。
- 传输完毕,需释放已建立的连接,效率低。
- UDP(User Datagram Protocol,用户数据报协议):位于 OSI 模型的传输层。一个无连接的协议。提供了应用程序之间要发送数据的数据报。由于UDP缺乏可靠性且属于无连接协议,所以应用程序通常必须容许一些丢失、错误或重复的数据包。
- 将数据、源、目的封装成数据包,不需要建立连接。
- 每个数据报的大小限制在64K内,不适合传输大量数据。
- 无需连接,因此是不可靠的。
- 发送数据结束时无需释放资源(因为不是面向连接的),速度快。
14.2 InetAddress
-
定义:这个类表示互联网协议(IP)地址。
-
常用方法如下所述:
-
static InetAddress getLocalHost():返回本地主机的InetAddress对象。
InetAddress localHost = InetAddress.getLocalHost(); System.out.println("localHost = " + localHost); //localHost = LAPTOP-AP2A3J2N/192.168.37.2
-
static InetAddress getByName(String host) :在给定主机名 / 域名的情况下获取InetAddress对象。
InetAddress inetAddress1 = InetAddress.getByName("www.baidu.com"); System.out.println("inetAddress1 = " + inetAddress1);//inetAddress1 = www.baidu.com/36.152.44.95 InetAddress inetAddress2 = InetAddress.getByName("LAPTOP-AP2A3J2N"); System.out.println("inetAddress2 = " + inetAddress2);//inetAddress2 = LAPTOP-AP2A3J2N/192.168.37.2
-
String getHostName():通过 InetAddress对象,获取对应的主机名/或者域名。
System.out.println("localHost的主机名为:" + localHost.getHostName()); //localHost的主机名为:LAPTOP-AP2A3J2N System.out.println("inetAddress1的域名为:" + inetAddress1.getHostName()); //inetAddress1的域名为:www.baidu.com System.out.println("inetAddress2的主机名为:" + inetAddress2.getHostName()); //inetAddress2的主机名为:LAPTOP-AP2A3J2N
-
String getHostAddress():通过 InetAddress对象,获取对应的ip地址
System.out.println("localHost的ip地址为为:" + localHost.getHostAddress()); //localHost的ip地址为为:192.168.37.2 System.out.println("inetAddress1的ip地址为:" + inetAddress1.getHostAddress()); //inetAddress1的ip地址为:36.152.44.95 System.out.println("inetAddress2的ip地址为:" + inetAddress2.getHostAddress()); //inetAddress2的ip地址为:192.168.37.2
-
14.3 netstat
14.4 Socket
-
基本介绍:Socket(套接字)开发网络应用程序被广泛采用,以至于成为工业标准。
-
通信的两端都要有Socket,是两台机器间通信的端点。网络通信其实就是Socket间的通信。
-
Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输。
-
一般主动发起通信的应用程序属于客户端,等待通信请求的为服务端。
14.3.1 Socket编程
-
套接字使用TCP提供了两台计算机之间的通信机制。 客户端程序创建一个套接字,并尝试连接服务器的套接字。
当连接建立时,服务器会创建一个 Socket 对象。客户端和服务器现在可以通过对 Socket 对象的写入和读取来进行通信。
java.net.Socket 类代表一个套接字,并且 java.net.ServerSocket 类为服务器程序提供了一种来监听客户端,并与他们建立连接的机制。
-
以下步骤在两台计算机之间使用套接字建立TCP连接时会出现:
- 服务器实例化一个 ServerSocket 对象,表示通过服务器上的端口通信。
- 服务器调用 ServerSocket 类的 accept() 方法,该方法将一直等待,直到客户端连接到服务器上给定的端口。
- 服务器正在等待时,一个客户端实例化一个 Socket 对象,指定服务器名称和端口号来请求连接。
- Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket 对象能够与服务器进行通信。
- 在服务器端,accept() 方法返回服务器上一个新的 socket 引用,该 socket 连接到客户端的 socket。
-
连接建立后,通过使用 I/O 流在进行通信,每一个socket都有一个输出流和一个输入流,客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。
14.5 TCP网络通信编程
-
TCP 是一个双向的通信协议,因此数据可以通过两个数据流在同一时间发送.以下是一些类提供的一套完整的有用的方法来实现 socket。Java 网络编程 | 菜鸟教程 (runoob.com)。
-
URL 处理:这部分会在另外的篇幅里讲,点击这里更详细地了解在 Java 语言中的 URL 处理。
14.5.1 TCP字节流编程
-
在发送信息完毕,需要显示的设置输出结束标记 socket.shutdownOutput,否则接收端可能处于阻塞状态。
-
服务器端:接收消息 “hello,server!”,并发送消息 “ hello client”。
public class SocketTCP01Server { public static void main(String[] args) throws IOException { // 1.public ServerSocket(int port) throws IOException:创建绑定到特定端口的服务器套接字。 // 这个 ServerSocket 可以通过 accept() 返回多个Socket[多个客户端连接服务器的并发] ServerSocket serverSocket = new ServerSocket(9999); System.out.println("服务端,在9999端口监听,等待连接.."); // 2.public Socket accept() throws IOException:侦听并接受到此套接字的连接,该方法将一直等待,直到客户端连接到服务器上给定的端口。 Socket socket = serverSocket.accept(); System.out.println("服务端 socket返回=" + socket.getClass()); //服务端 socket返回=class java.net.Socket // 3.public InputStream getInputStream() throws IOException:返回此套接字的输入流。 InputStream inputStream = socket.getInputStream(); byte[] buf = new byte[1024]; int readLen = 0; while ((readLen = inputStream.read(buf)) != -1) { System.out.println(new String(buf, 0, readLen)); //hello,server! } // 4.public OutputStream getOutputStream() throws IOException:返回此套接字的输出流。 OutputStream outputStream = socket.getOutputStream(); outputStream.write("hello,client".getBytes()); socket.shutdownOutput(); //设置输出结束标记 // 5.释放资源 outputSteam.close(); inputStream.close(); socket.close(); serverSocket.close(); System.out.println("服务端退出..."); } } /* 服务端,在9999端口监听,等待连接.. 服务端 socket返回=class java.net.Socket hello,server! 服务端退出... */
-
客户端:发送消息 “ hello,server!”,并接收消息 “ hello client”。
public class SocketTCP01Client { public static void main(String[] args) throws IOException { // 1.public Socket(InetAddress host, int port) throws IOException:创建一个流套接字并将其连接到指定 IP 地址的指定端口号。 Socket socket = new Socket(InetAddress.getLocalHost(), 9999); System.out.println("客户端 socket返回=" + socket.getClass()); //客户端 socket返回=class java.net.Socket // 2.public OutputStream getOutputStream() throws IOException:返回此套接字的输出流。 OutputStream outputStream = socket.getOutputStream(); outputStream.write("hello,server!".getBytes()); socket.shutdownOutput(); //设置输出结束标记 // 3.public InputStream getInputStream() throws IOException:返回此套接字的输入流。 InputStream inputStream = socket.getInputStream(); byte[] buf = new byte[1024]; int readLength = 0; while ((readLength = inputStream.read(buf)) != -1) { System.out.println(new String(buf, 0, readLength)); } // 4.释放资源 inputStream.close(); outputStream.close(); socket.close(); System.out.println("客户端退出....."); } } /* 客户端 socket返回=class java.net.Socket hello,client 客户端退出..... */
14.5.2 TCP字符流编程
-
在发送信息完毕,需要显示的设置刷新 bufferedWriter.flush() 和输出结束标记 socket.shutdownOutput,否则接收端可能处于阻塞状态。
-
服务器端:接收消息 “hello,server!”,并发送消息 “ hello client”。
public class SocketTCP02Server { public static void main(String[] args) throws IOException { // 1.public ServerSocket(int port) throws IOException:创建绑定到特定端口的服务器套接字。 ServerSocket serverSocket = new ServerSocket(8888); System.out.println("服务端,在8888端口监听,等待连接.."); // 2.public Socket accept() throws IOException:侦听并接受到此套接字的连接,该方法将一直等待,直到客户端连接到服务器上给定的端口。 Socket socket = serverSocket.accept(); System.out.println("服务端 socket返回=" + socket.getClass()); //服务端 socket返回=class java.net.Socket // 3.public InputStream getInputStream() throws IOException:返回此套接字的输入流。 String line = null; InputStream inputStream = socket.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); while ((line = bufferedReader.readLine()) != null) { System.out.println(line); } // 4.public OutputStream getOutputStream() throws IOException:返回此套接字的输出流。 OutputStream outputStream = socket.getOutputStream(); BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); bufferedWriter.write("hello,client"); bufferedWriter.newLine(); bufferedWriter.flush(); socket.shutdownOutput(); //设置输出结束标记 // 5.释放资源 bufferedWriter.close(); bufferedReader.close(); socket.close(); serverSocket.close(); System.out.println("服务端退出..."); } } /* 服务端,在8888端口监听,等待连接.. 服务端 socket返回=class java.net.Socket hello,server! 服务端退出... */
-
客户端:发送消息 “ hello,server!”,并接收消息 “ hello client”。
public class SocketTCP02Client { public static void main(String[] args) throws IOException { // 1.public Socket(InetAddress host, int port) throws IOException:创建一个流套接字并将其连接到指定 IP 地址的指定端口号。 Socket socket = new Socket(InetAddress.getLocalHost(), 8888); System.out.println("客户端 socket返回=" + socket.getClass()); //客户端 socket返回=class java.net.Socket // 2.public OutputStream getOutputStream() throws IOException:返回此套接字的输出流。 OutputStream outputStream = socket.getOutputStream(); BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); bufferedWriter.write("hello,server!"); bufferedWriter.newLine(); bufferedWriter.flush(); socket.shutdownOutput(); //设置输出结束标记 // 3.public InputStream getInputStream() throws IOException:返回此套接字的输入流。 String line = null; InputStream inputStream = socket.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); while ((line = bufferedReader.readLine()) != null) { System.out.println(line); } // 4.释放资源 bufferedReader.close(); bufferedWriter.close(); socket.close(); System.out.println("客户端退出....."); } } /* 客户端 socket返回=class java.net.Socket hello,client 客户端退出..... */
14.5.3 TCP上传和下载文件
-
服务器端:
public class TCPFileUploadServer { public static void main(String[] args) throws IOException { // 1.public ServerSocket(int port) throws IOException:创建绑定到特定端口的服务器套接字。 ServerSocket serverSocket = new ServerSocket(6666); System.out.println("服务端,在6666端口监听,等待连接.."); // 2.public Socket accept() throws IOException:侦听并接受到此套接字的连接,该方法将一直等待,直到客户端连接到服务器上给定的端口。 Socket socket = serverSocket.accept(); System.out.println("服务端 socket返回=" + socket.getClass()); //服务端 socket返回=class java.net.Socket // 3.接收来自客户端的图片 BufferedInputStream bufferedInputStream = new BufferedInputStream(socket.getInputStream()); byte[] bytes = StreamUtils.inputStreamToByteArray(bufferedInputStream); // 4.将图片保存到指定目录下 String destFilePath = "d:\\nature777.jpg"; BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(destFilePath)); bufferedOutputStream.write(bytes); // 5.发送 “收到消息” 给客户端 BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); bufferedWriter.write("服务器端成功收到图片"); bufferedWriter.flush(); socket.shutdownOutput(); // 6.关闭资源 bufferedWriter.close(); bufferedOutputStream.close(); bufferedInputStream.close(); socket.close(); serverSocket.close(); System.out.println("服务器端成功关闭..."); } }
-
客户端:
package upload_; import java.io.*; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; public class TCPFileUploadClient { public static void main(String[] args) throws IOException { // 1.public Socket(InetAddress host, int port) throws IOException:创建一个流套接字并将其连接到指定 IP 地址的指定端口号。 Socket socket = new Socket(InetAddress.getLocalHost(), 6666); System.out.println("客户端 socket返回=" + socket.getClass()); //客户端 socket返回=class java.net.Socket // 2.获取本地图片nature.jpg String filePath = "d:\\nature.jpg"; BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(filePath)); byte[] bytes = StreamUtils.inputStreamToByteArray(bufferedInputStream); // 3.上传图片到服务器端 BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(socket.getOutputStream()); bufferedOutputStream.write(bytes); socket.shutdownOutput(); // 4.接收来自服务端的消息 String line = null; BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream())); while ((line = bufferedReader.readLine()) != null) { System.out.println(line); } System.out.println("收到图片,我就放心了"); // 5.关闭资源 bufferedReader.close(); bufferedOutputStream.close(); bufferedInputStream.close(); socket.close(); System.out.println("客户端成功关闭..."); } }
-
StreamUtils(工具类):该类下的方法主要用于将一个输入流转换为字节数组,便于存储和传输二进制文件。
public class StreamUtils { public static byte[] inputStreamToByteArray(InputStream inputStream) throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); byte[] buf = new byte[1024]; int readLen = 0; while ((readLen = inputStream.read(buf)) != -1) { byteArrayOutputStream.write(buf, 0, readLen); } byte[] array = byteArrayOutputStream.toByteArray(); byteArrayOutputStream.close(); return array; } }
14.6 UDP网络通信编程
-
基本介绍:
-
基本流程:
-
应用案例:
-
接收端A:
public class UDPReceiverA { public static void main(String[] args) throws IOException { // 1.创建一个DatagramSocket对象,监听9999端口 DatagramSocket datagramSocket = new DatagramSocket(9999); System.out.println("接收端A 等待接收数据.."); // 2.1.接收数据包,接收数据包之前,需要先创建一个数据包用于接收数据包 byte[] buf = new byte[1024]; DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length); datagramSocket.receive(datagramPacket); // 2.2.解析数据包 int length = datagramPacket.getLength(); byte[] receiveData = datagramPacket.getData(); String s = new String(receiveData, 0, length); System.out.println(s); // 3.发送数据包,数据包需要指定 发送的数据(一般为字节数组)、发送数据的长度、目标InetAddress、目标端口 byte[] sendData = "A的答复:必须去!".getBytes(); datagramPacket = new DatagramPacket(sendData, sendData.length, InetAddress.getByName("LAPTOP-AP2A3J2N"), 8888); datagramSocket.send(datagramPacket); // 4.释放资源 datagramSocket.close(); System.out.println("接收端A退出程序..."); } }
-
发送端B:
public class UDPSenderB { public static void main(String[] args) throws IOException { // 1.创建一个DatagramSocket对象,监听8888端口 DatagramSocket datagramSocket = new DatagramSocket(8888); // 2.发送数据包,数据包需要指定 发送的数据(一般为字节数组)、发送数据的长度、目标InetAddress、目标端口 byte[] sendData = "来自B的消息:明天去参加NBA选秀吗?".getBytes(); DatagramPacket datagramPacket = new DatagramPacket(sendData, sendData.length, InetAddress.getByName("LAPTOP-AP2A3J2N"), 9999); datagramSocket.send(datagramPacket); // 3.1.接收数据包,接收数据包之前,需要先创建一个数据包用于接收数据包 byte[] buf = new byte[1024]; datagramPacket = new DatagramPacket(buf, buf.length); datagramSocket.receive(datagramPacket); // 3.2.解析数据包 int length = datagramPacket.getLength(); byte[] receiveData = datagramPacket.getData(); String s = new String(receiveData, 0, length); System.out.println(s); // 4.释放资源 datagramSocket.close(); System.out.println("发送端B退出程序..."); } }
-
14.7 TCP VS UDP
-
工具类使用方面:TCP使用ServerSocket来监听端口,Socket进行通信。而UDP使用DatagramSocket进行监听端口和通信。
-
TCP区分服务器端和客户端,而UDP只分发送端和接收端。
-
传输通道:
- TCP中服务端的Socket等待客户端成功连接之后返回,是一一对应的关系,因此TCP的通信是安全可靠的。
- 无需连接,只是在数据包中提供了完整的发送方信息和接收端消息,因此是不可靠的。
-
吞吐量:
- TCP:在连接中可进行大数据量的传输。
- UDP:每个数据报的大小限制在64K内,不适合传输大量数据。
-
效率:
- TCP:传输完毕,需释放已建立的连接,效率低。
- UDP:发送数据结束时无需释放资源(因为不是面向连接的),速度快。
14.8 本章习题
14.8.1 TCP习题
-
服务器端:
public class Homework01Server { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(9999); System.out.println("服务器端监听9999端口中..."); Socket socket = serverSocket.accept(); // 1.接收消息 InputStream inputStream = socket.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String answer = bufferedReader.readLine(); if ("name".equals(answer)) { answer = "irving"; } else if ("hobby".equals(answer)) { answer = "basketball"; } else { answer = "无法回答此问题"; } // 2.发送消息 OutputStream outputStream = socket.getOutputStream(); BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); bufferedWriter.write(answer); bufferedWriter.newLine(); bufferedWriter.flush(); // 3.释放资源 bufferedWriter.close(); bufferedReader.close(); System.out.println("服务器端成功退出..."); } } /* 服务器端监听9999端口中... 服务器端成功退出... */
-
客户端:
public class Homework01Client { public static void main(String[] args) throws IOException { Socket socket = new Socket(InetAddress.getLocalHost(), 9999); // 1.发送消息 OutputStream outputStream = socket.getOutputStream(); BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); Scanner scanner = new Scanner(System.in); System.out.print("请输入问题:"); String question = scanner.next(); bufferedWriter.write(question); bufferedWriter.newLine(); bufferedWriter.flush(); // 2.接收消息 InputStream inputStream = socket.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String apply = bufferedReader.readLine(); System.out.println(apply); // 3.释放资源 bufferedReader.close(); bufferedWriter.close(); System.out.println("客户端成功退出..."); } } /* 请输入问题:name irving 客户端成功退出... */
14.8.2 UDP习题
-
接收端:
public class Homework02Receiver { public static void main(String[] args) throws IOException { DatagramSocket datagramSocket = new DatagramSocket(9999); System.out.println("接收端Receiver正在监听9999端口..."); // 1.接收数据包 byte[] buf = new byte[1024]; DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length); datagramSocket.receive(datagramPacket); // 2.解析数据包 int length = datagramPacket.getLength(); byte[] receiveData = datagramPacket.getData(); System.out.println("收到来自发送端Sender的消息:" + new String(receiveData, 0, length)); // 3.发送数据包 byte[] sendData = "红楼梦、西游记、三国演义、水浒传".getBytes(); datagramPacket = new DatagramPacket(sendData, sendData.length, InetAddress.getByName("LAPTOP-AP2A3J2N"), 8888); datagramSocket.send(datagramPacket); // 4.关闭资源 datagramSocket.close(); System.out.println("接收端Receiver成功退出..."); } } /* 接收端Receiver正在监听9999端口... 收到来自发送端Sender的消息:四大名著有哪些? 接收端Receiver成功退出... */
-
发送端:
public class Homework02Sender { public static void main(String[] args) throws IOException { DatagramSocket datagramSocket = new DatagramSocket(8888); System.out.println("发送端Sender正在监听8888端口..."); // 1.发送数据包 byte[] sendData = "四大名著有哪些?".getBytes(); DatagramPacket datagramPacket = new DatagramPacket(sendData, sendData.length, InetAddress.getByName("LAPTOP-AP2A3J2N"), 9999); datagramSocket.send(datagramPacket); // 2.接收数据包 byte[] buf = new byte[1024]; datagramPacket = new DatagramPacket(buf, buf.length); datagramSocket.receive(datagramPacket); // 3.解析数据包 int length = datagramPacket.getLength(); byte[] receiveData = datagramPacket.getData(); System.out.println("收到来自接收端Receiver的答复:" + new String(receiveData, 0, length)); // 4.关闭资源 datagramSocket.close(); System.out.println("发送端Sender成功退出..."); } } /* 发送端Sender正在监听8888端口... 收到来自接收端Receiver的答复:红楼梦、西游记、三国演义、水浒传 发送端Sender成功退出... */
14.8.3 文件上传和下载习题
-
服务器端:
public class Homework03Server { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(9999); System.out.println("服务器端正在监听9999端口"); // 1.接收客户端的目标文件名 Socket socket = serverSocket.accept(); InputStream inputStream = socket.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String line = null; String downLoadFileName = ""; while ((line = bufferedReader.readLine()) != null) { downLoadFileName += line; } // 2.从本地获取文件 String resFileName = null; if ("高山流水".equals(downLoadFileName)) { resFileName = "d:\\高山流水.mp3"; } else { resFileName = "d:\\无名.mp3"; } BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(resFileName)); byte[] bytes = StreamUtils.inputStreamToByteArray(bufferedInputStream); // 3.传送文件给客户端 BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(socket.getOutputStream()); bufferedOutputStream.write(bytes); socket.shutdownOutput(); // 4.释放资源 bufferedOutputStream.close(); bufferedInputStream.close(); bufferedReader.close(); inputStream.close(); System.out.println("服务器端成功退出..."); } } /* 服务器端正在监听9999端口 服务器端成功退出... */
-
客户端:
public class Homework03Client { public static void main(String[] args) throws IOException { Socket socket = new Socket(InetAddress.getByName("LAPTOP-AP2A3J2N"), 9999); // 1.发送目标文件的文件名给服务器端 Scanner scanner = new Scanner(System.in); System.out.print("请输入想要获得的音乐名:"); String musicName = scanner.next(); BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); bufferedWriter.write(musicName); bufferedWriter.newLine(); bufferedWriter.flush(); socket.shutdownOutput(); // 2.接收来自服务器端的文件 BufferedInputStream bufferedInputStream = new BufferedInputStream(socket.getInputStream()); byte[] bytes = StreamUtils.inputStreamToByteArray(bufferedInputStream); // 3.将文件保存到指定目录 String destFilePath = "d:\\拷贝得到的文件.mp3"; BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(destFilePath)); bufferedOutputStream.write(bytes); // 4.释放资源 bufferedOutputStream.close(); bufferedInputStream.close(); bufferedWriter.close(); System.out.println("客户端成功退出..."); } } /* 请输入想要获得的音乐名:高山流水 客户端成功退出... */
-
工具类:
public class StreamUtils { public static byte[] inputStreamToByteArray(InputStream inputStream) throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); int readLength = 0; byte[] buf = new byte[1024]; while ((readLength = inputStream.read(buf)) != -1) { byteArrayOutputStream.write(buf, 0, readLength); } byte[] bytes = byteArrayOutputStream.toByteArray(); byteArrayOutputStream.close(); inputStream.close(); return bytes; } }
14.9 拓展知识
14.9.1 TCP 协议为什么要设计三次握手?
-
TCP协议的特点:TCP 协议,是一种可靠的,基于字节流的,面向连接的传输层协议。
- 可靠性体现在 TCP 协议通信双方的数据传输是稳定的,即便是在网络不好的情况下,TCP 都能够保证数据传输到目标端,而这个可靠性是基于数据包确认机制来实现的。
- TCP 通信双方的数据传输是通过字节流来实现传输的。
- 面向连接,是说数据传输之前,必须要建立一个连接,然后基于这个连接进行数据传输。
-
因为 TCP 是面向连接的协议,所以在进行数据通信之前,需要建立一个可靠的连接,TCP 采用了三次握手的方式来实现连接的建立。
-
客户端向服务端发送连接请求并携带同步序列号 SYN。
-
服务端收到请求后,发送 SYN 和 ACK, 这里的 SYN 表示服务端的同步序列号,ACK 表示对前面收到请求的一个确认,表示告诉客户端,我收到了你的请求。
-
客户端收到服务端的请求后,再次发送 ACK,这个 ACK 是针对服务端连接的一个确认,表示告诉服务端,我收到了你的请求。
-
-
之所以 TCP 要设计三次握手,我认为有三个方面的原因:
- TCP 是可靠性通信协议,所以 TCP 协议的通信双方都必须要维护一个序列号,去标记已经发送出去的数据包,哪些是已经被对方签收的。而三次握手就是通信双方相互告知序列号的起始值,为了确保这个序列号被收到,所以双方都需要有一个确认的操作。
- TCP 协议需要在一个不可靠的网络环境下实现可靠的数据传输,意味着通信双方必须要通过某种手段来实现一个可靠的数据传输通道,而三次通信是建立这样一个通道的最小值。当然还可以四次、五次,只是没必要浪费这个资源。
- 防止历史的重复连接初始化造成的混乱问题,比如说在网络比较差的情况下,客户端连续多次发送建立连接的请求,假设只有两次握手,那么服务端只能选择接受或者拒绝这个连接请求,但是服务端不知道这次请求是不是之前因为网络堵塞而过期的请求,也就是说服务端不知道当前客户端的连接是有效还是无效。
14.9.2 IO VS NIO
-
首先,I/O ,指的是 IO 流, 它可以实现数据从磁盘中的读取以及写入。在 Java 里面,提供了字符流和字节流两种方式来实现数据流的操作。其次,当程序是面向网络进行数据的 IO 操作的时候,Java 里面提供了 Socket 的方式来实现。
-
(如图)基于 Socket 的 IO 通信,它是属于阻塞式 IO,也就是说,在连接以及 IO 事件未就绪的情况下,当前的连接会处于阻塞等待的状态 。如果一旦某个连接处于阻塞状态,那么后续的连接都得等待。所以服务端能够处理的接数量非常有限。
-
NIO,是 JDK1.4 里面新增的一种 NEW IO 机制,相比于传统的 IO,NIO 在效率上做了很大的优化,并且新增了几个核心组件。
Channel
、Buffer
、Selectors
。(如图)另外,还提供了非阻塞的特性,所以,对于网络 IO 来说,NIO 通常也称为No-Block IO,非阻塞 IO。也就是说,通过 NIO 进行网络数据传输的时候,如果连接未就绪或者 IO 事件未就绪的情况下,服务端不会阻塞当前连接,而是继续去轮询后续的连接来处理。所以在 NIO 里面,服务端能够并行处理的链接数量更多。